srtree 1.0.0.5 → 2.0.0.0
raw patch · 40 files changed
+8337/−398 lines, 40 filesdep +attoparsecdep +attoparsec-exprdep +bytestringdep ~containersnew-component:exe:egraphGPnew-component:exe:eqsatreprnew-component:exe:ieeexplorenew-component:exe:srsimplifynew-component:exe:srtoolsnew-component:exe:tinygpPVP ok
version bump matches the API change (PVP)
Dependencies added: attoparsec, attoparsec-expr, bytestring, exceptions, filepath, hashable, ieee754, lens, list-shuffle, massiv, nlopt-haskell, normaldistribution, optparse-applicative, split, statistics, transformers, unordered-containers, zlib
Dependency ranges changed: containers
API changes (from Hackage documentation)
- Data.SRTree: derivative :: Floating a => Function -> a -> a
- Data.SRTree: deriveBy :: Bool -> Int -> Fix SRTree -> Fix SRTree
- Data.SRTree: deriveByParam :: Int -> Fix SRTree -> Fix SRTree
- Data.SRTree: deriveByVar :: Int -> Fix SRTree -> Fix SRTree
- Data.SRTree: evalFun :: Floating a => Function -> a -> a
- Data.SRTree: evalOp :: Floating a => Op -> a -> a -> a
- Data.SRTree: evalTree :: (Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> a
- Data.SRTree: forwardMode :: (Show a, Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> [a]
- Data.SRTree: gradParamsFwd :: (Show a, Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> (a, [a])
- Data.SRTree: gradParamsRev :: forall a. (Show a, Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> (a, [a])
- Data.SRTree: inverseFunc :: Function -> Function
- Data.SRTree.Internal: derivative :: Floating a => Function -> a -> a
- Data.SRTree.Internal: deriveBy :: Bool -> Int -> Fix SRTree -> Fix SRTree
- Data.SRTree.Internal: deriveByParam :: Int -> Fix SRTree -> Fix SRTree
- Data.SRTree.Internal: deriveByVar :: Int -> Fix SRTree -> Fix SRTree
- Data.SRTree.Internal: evalFun :: Floating a => Function -> a -> a
- Data.SRTree.Internal: evalOp :: Floating a => Op -> a -> a -> a
- Data.SRTree.Internal: evalTree :: (Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> a
- Data.SRTree.Internal: forwardMode :: (Show a, Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> [a]
- Data.SRTree.Internal: gradParamsFwd :: (Show a, Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> (a, [a])
- Data.SRTree.Internal: gradParamsRev :: forall a. (Show a, Num a, Floating a) => Vector a -> Vector Double -> (Double -> a) -> Fix SRTree -> (a, [a])
- Data.SRTree.Internal: instance GHC.Base.Functor (Data.SRTree.Internal.TupleF a)
- Data.SRTree.Internal: instance GHC.Base.Functor Data.SRTree.Internal.Tape
- Data.SRTree.Internal: instance GHC.Float.Floating a => GHC.Float.Floating (Data.SRTree.Internal.Tape a)
- Data.SRTree.Internal: instance GHC.Num.Num a => GHC.Num.Num (Data.SRTree.Internal.Tape a)
- Data.SRTree.Internal: instance GHC.Real.Fractional a => GHC.Real.Fractional (Data.SRTree.Internal.Tape a)
- Data.SRTree.Internal: instance GHC.Show.Show a => GHC.Show.Show (Data.SRTree.Internal.Tape a)
- Data.SRTree.Internal: inverseFunc :: Function -> Function
+ Algorithm.EqSat: applySingleMergeOnlyEqSat :: Monad m => CostFun -> [Rule] -> EGraphST m ()
+ Algorithm.EqSat: eqSat :: Monad m => Fix SRTree -> [Rule] -> CostFun -> Int -> EGraphST m (Fix SRTree)
+ Algorithm.EqSat: fromJust :: Maybe a -> a
+ Algorithm.EqSat: matchWithScheduler :: Int -> Int -> Rule -> Scheduler [Rule]
+ Algorithm.EqSat: recalculateBest :: Monad m => CostFun -> EClassId -> EGraphST m (Fix SRTree)
+ Algorithm.EqSat: runEqSat :: Monad m => CostFun -> [Rule] -> Int -> EGraphST m (Bool, Int)
+ Algorithm.EqSat: type CostMap = Map EClassId (Int, Fix SRTree)
+ Algorithm.EqSat: type Scheduler a = State (IntMap Int) a
+ Algorithm.EqSat.Build: add :: Monad m => CostFun -> ENode -> EGraphST m EClassId
+ Algorithm.EqSat.Build: addToDB :: Monad m => ENode -> EClassId -> EGraphST m ()
+ Algorithm.EqSat.Build: applyMatch :: Monad m => CostFun -> Rule -> (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m ()
+ Algorithm.EqSat.Build: applyMergeOnlyMatch :: Monad m => CostFun -> Rule -> (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m ()
+ Algorithm.EqSat.Build: canonizeMap :: Monad m => (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m (Map ClassOrVar ClassOrVar, ClassOrVar)
+ Algorithm.EqSat.Build: classOfENode :: Monad m => CostFun -> Map ClassOrVar ClassOrVar -> Pattern -> EGraphST m (Maybe EClassId)
+ Algorithm.EqSat.Build: cleanMaps :: Monad m => EGraphST m ()
+ Algorithm.EqSat.Build: createDB :: Monad m => EGraphST m DB
+ Algorithm.EqSat.Build: forceState :: Monad m => StateT s m ()
+ Algorithm.EqSat.Build: fromTree :: Monad m => CostFun -> Fix SRTree -> EGraphST m EClassId
+ Algorithm.EqSat.Build: fromTrees :: Monad m => CostFun -> [Fix SRTree] -> EGraphST m [EClassId]
+ Algorithm.EqSat.Build: getAllExpressionsFrom :: Monad m => EClassId -> EGraphST m [Fix SRTree]
+ Algorithm.EqSat.Build: getBest :: Monad m => EClassId -> EGraphST m (Fix SRTree)
+ Algorithm.EqSat.Build: getExpressionFrom :: Monad m => EClassId -> EGraphST m (Fix SRTree)
+ Algorithm.EqSat.Build: getRndExpressionFrom :: EClassId -> EGraphST (State StdGen) (Fix SRTree)
+ Algorithm.EqSat.Build: isValidConditions :: Monad m => Condition -> (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m Bool
+ Algorithm.EqSat.Build: isValidHeight :: Monad m => (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m Bool
+ Algorithm.EqSat.Build: merge :: Monad m => CostFun -> EClassId -> EClassId -> EGraphST m EClassId
+ Algorithm.EqSat.Build: modifyEClass :: Monad m => CostFun -> EClassId -> EGraphST m EClassId
+ Algorithm.EqSat.Build: populate :: Maybe IntTrie -> [EClassId] -> Maybe IntTrie
+ Algorithm.EqSat.Build: rebuild :: Monad m => CostFun -> EGraphST m ()
+ Algorithm.EqSat.Build: repair :: Monad m => CostFun -> EClassId -> ENode -> EGraphST m ()
+ Algorithm.EqSat.Build: repairAnalysis :: Monad m => CostFun -> EClassId -> ENode -> EGraphST m ()
+ Algorithm.EqSat.Build: reprPrat :: Monad m => CostFun -> Map ClassOrVar ClassOrVar -> Pattern -> EGraphST m EClassId
+ Algorithm.EqSat.DB: (:==:) :: Pattern -> Pattern -> Rule
+ Algorithm.EqSat.DB: (:=>) :: Pattern -> Pattern -> Rule
+ Algorithm.EqSat.DB: (:|) :: Rule -> Condition -> Rule
+ Algorithm.EqSat.DB: Atom :: ClassOrVar -> SRTree ClassOrVar -> Atom
+ Algorithm.EqSat.DB: Fixed :: SRTree Pattern -> Pattern
+ Algorithm.EqSat.DB: VarPat :: Char -> Pattern
+ Algorithm.EqSat.DB: cleanDB :: Monad m => EGraphST m ()
+ Algorithm.EqSat.DB: compileToQuery :: Pattern -> (Query, ClassOrVar)
+ Algorithm.EqSat.DB: data Atom
+ Algorithm.EqSat.DB: data Pattern
+ Algorithm.EqSat.DB: data Rule
+ Algorithm.EqSat.DB: domainX :: Monad m => ClassOrVar -> Query -> ClassOrVar -> EGraphST m [ClassOrVar]
+ Algorithm.EqSat.DB: elemOfAtom :: ClassOrVar -> Atom -> Bool
+ Algorithm.EqSat.DB: genericJoin :: Monad m => Query -> ClassOrVar -> EGraphST m [Map ClassOrVar ClassOrVar]
+ Algorithm.EqSat.DB: getConditions :: Rule -> [Condition]
+ Algorithm.EqSat.DB: getElems :: SRTree a -> [a]
+ Algorithm.EqSat.DB: getInt :: ClassOrVar -> Int
+ Algorithm.EqSat.DB: infix 3 :==:
+ Algorithm.EqSat.DB: infixl 2 :|
+ Algorithm.EqSat.DB: instance Data.String.IsString Algorithm.EqSat.DB.Pattern
+ Algorithm.EqSat.DB: instance GHC.Float.Floating Algorithm.EqSat.DB.Pattern
+ Algorithm.EqSat.DB: instance GHC.Num.Num Algorithm.EqSat.DB.Pattern
+ Algorithm.EqSat.DB: instance GHC.Real.Fractional Algorithm.EqSat.DB.Pattern
+ Algorithm.EqSat.DB: instance GHC.Show.Show Algorithm.EqSat.DB.Atom
+ Algorithm.EqSat.DB: instance GHC.Show.Show Algorithm.EqSat.DB.Pattern
+ Algorithm.EqSat.DB: instance GHC.Show.Show Algorithm.EqSat.DB.Rule
+ Algorithm.EqSat.DB: intersectAtoms :: Monad m => ClassOrVar -> Query -> ClassOrVar -> EGraphST m [EClassId]
+ Algorithm.EqSat.DB: intersectTries :: ClassOrVar -> Map ClassOrVar EClassId -> IntTrie -> [ClassOrVar] -> Maybe (HashSet EClassId)
+ Algorithm.EqSat.DB: isDiffFrom :: Int -> ClassOrVar -> Bool
+ Algorithm.EqSat.DB: match :: Monad m => Pattern -> EGraphST m [(Map ClassOrVar ClassOrVar, ClassOrVar)]
+ Algorithm.EqSat.DB: orderedVars :: Query -> [ClassOrVar]
+ Algorithm.EqSat.DB: source :: Rule -> Pattern
+ Algorithm.EqSat.DB: target :: Rule -> Pattern
+ Algorithm.EqSat.DB: type ClassOrVar = Either EClassId Int
+ Algorithm.EqSat.DB: type Condition = Map ClassOrVar ClassOrVar -> EGraph -> Bool
+ Algorithm.EqSat.DB: type Query = [Atom]
+ Algorithm.EqSat.DB: unFixPat :: Pattern -> SRTree Pattern
+ Algorithm.EqSat.DB: updateVar :: ClassOrVar -> ClassOrVar -> Query -> Query
+ Algorithm.EqSat.Egraph: ConstVal :: Double -> Consts
+ Algorithm.EqSat.Egraph: EClass :: Int -> HashSet ENodeEnc -> HashSet (EClassId, ENode) -> Int -> EClassData -> EClass
+ Algorithm.EqSat.Egraph: EDB :: HashSet (EClassId, ENode) -> HashSet (EClassId, ENode) -> DB -> RangeTree Double -> IntMap IntSet -> IntMap (RangeTree Double) -> IntSet -> Int -> EGraphDB
+ Algorithm.EqSat.Egraph: EData :: Cost -> ENode -> Consts -> Maybe Double -> Maybe PVector -> Int -> EClassData
+ Algorithm.EqSat.Egraph: EGraph :: ClassIdMap EClassId -> Map ENode EClassId -> ClassIdMap EClass -> EGraphDB -> EGraph
+ Algorithm.EqSat.Egraph: IntTrie :: HashSet EClassId -> IntMap IntTrie -> IntTrie
+ Algorithm.EqSat.Egraph: Negative :: Property
+ Algorithm.EqSat.Egraph: NonZero :: Property
+ Algorithm.EqSat.Egraph: NotConst :: Consts
+ Algorithm.EqSat.Egraph: ParamIx :: Int -> Consts
+ Algorithm.EqSat.Egraph: Positive :: Property
+ Algorithm.EqSat.Egraph: Real :: Property
+ Algorithm.EqSat.Egraph: [_analysis] :: EGraphDB -> HashSet (EClassId, ENode)
+ Algorithm.EqSat.Egraph: [_best] :: EClassData -> ENode
+ Algorithm.EqSat.Egraph: [_canonicalMap] :: EGraph -> ClassIdMap EClassId
+ Algorithm.EqSat.Egraph: [_consts] :: EClassData -> Consts
+ Algorithm.EqSat.Egraph: [_cost] :: EClassData -> Cost
+ Algorithm.EqSat.Egraph: [_eClassId] :: EClass -> Int
+ Algorithm.EqSat.Egraph: [_eClass] :: EGraph -> ClassIdMap EClass
+ Algorithm.EqSat.Egraph: [_eDB] :: EGraph -> EGraphDB
+ Algorithm.EqSat.Egraph: [_eNodeToEClass] :: EGraph -> Map ENode EClassId
+ Algorithm.EqSat.Egraph: [_eNodes] :: EClass -> HashSet ENodeEnc
+ Algorithm.EqSat.Egraph: [_fitRangeDB] :: EGraphDB -> RangeTree Double
+ Algorithm.EqSat.Egraph: [_fitness] :: EClassData -> Maybe Double
+ Algorithm.EqSat.Egraph: [_height] :: EClass -> Int
+ Algorithm.EqSat.Egraph: [_info] :: EClass -> EClassData
+ Algorithm.EqSat.Egraph: [_keys] :: IntTrie -> HashSet EClassId
+ Algorithm.EqSat.Egraph: [_nextId] :: EGraphDB -> Int
+ Algorithm.EqSat.Egraph: [_parents] :: EClass -> HashSet (EClassId, ENode)
+ Algorithm.EqSat.Egraph: [_patDB] :: EGraphDB -> DB
+ Algorithm.EqSat.Egraph: [_sizeDB] :: EGraphDB -> IntMap IntSet
+ Algorithm.EqSat.Egraph: [_sizeFitDB] :: EGraphDB -> IntMap (RangeTree Double)
+ Algorithm.EqSat.Egraph: [_size] :: EClassData -> Int
+ Algorithm.EqSat.Egraph: [_theta] :: EClassData -> Maybe PVector
+ Algorithm.EqSat.Egraph: [_trie] :: IntTrie -> IntMap IntTrie
+ Algorithm.EqSat.Egraph: [_unevaluated] :: EGraphDB -> IntSet
+ Algorithm.EqSat.Egraph: [_worklist] :: EGraphDB -> HashSet (EClassId, ENode)
+ Algorithm.EqSat.Egraph: analysis :: Lens' EGraphDB (HashSet (EClassId, ENode))
+ Algorithm.EqSat.Egraph: best :: Lens' EClassData ENode
+ Algorithm.EqSat.Egraph: canonical :: Monad m => EClassId -> EGraphST m EClassId
+ Algorithm.EqSat.Egraph: canonicalMap :: Lens' EGraph (ClassIdMap EClassId)
+ Algorithm.EqSat.Egraph: canonize :: Monad m => ENode -> EGraphST m ENode
+ Algorithm.EqSat.Egraph: consts :: Lens' EClassData Consts
+ Algorithm.EqSat.Egraph: cost :: Lens' EClassData Cost
+ Algorithm.EqSat.Egraph: createEClass :: EClassId -> ENode -> EClassData -> Int -> EClass
+ Algorithm.EqSat.Egraph: data Consts
+ Algorithm.EqSat.Egraph: data EClass
+ Algorithm.EqSat.Egraph: data EClassData
+ Algorithm.EqSat.Egraph: data EGraph
+ Algorithm.EqSat.Egraph: data EGraphDB
+ Algorithm.EqSat.Egraph: data IntTrie
+ Algorithm.EqSat.Egraph: data Property
+ Algorithm.EqSat.Egraph: decodeEnode :: ENodeEnc -> ENode
+ Algorithm.EqSat.Egraph: eClass :: Lens' EGraph (ClassIdMap EClass)
+ Algorithm.EqSat.Egraph: eClassId :: Lens' EClass Int
+ Algorithm.EqSat.Egraph: eDB :: Lens' EGraph EGraphDB
+ Algorithm.EqSat.Egraph: eNodeToEClass :: Lens' EGraph (Map ENode EClassId)
+ Algorithm.EqSat.Egraph: eNodes :: Lens' EClass (HashSet ENodeEnc)
+ Algorithm.EqSat.Egraph: emptyDB :: EGraphDB
+ Algorithm.EqSat.Egraph: emptyGraph :: EGraph
+ Algorithm.EqSat.Egraph: encodeEnode :: ENode -> ENodeEnc
+ Algorithm.EqSat.Egraph: fitRangeDB :: Lens' EGraphDB (RangeTree Double)
+ Algorithm.EqSat.Egraph: fitness :: Lens' EClassData (Maybe Double)
+ Algorithm.EqSat.Egraph: getEClass :: Monad m => EClassId -> EGraphST m EClass
+ Algorithm.EqSat.Egraph: getGreatest :: Ord a => RangeTree a -> (a, EClassId)
+ Algorithm.EqSat.Egraph: getSmallest :: Ord a => RangeTree a -> (a, EClassId)
+ Algorithm.EqSat.Egraph: getWithinRange :: Ord a => a -> a -> RangeTree a -> [EClassId]
+ Algorithm.EqSat.Egraph: height :: Lens' EClass Int
+ Algorithm.EqSat.Egraph: info :: Lens' EClass EClassData
+ Algorithm.EqSat.Egraph: insertRange :: (Ord a, Show a) => EClassId -> a -> RangeTree a -> RangeTree a
+ Algorithm.EqSat.Egraph: instance Data.Hashable.Class.Hashable Algorithm.EqSat.Egraph.ENode
+ Algorithm.EqSat.Egraph: instance GHC.Classes.Eq Algorithm.EqSat.Egraph.Consts
+ Algorithm.EqSat.Egraph: instance GHC.Classes.Eq Algorithm.EqSat.Egraph.EClass
+ Algorithm.EqSat.Egraph: instance GHC.Classes.Eq Algorithm.EqSat.Egraph.EClassData
+ Algorithm.EqSat.Egraph: instance GHC.Classes.Eq Algorithm.EqSat.Egraph.Property
+ Algorithm.EqSat.Egraph: instance GHC.Show.Show Algorithm.EqSat.Egraph.Consts
+ Algorithm.EqSat.Egraph: instance GHC.Show.Show Algorithm.EqSat.Egraph.EClass
+ Algorithm.EqSat.Egraph: instance GHC.Show.Show Algorithm.EqSat.Egraph.EClassData
+ Algorithm.EqSat.Egraph: instance GHC.Show.Show Algorithm.EqSat.Egraph.EGraph
+ Algorithm.EqSat.Egraph: instance GHC.Show.Show Algorithm.EqSat.Egraph.EGraphDB
+ Algorithm.EqSat.Egraph: instance GHC.Show.Show Algorithm.EqSat.Egraph.IntTrie
+ Algorithm.EqSat.Egraph: instance GHC.Show.Show Algorithm.EqSat.Egraph.Property
+ Algorithm.EqSat.Egraph: isConst :: Monad m => EClassId -> EGraphST m Bool
+ Algorithm.EqSat.Egraph: nextId :: Lens' EGraphDB Int
+ Algorithm.EqSat.Egraph: parents :: Lens' EClass (HashSet (EClassId, ENode))
+ Algorithm.EqSat.Egraph: patDB :: Lens' EGraphDB DB
+ Algorithm.EqSat.Egraph: removeRange :: (Ord a, Show a) => EClassId -> a -> RangeTree a -> RangeTree a
+ Algorithm.EqSat.Egraph: size :: Lens' EClassData Int
+ Algorithm.EqSat.Egraph: sizeDB :: Lens' EGraphDB (IntMap IntSet)
+ Algorithm.EqSat.Egraph: sizeFitDB :: Lens' EGraphDB (IntMap (RangeTree Double))
+ Algorithm.EqSat.Egraph: theta :: Lens' EClassData (Maybe PVector)
+ Algorithm.EqSat.Egraph: trie :: EClassId -> IntMap IntTrie -> IntTrie
+ Algorithm.EqSat.Egraph: type ClassIdMap = IntMap
+ Algorithm.EqSat.Egraph: type Cost = Int
+ Algorithm.EqSat.Egraph: type CostFun = SRTree Cost -> Cost
+ Algorithm.EqSat.Egraph: type DB = Map (SRTree ()) IntTrie
+ Algorithm.EqSat.Egraph: type EClassId = Int
+ Algorithm.EqSat.Egraph: type EGraphST m a = StateT EGraph m a
+ Algorithm.EqSat.Egraph: type ENode = SRTree EClassId
+ Algorithm.EqSat.Egraph: type ENodeEnc = (Int, Int, Int, Double)
+ Algorithm.EqSat.Egraph: type RangeTree a = Seq (a, EClassId)
+ Algorithm.EqSat.Egraph: unevaluated :: Lens' EGraphDB IntSet
+ Algorithm.EqSat.Egraph: worklist :: Lens' EGraphDB (HashSet (EClassId, ENode))
+ Algorithm.EqSat.Info: calculateConsts :: Monad m => SRTree EClassId -> EGraphST m Consts
+ Algorithm.EqSat.Info: calculateCost :: Monad m => CostFun -> SRTree EClassId -> EGraphST m Cost
+ Algorithm.EqSat.Info: calculateHeights :: Monad m => EGraphST m ()
+ Algorithm.EqSat.Info: combineConsts :: SRTree Consts -> Consts
+ Algorithm.EqSat.Info: getChildrenMinHeight :: Monad m => ENode -> EGraphST m Int
+ Algorithm.EqSat.Info: insertFitness :: Monad m => EClassId -> Double -> PVector -> EGraphST m ()
+ Algorithm.EqSat.Info: joinData :: EClassData -> EClassData -> EClassData
+ Algorithm.EqSat.Info: makeAnalysis :: Monad m => CostFun -> ENode -> EGraphST m EClassData
+ Algorithm.EqSat.Queries: findRootClasses :: Monad m => EGraphST m [EClassId]
+ Algorithm.EqSat.Queries: getEClassesThat :: Monad m => (EClass -> Bool) -> EGraphST m [EClassId]
+ Algorithm.EqSat.Queries: getTopECLassThat :: Monad m => Int -> (EClass -> Bool) -> EGraphST m [EClassId]
+ Algorithm.EqSat.Queries: getTopECLassWithSize :: Monad m => Int -> Int -> EGraphST m [EClassId]
+ Algorithm.EqSat.Queries: updateFitness :: Monad m => Double -> EClassId -> EGraphST m ()
+ Algorithm.EqSat.Simplify: (:==:) :: Pattern -> Pattern -> Rule
+ Algorithm.EqSat.Simplify: (:=>) :: Pattern -> Pattern -> Rule
+ Algorithm.EqSat.Simplify: (:|) :: Rule -> Condition -> Rule
+ Algorithm.EqSat.Simplify: applyMergeOnlyDftl :: Monad m => CostFun -> EGraphST m ()
+ Algorithm.EqSat.Simplify: data Rule
+ Algorithm.EqSat.Simplify: infix 3 :==:
+ Algorithm.EqSat.Simplify: infixl 2 :|
+ Algorithm.EqSat.Simplify: rewriteBasic :: [Rule]
+ Algorithm.EqSat.Simplify: rewrites :: [Rule]
+ Algorithm.EqSat.Simplify: rewritesFun :: [Rule]
+ Algorithm.EqSat.Simplify: simplifyEqSatDefault :: Fix SRTree -> Fix SRTree
+ Algorithm.Massiv.Utils: NegDef :: NegDef
+ Algorithm.Massiv.Utils: appendCol :: MonadThrow m => SRMatrix -> PVector -> m SRMatrix
+ Algorithm.Massiv.Utils: appendRow :: MonadThrow m => SRMatrix -> PVector -> m SRMatrix
+ Algorithm.Massiv.Utils: backwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector
+ Algorithm.Massiv.Utils: cholesky :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> m SRMatrix
+ Algorithm.Massiv.Utils: chunkBy :: Int -> [t] -> [[t]]
+ Algorithm.Massiv.Utils: cubicSplineCoefficients :: [(Double, Double)] -> [PolyCos]
+ Algorithm.Massiv.Utils: data NegDef
+ Algorithm.Massiv.Utils: det :: SRMatrix -> Double
+ Algorithm.Massiv.Utils: detChol :: SRMatrix -> Double
+ Algorithm.Massiv.Utils: forwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector
+ Algorithm.Massiv.Utils: genSplineFun :: [(Double, Double)] -> Double -> Double
+ Algorithm.Massiv.Utils: getCols :: SRMatrix -> Array B Ix1 PVector
+ Algorithm.Massiv.Utils: getRows :: SRMatrix -> Array B Ix1 PVector
+ Algorithm.Massiv.Utils: instance GHC.Exception.Type.Exception Algorithm.Massiv.Utils.NegDef
+ Algorithm.Massiv.Utils: instance GHC.Show.Show Algorithm.Massiv.Utils.NegDef
+ Algorithm.Massiv.Utils: invChol :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> m SRMatrix
+ Algorithm.Massiv.Utils: linSpace :: Int -> (Double, Double) -> [Double]
+ Algorithm.Massiv.Utils: lu :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> m (SRMatrix, SRMatrix)
+ Algorithm.Massiv.Utils: luSolve :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector
+ Algorithm.Massiv.Utils: outer :: MonadThrow m => PVector -> PVector -> m SRMatrix
+ Algorithm.Massiv.Utils: rangedLinearDotProd :: PrimMonad m => Int -> Int -> Int -> MMassArray m -> m Double
+ Algorithm.Massiv.Utils: type MMassArray m = MArray (PrimState m) S Ix2 Double
+ Algorithm.Massiv.Utils: type PolyCos = (Double, Double, Double)
+ Algorithm.Massiv.Utils: updateS :: Array S Ix1 Double -> [(Int, Double)] -> Array S Ix1 Double
+ Algorithm.SRTree.AD: forwardMode :: Array S Ix2 Double -> Array S Ix1 Double -> SRVector -> Fix SRTree -> (Array D Ix1 Double, Array S Ix1 Double)
+ Algorithm.SRTree.AD: forwardModeUnique :: SRMatrix -> PVector -> SRVector -> Fix SRTree -> (SRVector, Array S Ix1 Double)
+ Algorithm.SRTree.AD: forwardModeUniqueJac :: SRMatrix -> PVector -> Fix SRTree -> [PVector]
+ Algorithm.SRTree.AD: instance GHC.Base.Functor (Algorithm.SRTree.AD.TupleF a)
+ Algorithm.SRTree.AD: reverseModeUnique :: SRMatrix -> PVector -> SRVector -> (SRVector -> SRVector) -> Fix SRTree -> (Array D Ix1 Double, Array S Ix1 Double)
+ Algorithm.SRTree.ConfidenceIntervals: Bates :: PType
+ Algorithm.SRTree.ConfidenceIntervals: CI :: Double -> Double -> Double -> CI
+ Algorithm.SRTree.ConfidenceIntervals: Constrained :: PType
+ Algorithm.SRTree.ConfidenceIntervals: Laplace :: BasicStats -> CIType
+ Algorithm.SRTree.ConfidenceIntervals: MkStats :: SRMatrix -> SRMatrix -> PVector -> BasicStats
+ Algorithm.SRTree.ConfidenceIntervals: ODE :: PType
+ Algorithm.SRTree.ConfidenceIntervals: Profile :: BasicStats -> [ProfileT] -> CIType
+ Algorithm.SRTree.ConfidenceIntervals: ProfileT :: PVector -> SRMatrix -> Double -> (Double -> Double) -> (Double -> Double) -> ProfileT
+ Algorithm.SRTree.ConfidenceIntervals: [_corr] :: BasicStats -> SRMatrix
+ Algorithm.SRTree.ConfidenceIntervals: [_cov] :: BasicStats -> SRMatrix
+ Algorithm.SRTree.ConfidenceIntervals: [_opt] :: ProfileT -> Double
+ Algorithm.SRTree.ConfidenceIntervals: [_stdErr] :: BasicStats -> PVector
+ Algorithm.SRTree.ConfidenceIntervals: [_tau2theta] :: ProfileT -> Double -> Double
+ Algorithm.SRTree.ConfidenceIntervals: [_taus] :: ProfileT -> PVector
+ Algorithm.SRTree.ConfidenceIntervals: [_theta2tau] :: ProfileT -> Double -> Double
+ Algorithm.SRTree.ConfidenceIntervals: [_thetas] :: ProfileT -> SRMatrix
+ Algorithm.SRTree.ConfidenceIntervals: [est_] :: CI -> Double
+ Algorithm.SRTree.ConfidenceIntervals: [lower_] :: CI -> Double
+ Algorithm.SRTree.ConfidenceIntervals: [upper_] :: CI -> Double
+ Algorithm.SRTree.ConfidenceIntervals: approximateContour :: Int -> Int -> [ProfileT] -> Int -> Int -> Double -> [(Double, Double)]
+ Algorithm.SRTree.ConfidenceIntervals: calcTheta0 :: Distribution -> Fix SRTree -> Fix SRTree
+ Algorithm.SRTree.ConfidenceIntervals: createSplines :: PVector -> SRMatrix -> Double -> Double -> Int -> (Double -> Double, Double -> Double)
+ Algorithm.SRTree.ConfidenceIntervals: data BasicStats
+ Algorithm.SRTree.ConfidenceIntervals: data CI
+ Algorithm.SRTree.ConfidenceIntervals: data CIType
+ Algorithm.SRTree.ConfidenceIntervals: data PType
+ Algorithm.SRTree.ConfidenceIntervals: data ProfileT
+ Algorithm.SRTree.ConfidenceIntervals: evalVar :: PVector -> Fix SRTree -> Fix SRTree
+ Algorithm.SRTree.ConfidenceIntervals: getAllProfiles :: PType -> Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> PVector -> [CI] -> Double -> [ProfileT]
+ Algorithm.SRTree.ConfidenceIntervals: getCol :: Int -> SRMatrix -> PVector
+ Algorithm.SRTree.ConfidenceIntervals: getEndPoint :: Distribution -> Maybe Double -> Array S Ix2 Double -> Array S Ix1 Double -> Fix SRTree -> Array S Ix1 Double -> Double -> Int -> Bool -> Double
+ Algorithm.SRTree.ConfidenceIntervals: getProfile :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> Double -> Double -> Int -> Either PVector ProfileT
+ Algorithm.SRTree.ConfidenceIntervals: getProfileCnstr :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> Double -> Double -> Int -> Either PVector ProfileT
+ Algorithm.SRTree.ConfidenceIntervals: getProfileODE :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> Double -> CI -> Double -> Int -> Either PVector ProfileT
+ Algorithm.SRTree.ConfidenceIntervals: getStatsFromModel :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> BasicStats
+ Algorithm.SRTree.ConfidenceIntervals: instance GHC.Classes.Eq Algorithm.SRTree.ConfidenceIntervals.BasicStats
+ Algorithm.SRTree.ConfidenceIntervals: instance GHC.Classes.Eq Algorithm.SRTree.ConfidenceIntervals.CI
+ Algorithm.SRTree.ConfidenceIntervals: instance GHC.Read.Read Algorithm.SRTree.ConfidenceIntervals.CI
+ Algorithm.SRTree.ConfidenceIntervals: instance GHC.Read.Read Algorithm.SRTree.ConfidenceIntervals.PType
+ Algorithm.SRTree.ConfidenceIntervals: instance GHC.Show.Show Algorithm.SRTree.ConfidenceIntervals.BasicStats
+ Algorithm.SRTree.ConfidenceIntervals: instance GHC.Show.Show Algorithm.SRTree.ConfidenceIntervals.CI
+ Algorithm.SRTree.ConfidenceIntervals: instance GHC.Show.Show Algorithm.SRTree.ConfidenceIntervals.PType
+ Algorithm.SRTree.ConfidenceIntervals: inverseDist :: Floating p => Distribution -> p -> p
+ Algorithm.SRTree.ConfidenceIntervals: paramCI :: CIType -> Int -> PVector -> Double -> [CI]
+ Algorithm.SRTree.ConfidenceIntervals: predictionCI :: CIType -> Distribution -> (SRMatrix -> PVector) -> (SRMatrix -> [PVector]) -> (CI -> PVector -> Fix SRTree -> (Double -> Double, Double)) -> SRMatrix -> Fix SRTree -> PVector -> Double -> [CI] -> [CI]
+ Algorithm.SRTree.ConfidenceIntervals: printCI :: Int -> CI -> IO ()
+ Algorithm.SRTree.ConfidenceIntervals: replaceParam0 :: Fix SRTree -> Fix SRTree -> Fix SRTree
+ Algorithm.SRTree.ConfidenceIntervals: rk :: (Double -> PVector -> PVector) -> (Double, PVector) -> Double -> (Double, PVector)
+ Algorithm.SRTree.ConfidenceIntervals: showCI :: Int -> CI -> String
+ Algorithm.SRTree.ConfidenceIntervals: sortOnFirst :: PVector -> PVector -> [(Double, Double)]
+ Algorithm.SRTree.ConfidenceIntervals: splinesSketches :: Double -> PVector -> PVector -> (Double -> Double) -> Double -> Double
+ Algorithm.SRTree.Likelihoods: Bernoulli :: Distribution
+ Algorithm.SRTree.Likelihoods: Gaussian :: Distribution
+ Algorithm.SRTree.Likelihoods: Poisson :: Distribution
+ Algorithm.SRTree.Likelihoods: data Distribution
+ Algorithm.SRTree.Likelihoods: fisherNLL :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> SRVector
+ Algorithm.SRTree.Likelihoods: getSErr :: Num a => Distribution -> a -> Maybe a -> a
+ Algorithm.SRTree.Likelihoods: gradNLL :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (Double, SRVector)
+ Algorithm.SRTree.Likelihoods: gradNLLNonUnique :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (Double, SRVector)
+ Algorithm.SRTree.Likelihoods: hessianNLL :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> SRMatrix
+ Algorithm.SRTree.Likelihoods: instance GHC.Enum.Bounded Algorithm.SRTree.Likelihoods.Distribution
+ Algorithm.SRTree.Likelihoods: instance GHC.Enum.Enum Algorithm.SRTree.Likelihoods.Distribution
+ Algorithm.SRTree.Likelihoods: instance GHC.Read.Read Algorithm.SRTree.Likelihoods.Distribution
+ Algorithm.SRTree.Likelihoods: instance GHC.Show.Show Algorithm.SRTree.Likelihoods.Distribution
+ Algorithm.SRTree.Likelihoods: mse :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double
+ Algorithm.SRTree.Likelihoods: nll :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> Double
+ Algorithm.SRTree.Likelihoods: predict :: Distribution -> Fix SRTree -> PVector -> SRMatrix -> SRVector
+ Algorithm.SRTree.Likelihoods: r2 :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double
+ Algorithm.SRTree.Likelihoods: rmse :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double
+ Algorithm.SRTree.Likelihoods: sse :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double
+ Algorithm.SRTree.Likelihoods: type PVector = Array S Ix1 Double
+ Algorithm.SRTree.Likelihoods: type SRMatrix = Array S Ix2 Double
+ Algorithm.SRTree.ModelSelection: aic :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: bic :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: evidence :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: logFunctional :: Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: logFunctionalFreq :: Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: logParameters :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: logParametersLatt :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: mdl :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: mdlFreq :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: mdlLatt :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: nll' :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double
+ Algorithm.SRTree.ModelSelection: treeToNat :: Fix SRTree -> Double
+ Algorithm.SRTree.NonlinearOpt: (:|) :: a -> [a] -> NonEmpty a
+ Algorithm.SRTree.NonlinearOpt: AUGLAG_EQ_GLOBAL :: GlobalProblem -> AugLagAlgorithm
+ Algorithm.SRTree.NonlinearOpt: AUGLAG_EQ_LOCAL :: LocalProblem -> AugLagAlgorithm
+ Algorithm.SRTree.NonlinearOpt: AUGLAG_GLOBAL :: GlobalProblem -> InequalityConstraints -> InequalityConstraintsD -> AugLagAlgorithm
+ Algorithm.SRTree.NonlinearOpt: AUGLAG_LOCAL :: LocalProblem -> InequalityConstraints -> InequalityConstraintsD -> AugLagAlgorithm
+ Algorithm.SRTree.NonlinearOpt: AugLagProblem :: EqualityConstraints -> EqualityConstraintsD -> AugLagAlgorithm -> AugLagProblem
+ Algorithm.SRTree.NonlinearOpt: BOBYQA :: Objective -> [Bounds] -> Maybe InitialStep -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: CCSAQ :: ObjectiveD -> Preconditioner -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: COBYLA :: Objective -> [Bounds] -> InequalityConstraints -> EqualityConstraints -> Maybe InitialStep -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: CRS2_LM :: Objective -> RandomSeed -> Maybe Population -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: DIRECT :: Objective -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: DIRECT_L :: Objective -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: DIRECT_L_NOSCAL :: Objective -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: DIRECT_L_RAND :: Objective -> RandomSeed -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: DIRECT_L_RAND_NOSCAL :: Objective -> RandomSeed -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: DIRECT_NOSCAL :: Objective -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: Don'tSeed :: RandomSeed
+ Algorithm.SRTree.NonlinearOpt: ESCH :: Objective -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: EqualityConstraint :: Constraint s v -> Double -> EqualityConstraint s v
+ Algorithm.SRTree.NonlinearOpt: FAILURE :: Result
+ Algorithm.SRTree.NonlinearOpt: FORCED_STOP :: Result
+ Algorithm.SRTree.NonlinearOpt: FTOL_REACHED :: Result
+ Algorithm.SRTree.NonlinearOpt: GlobalProblem :: Vector Double -> Vector Double -> NonEmpty StoppingCondition -> GlobalAlgorithm -> GlobalProblem
+ Algorithm.SRTree.NonlinearOpt: INVALID_ARGS :: Result
+ Algorithm.SRTree.NonlinearOpt: ISRES :: Objective -> InequalityConstraints -> EqualityConstraints -> RandomSeed -> Maybe Population -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: InequalityConstraint :: Constraint s v -> Double -> InequalityConstraint s v
+ Algorithm.SRTree.NonlinearOpt: InitialStep :: Vector Double -> InitialStep
+ Algorithm.SRTree.NonlinearOpt: LBFGS :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: LBFGS_NOCEDAL :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: LocalProblem :: Word -> NonEmpty StoppingCondition -> LocalAlgorithm -> LocalProblem
+ Algorithm.SRTree.NonlinearOpt: LowerBounds :: Vector Double -> Bounds
+ Algorithm.SRTree.NonlinearOpt: MAXEVAL_REACHED :: Result
+ Algorithm.SRTree.NonlinearOpt: MAXTIME_REACHED :: Result
+ Algorithm.SRTree.NonlinearOpt: MLSL :: Objective -> LocalProblem -> Maybe Population -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: MLSL_LDS :: Objective -> LocalProblem -> Maybe Population -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: MMA :: ObjectiveD -> InequalityConstraintsD -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: MaximumEvaluations :: Word -> StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: MaximumTime :: Double -> StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: MinimumValue :: Double -> StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: NELDERMEAD :: Objective -> [Bounds] -> Maybe InitialStep -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: NEWUOA :: Objective -> Maybe InitialStep -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: NEWUOA_BOUND :: Objective -> [Bounds] -> Maybe InitialStep -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: ORIG_DIRECT :: Objective -> InequalityConstraints -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: ORIG_DIRECT_L :: Objective -> InequalityConstraints -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: OUT_OF_MEMORY :: Result
+ Algorithm.SRTree.NonlinearOpt: ObjectiveAbsoluteTolerance :: Double -> StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: ObjectiveRelativeTolerance :: Double -> StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: PRAXIS :: Objective -> [Bounds] -> Maybe InitialStep -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: ParameterAbsoluteTolerance :: Vector Double -> StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: ParameterRelativeTolerance :: Double -> StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: Population :: Word -> Population
+ Algorithm.SRTree.NonlinearOpt: Preconditioned :: Preconditioner -> s -> Constraint s v
+ Algorithm.SRTree.NonlinearOpt: ROUNDOFF_LIMITED :: Result
+ Algorithm.SRTree.NonlinearOpt: SBPLX :: Objective -> [Bounds] -> Maybe InitialStep -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: SLSQP :: ObjectiveD -> [Bounds] -> InequalityConstraintsD -> EqualityConstraintsD -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: STOGO :: ObjectiveD -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: STOGO_RAND :: ObjectiveD -> RandomSeed -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: STOPVAL_REACHED :: Result
+ Algorithm.SRTree.NonlinearOpt: SUCCESS :: Result
+ Algorithm.SRTree.NonlinearOpt: Scalar :: s -> Constraint s v
+ Algorithm.SRTree.NonlinearOpt: SeedFromTime :: RandomSeed
+ Algorithm.SRTree.NonlinearOpt: SeedValue :: Word -> RandomSeed
+ Algorithm.SRTree.NonlinearOpt: Solution :: Double -> Vector Double -> Result -> Solution
+ Algorithm.SRTree.NonlinearOpt: TNEWTON :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: TNEWTON_PRECOND :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: TNEWTON_PRECOND_RESTART :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: TNEWTON_RESTART :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: UpperBounds :: Vector Double -> Bounds
+ Algorithm.SRTree.NonlinearOpt: VAR1 :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: VAR2 :: ObjectiveD -> Maybe VectorStorage -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: Vector :: Word -> v -> Constraint s v
+ Algorithm.SRTree.NonlinearOpt: VectorStorage :: Word -> VectorStorage
+ Algorithm.SRTree.NonlinearOpt: XTOL_REACHED :: Result
+ Algorithm.SRTree.NonlinearOpt: [alEqualityD] :: AugLagProblem -> EqualityConstraintsD
+ Algorithm.SRTree.NonlinearOpt: [alEquality] :: AugLagProblem -> EqualityConstraints
+ Algorithm.SRTree.NonlinearOpt: [alalgorithm] :: AugLagProblem -> AugLagAlgorithm
+ Algorithm.SRTree.NonlinearOpt: [eqConstraintFunctions] :: EqualityConstraint s v -> Constraint s v
+ Algorithm.SRTree.NonlinearOpt: [eqConstraintTolerance] :: EqualityConstraint s v -> Double
+ Algorithm.SRTree.NonlinearOpt: [galgorithm] :: GlobalProblem -> GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: [gstop] :: GlobalProblem -> NonEmpty StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: [ineqConstraintFunctions] :: InequalityConstraint s v -> Constraint s v
+ Algorithm.SRTree.NonlinearOpt: [ineqConstraintTolerance] :: InequalityConstraint s v -> Double
+ Algorithm.SRTree.NonlinearOpt: [lalgorithm] :: LocalProblem -> LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: [lowerBounds] :: GlobalProblem -> Vector Double
+ Algorithm.SRTree.NonlinearOpt: [lsize] :: LocalProblem -> Word
+ Algorithm.SRTree.NonlinearOpt: [lstop] :: LocalProblem -> NonEmpty StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: [solutionCost] :: Solution -> Double
+ Algorithm.SRTree.NonlinearOpt: [solutionParams] :: Solution -> Vector Double
+ Algorithm.SRTree.NonlinearOpt: [solutionResult] :: Solution -> Result
+ Algorithm.SRTree.NonlinearOpt: [upperBounds] :: GlobalProblem -> Vector Double
+ Algorithm.SRTree.NonlinearOpt: data AugLagAlgorithm
+ Algorithm.SRTree.NonlinearOpt: data AugLagProblem
+ Algorithm.SRTree.NonlinearOpt: data Bounds
+ Algorithm.SRTree.NonlinearOpt: data Constraint s v
+ Algorithm.SRTree.NonlinearOpt: data EqualityConstraint s v
+ Algorithm.SRTree.NonlinearOpt: data GlobalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: data GlobalProblem
+ Algorithm.SRTree.NonlinearOpt: data InequalityConstraint s v
+ Algorithm.SRTree.NonlinearOpt: data LocalAlgorithm
+ Algorithm.SRTree.NonlinearOpt: data LocalProblem
+ Algorithm.SRTree.NonlinearOpt: data () => NonEmpty a
+ Algorithm.SRTree.NonlinearOpt: data RandomSeed
+ Algorithm.SRTree.NonlinearOpt: data () => Result
+ Algorithm.SRTree.NonlinearOpt: data Solution
+ Algorithm.SRTree.NonlinearOpt: data StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: infixr 5 :|
+ Algorithm.SRTree.NonlinearOpt: instance Algorithm.SRTree.NonlinearOpt.ApplyConstraint (Algorithm.SRTree.NonlinearOpt.EqualityConstraint Algorithm.SRTree.NonlinearOpt.ScalarConstraint Algorithm.SRTree.NonlinearOpt.VectorConstraint)
+ Algorithm.SRTree.NonlinearOpt: instance Algorithm.SRTree.NonlinearOpt.ApplyConstraint (Algorithm.SRTree.NonlinearOpt.EqualityConstraint Algorithm.SRTree.NonlinearOpt.ScalarConstraintD Algorithm.SRTree.NonlinearOpt.VectorConstraintD)
+ Algorithm.SRTree.NonlinearOpt: instance Algorithm.SRTree.NonlinearOpt.ApplyConstraint (Algorithm.SRTree.NonlinearOpt.InequalityConstraint Algorithm.SRTree.NonlinearOpt.ScalarConstraint Algorithm.SRTree.NonlinearOpt.VectorConstraint)
+ Algorithm.SRTree.NonlinearOpt: instance Algorithm.SRTree.NonlinearOpt.ApplyConstraint (Algorithm.SRTree.NonlinearOpt.InequalityConstraint Algorithm.SRTree.NonlinearOpt.ScalarConstraintD Algorithm.SRTree.NonlinearOpt.VectorConstraintD)
+ Algorithm.SRTree.NonlinearOpt: instance Algorithm.SRTree.NonlinearOpt.ProblemSize Algorithm.SRTree.NonlinearOpt.AugLagProblem
+ Algorithm.SRTree.NonlinearOpt: instance Algorithm.SRTree.NonlinearOpt.ProblemSize Algorithm.SRTree.NonlinearOpt.GlobalProblem
+ Algorithm.SRTree.NonlinearOpt: instance Algorithm.SRTree.NonlinearOpt.ProblemSize Algorithm.SRTree.NonlinearOpt.LocalProblem
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Classes.Eq Algorithm.SRTree.NonlinearOpt.Bounds
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Classes.Eq Algorithm.SRTree.NonlinearOpt.InitialStep
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Classes.Eq Algorithm.SRTree.NonlinearOpt.Population
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Classes.Eq Algorithm.SRTree.NonlinearOpt.RandomSeed
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Classes.Eq Algorithm.SRTree.NonlinearOpt.Solution
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Classes.Eq Algorithm.SRTree.NonlinearOpt.StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Classes.Eq Algorithm.SRTree.NonlinearOpt.VectorStorage
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Exception.Type.Exception Algorithm.SRTree.NonlinearOpt.NloptException
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Read.Read Algorithm.SRTree.NonlinearOpt.Bounds
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Read.Read Algorithm.SRTree.NonlinearOpt.InitialStep
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Read.Read Algorithm.SRTree.NonlinearOpt.Population
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Read.Read Algorithm.SRTree.NonlinearOpt.RandomSeed
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Read.Read Algorithm.SRTree.NonlinearOpt.Solution
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Read.Read Algorithm.SRTree.NonlinearOpt.StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Read.Read Algorithm.SRTree.NonlinearOpt.VectorStorage
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.Bounds
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.InitialStep
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.NloptException
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.Population
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.RandomSeed
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.Solution
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.StoppingCondition
+ Algorithm.SRTree.NonlinearOpt: instance GHC.Show.Show Algorithm.SRTree.NonlinearOpt.VectorStorage
+ Algorithm.SRTree.NonlinearOpt: minimizeAugLag :: AugLagProblem -> Vector Double -> Either Result Solution
+ Algorithm.SRTree.NonlinearOpt: minimizeGlobal :: GlobalProblem -> Vector Double -> Either Result Solution
+ Algorithm.SRTree.NonlinearOpt: minimizeLocal :: LocalProblem -> Vector Double -> Either Result Solution
+ Algorithm.SRTree.NonlinearOpt: newtype InitialStep
+ Algorithm.SRTree.NonlinearOpt: newtype Population
+ Algorithm.SRTree.NonlinearOpt: newtype VectorStorage
+ Algorithm.SRTree.NonlinearOpt: type EqualityConstraints = [EqualityConstraint ScalarConstraint VectorConstraint]
+ Algorithm.SRTree.NonlinearOpt: type EqualityConstraintsD = [EqualityConstraint ScalarConstraintD VectorConstraintD]
+ Algorithm.SRTree.NonlinearOpt: type InequalityConstraints = [InequalityConstraint ScalarConstraint VectorConstraint]
+ Algorithm.SRTree.NonlinearOpt: type InequalityConstraintsD = [InequalityConstraint ScalarConstraintD VectorConstraintD]
+ Algorithm.SRTree.NonlinearOpt: type Objective = -- | Parameter vector Vector Double -> -- | Objective function value Double
+ Algorithm.SRTree.NonlinearOpt: type ObjectiveD = -- | Parameter vector Vector Double -> -- | (Objective function value, gradient) (Double, Vector Double)
+ Algorithm.SRTree.NonlinearOpt: type Preconditioner = -- | Parameter vector @x@ Vector Double -> -- | Vector @v@ to precondition at @x@ Vector Double -> -- | Preconditioned vector @vpre@ Vector Double
+ Algorithm.SRTree.NonlinearOpt: type ScalarConstraint = -- | Parameter vector @x@ Vector Double -> -- | Constraint violation (deviation from 0) Double
+ Algorithm.SRTree.NonlinearOpt: type ScalarConstraintD = -- | Parameter vector Vector Double -> -- | (Constraint violation, constraint gradient) (Double, Vector Double)
+ Algorithm.SRTree.NonlinearOpt: type VectorConstraint = -- | Parameter vector Vector Double -> -- | Constraint Vectorize Word -> -- | Constraint violation vector Vector Double
+ Algorithm.SRTree.NonlinearOpt: type VectorConstraintD = -- | Parameter vector Vector Double -> -- | Constraint Vectorize Word -> -- | (Constraint violation vector, -- constraint Jacobian) (Vector Double, Matrix Double)
+ Algorithm.SRTree.Opt: estimateSErr :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Int -> Maybe Double
+ Algorithm.SRTree.Opt: minimizeBinomial :: Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)
+ Algorithm.SRTree.Opt: minimizeGaussian :: Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)
+ Algorithm.SRTree.Opt: minimizeNLL :: Distribution -> Maybe Double -> Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)
+ Algorithm.SRTree.Opt: minimizeNLLNonUnique :: Distribution -> Maybe Double -> Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)
+ Algorithm.SRTree.Opt: minimizeNLLWithFixedParam :: Distribution -> Maybe Double -> Int -> SRMatrix -> PVector -> Fix SRTree -> Int -> PVector -> PVector
+ Algorithm.SRTree.Opt: minimizePoisson :: Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)
+ Data.SRTree: AQ :: Op
+ Data.SRTree: Cube :: Function
+ Data.SRTree: LogAbs :: Function
+ Data.SRTree: PowerAbs :: Op
+ Data.SRTree: Recip :: Function
+ Data.SRTree: SqrtAbs :: Function
+ Data.SRTree: childrenOf :: SRTree a -> [a]
+ Data.SRTree: constv :: Double -> Fix SRTree
+ Data.SRTree: countUniqueTokens :: Num a => Fix SRTree -> a
+ Data.SRTree: getIntConsts :: Fix SRTree -> [Double]
+ Data.SRTree: getOperator :: SRTree a -> SRTree ()
+ Data.SRTree: numberOfVars :: Num a => Fix SRTree -> a
+ Data.SRTree: relabelVars :: Fix SRTree -> Fix SRTree
+ Data.SRTree: replaceChildren :: [a] -> SRTree b -> SRTree a
+ Data.SRTree.Datasets: loadDataset :: FilePath -> Bool -> IO ((SRMatrix, PVector, SRMatrix, PVector), String, String)
+ Data.SRTree.Derivative: derivative :: Floating a => Function -> a -> a
+ Data.SRTree.Derivative: deriveByParam :: Int -> Fix SRTree -> Fix SRTree
+ Data.SRTree.Derivative: deriveByVar :: Int -> Fix SRTree -> Fix SRTree
+ Data.SRTree.Derivative: doubleDerivative :: Floating a => Function -> a -> a
+ Data.SRTree.Eval: cbrt :: Floating a => a -> a
+ Data.SRTree.Eval: compMode :: Comp
+ Data.SRTree.Eval: evalFun :: Floating a => Function -> a -> a
+ Data.SRTree.Eval: evalInverse :: Floating a => Function -> a -> a
+ Data.SRTree.Eval: evalOp :: Floating a => Op -> a -> a -> a
+ Data.SRTree.Eval: evalTree :: SRMatrix -> PVector -> Fix SRTree -> SRVector
+ Data.SRTree.Eval: instance Data.Massiv.Core.Index.Internal.Index ix => GHC.Float.Floating (Data.Massiv.Core.Common.Array Data.Massiv.Array.Delayed.Pull.D ix GHC.Types.Double)
+ Data.SRTree.Eval: instance Data.Massiv.Core.Index.Internal.Index ix => GHC.Num.Num (Data.Massiv.Core.Common.Array Data.Massiv.Array.Delayed.Pull.D ix GHC.Types.Double)
+ Data.SRTree.Eval: instance Data.Massiv.Core.Index.Internal.Index ix => GHC.Real.Fractional (Data.Massiv.Core.Common.Array Data.Massiv.Array.Delayed.Pull.D ix GHC.Types.Double)
+ Data.SRTree.Eval: inverseFunc :: Function -> Function
+ Data.SRTree.Eval: invertibles :: [Function]
+ Data.SRTree.Eval: invleft :: Floating a => Op -> a -> a -> a
+ Data.SRTree.Eval: invright :: Floating a => Op -> a -> a -> a
+ Data.SRTree.Eval: replicateAs :: SRMatrix -> Double -> SRVector
+ Data.SRTree.Eval: type PVector = Array S Ix1 Double
+ Data.SRTree.Eval: type SRMatrix = Array S Ix2 Double
+ Data.SRTree.Eval: type SRVector = Array D Ix1 Double
+ Data.SRTree.Internal: AQ :: Op
+ Data.SRTree.Internal: Cube :: Function
+ Data.SRTree.Internal: LogAbs :: Function
+ Data.SRTree.Internal: PowerAbs :: Op
+ Data.SRTree.Internal: Recip :: Function
+ Data.SRTree.Internal: SqrtAbs :: Function
+ Data.SRTree.Internal: childrenOf :: SRTree a -> [a]
+ Data.SRTree.Internal: constv :: Double -> Fix SRTree
+ Data.SRTree.Internal: countUniqueTokens :: Num a => Fix SRTree -> a
+ Data.SRTree.Internal: getIntConsts :: Fix SRTree -> [Double]
+ Data.SRTree.Internal: getOperator :: SRTree a -> SRTree ()
+ Data.SRTree.Internal: instance Data.Foldable.Foldable Data.SRTree.Internal.SRTree
+ Data.SRTree.Internal: instance Data.String.IsString (Data.SRTree.Recursion.Fix Data.SRTree.Internal.SRTree)
+ Data.SRTree.Internal: instance Data.Traversable.Traversable Data.SRTree.Internal.SRTree
+ Data.SRTree.Internal: numberOfVars :: Num a => Fix SRTree -> a
+ Data.SRTree.Internal: relabelVars :: Fix SRTree -> Fix SRTree
+ Data.SRTree.Internal: replaceChildren :: [a] -> SRTree b -> SRTree a
+ Data.SRTree.Print: printExprWithVars :: [String] -> Fix SRTree -> IO ()
+ Data.SRTree.Print: showExprWithVars :: [String] -> Fix SRTree -> String
+ Text.ParseSR: BINGO :: SRAlgs
+ Text.ParseSR: EPLEX :: SRAlgs
+ Text.ParseSR: GOMEA :: SRAlgs
+ Text.ParseSR: HL :: SRAlgs
+ Text.ParseSR: LATEX :: Output
+ Text.ParseSR: MATH :: Output
+ Text.ParseSR: OPERON :: SRAlgs
+ Text.ParseSR: PYSR :: SRAlgs
+ Text.ParseSR: PYTHON :: Output
+ Text.ParseSR: SBP :: SRAlgs
+ Text.ParseSR: TIKZ :: Output
+ Text.ParseSR: TIR :: SRAlgs
+ Text.ParseSR: data Output
+ Text.ParseSR: data SRAlgs
+ Text.ParseSR: instance GHC.Enum.Bounded Text.ParseSR.Output
+ Text.ParseSR: instance GHC.Enum.Bounded Text.ParseSR.SRAlgs
+ Text.ParseSR: instance GHC.Enum.Enum Text.ParseSR.Output
+ Text.ParseSR: instance GHC.Enum.Enum Text.ParseSR.SRAlgs
+ Text.ParseSR: instance GHC.Read.Read Text.ParseSR.Output
+ Text.ParseSR: instance GHC.Read.Read Text.ParseSR.SRAlgs
+ Text.ParseSR: instance GHC.Show.Show Text.ParseSR.Output
+ Text.ParseSR: instance GHC.Show.Show Text.ParseSR.SRAlgs
+ Text.ParseSR: parseSR :: SRAlgs -> ByteString -> Bool -> ByteString -> Either String (Fix SRTree)
+ Text.ParseSR: showOutput :: Output -> Fix SRTree -> String
+ Text.ParseSR.IO: withInput :: String -> SRAlgs -> String -> Bool -> Bool -> IO [Either String (Fix SRTree)]
+ Text.ParseSR.IO: withOutput :: String -> Output -> [Either String (Fix SRTree)] -> IO ()
+ Text.ParseSR.IO: withOutputDebug :: String -> Output -> [Either String (Fix SRTree, Fix SRTree)] -> IO ()
- Data.SRTree: countConsts :: Fix SRTree -> Int
+ Data.SRTree: countConsts :: Num a => Fix SRTree -> a
- Data.SRTree: countNodes :: Fix SRTree -> Int
+ Data.SRTree: countNodes :: Num a => Fix SRTree -> a
- Data.SRTree: countOccurrences :: Int -> Fix SRTree -> Int
+ Data.SRTree: countOccurrences :: Num a => Int -> Fix SRTree -> a
- Data.SRTree: countParams :: Fix SRTree -> Int
+ Data.SRTree: countParams :: Num a => Fix SRTree -> a
- Data.SRTree: countVarNodes :: Fix SRTree -> Int
+ Data.SRTree: countVarNodes :: Num a => Fix SRTree -> a
- Data.SRTree.Internal: countConsts :: Fix SRTree -> Int
+ Data.SRTree.Internal: countConsts :: Num a => Fix SRTree -> a
- Data.SRTree.Internal: countNodes :: Fix SRTree -> Int
+ Data.SRTree.Internal: countNodes :: Num a => Fix SRTree -> a
- Data.SRTree.Internal: countOccurrences :: Int -> Fix SRTree -> Int
+ Data.SRTree.Internal: countOccurrences :: Num a => Int -> Fix SRTree -> a
- Data.SRTree.Internal: countParams :: Fix SRTree -> Int
+ Data.SRTree.Internal: countParams :: Num a => Fix SRTree -> a
- Data.SRTree.Internal: countVarNodes :: Fix SRTree -> Int
+ Data.SRTree.Internal: countVarNodes :: Num a => Fix SRTree -> a
Files
- ChangeLog.md +14/−0
- README.md +262/−13
- apps/egraphGP/Main.hs +598/−0
- apps/egraphGP/Random.hs +54/−0
- apps/eqsatrepr/Main.hs +302/−0
- apps/ieeexplore/Main.hs +101/−0
- apps/srsimplify/Main.hs +103/−0
- apps/srtools/Args.hs +178/−0
- apps/srtools/IO.hs +181/−0
- apps/srtools/Main.hs +31/−0
- apps/srtools/Report.hs +271/−0
- apps/tinygp/GP.hs +222/−0
- apps/tinygp/Initialization.hs +50/−0
- apps/tinygp/Main.hs +72/−0
- src/Algorithm/EqSat.hs +173/−0
- src/Algorithm/EqSat/Build.hs +460/−0
- src/Algorithm/EqSat/DB.hs +351/−0
- src/Algorithm/EqSat/Egraph.hs +270/−0
- src/Algorithm/EqSat/Info.hs +168/−0
- src/Algorithm/EqSat/Queries.hs +92/−0
- src/Algorithm/EqSat/Simplify.hs +214/−0
- src/Algorithm/Massiv/Utils.hs +278/−0
- src/Algorithm/SRTree/AD.hs +323/−0
- src/Algorithm/SRTree/ConfidenceIntervals.hs +447/−0
- src/Algorithm/SRTree/Likelihoods.hs +260/−0
- src/Algorithm/SRTree/ModelSelection.hs +176/−0
- src/Algorithm/SRTree/NonlinearOpt.hs +974/−0
- src/Algorithm/SRTree/Opt.hs +109/−0
- src/Data/SRTree.hs +17/−23
- src/Data/SRTree/Datasets.hs +214/−0
- src/Data/SRTree/Derivative.hs +125/−0
- src/Data/SRTree/Eval.hs +210/−0
- src/Data/SRTree/Internal.hs +229/−296
- src/Data/SRTree/Print.hs +97/−34
- src/Data/SRTree/Random.hs +20/−12
- src/Data/SRTree/Recursion.hs +12/−0
- src/Text/ParseSR.hs +302/−0
- src/Text/ParseSR/IO.hs +73/−0
- srtree.cabal +268/−6
- test/Spec.hs +36/−14
ChangeLog.md view
@@ -1,5 +1,19 @@ # Changelog for srtree +## 2.0.0.0 ++- Complete refactoring of the library+- Integration of other tools such as: srtree-opt, srtree-tools, srsimplify+- Implementation of Equality Saturation and support to e-graph +- Using Massiv for performance +- Using NLOpt as the optimization library ++## 1.1.0.0++- Reorganization of modules+- Renaming AD functions+- Inclusion of reverse mode that calculates the diagonal of and the full Hessian matrices+ ## 1.0.0.5 - Changed `base` and `mtl` versions
README.md view
@@ -1,25 +1,274 @@-# srtree: A symbolic regression expression tree structure.+# srtree: A supporting library for tree-based symbolic regression -`srtree` is a Haskell library with a data structure and supporting functions to manipulate expression trees for symbolic regression.+`srtree` is a Haskell library that implements a tree-based structure for expressions and supporting functions to be used in the context of **symbolic regression**. -The tree-like structure is defined as a fixed-point of an n-ary tree. The variables and parameters of the regression model are indexed as `Int`type and the constant values are `Double`.+The expression structure is defined as a fixed-point of a mix of unary and binary tree. This makes it easier to implement supporting functions that requires the traversal of the trees. Also, since it is a parameterized structure, we can creating partial trees to pattern math structures of interest.+This structure may contain four types of nodes: -The tree supports leaf nodes containing a variable, a free parameter, or a constant value; internal nodes that represents binary operators such as the four basic math operations, logarithm with custom base, and the power of two expressions; and unary functions specified by `Function` data type.+- `Bin Op l r` that represents a binary operator `Op` with two children.+- `Uni Function t` that represents an unary function `Function` with a single child.+- `Var Int` representing the index of a variable (i.e., x0, x1, etc.).+- `Param Int` representing the index of a adjustable parameter (i.e., theta0, theta1, etc.).+- `Const Double` representing a constant value. -The `SRTree` structure has instances for `Num, Fractional, Floating` which allows to create an expression as a valid Haskell expression such as: +The `SRTree` structure has instances for `Num, Fractional, Floating, IsString` which allows to create an expression as a valid Haskell expression such as (remember to turn on OverloadedStrings extension):+ ```haskell-x = var 0)-y = var 1-expr = x * 2 + sin(y * pi + x) :: Fix SRTree+expr = "x0" * 2 + sin("x1" * pi + "x0") :: Fix SRTree ``` -## Other features:+This library comes with support to many quality of life functions to handle this data structure. Such as: -- derivative w.r.t. a variable (`deriveByVar`) and w.r.t. a parameter (`deriveByParam`)-- evaluation (`evalTree`)-- relabel free parameters sequentially (`relabelParams`)-- gradient calculation with `forwardMode`, or optimized with `gradParams` if there is only a single occurrence of each parameter (most of the cases).+- getting the arity of a node+- getting the children of a node as a list+- count the number of nodes +- number of nodes of a specific type+- counting unique tokens +- number of variables and parameters +- relabeling the parameters from 0 to p +- converting floating point constants to parameters ++Additionally, the library provides supporting function to work with datasets, evaluating the expressions, +calculating the derivatives, printing, generating random trees, simplifying the expression, calculating overall statistics,+optimizing parameters, and model selection metrics. ++Together with this library, we provide example applications (please refer to their corresponding README files):++- [srsimplify](apps/srsimplify/README.md): a parser and simplification tool supporting the output of many popular SR algorithms.+- [srtools](apps/srtools/README.md): a tool that can be used to evaluate symbolic regression expressions and create nice reports with confidence intervals. +- [tinygp](apps/tinygp/README.md): a simple GP implementation based on tinyGP.++## Organization++The library is organized as `Data`, `Algorithm`, and `Text` modules where the `Data` modules implement functions directly tied to the data structure and the `Algorithm` modules implement algorithms related to symbolic regression, finally, the `Text` modules parse string expressions from different formats and apply simplification, when requested.++### `Data` modules++The `Data` modules is split into $5$ submodules:++- `Data.SRTree` contains the data strucuture and basic supporting functions.+- `Data.SRTree.Datasets` contains functions supporting loading datasets into Massiv.Arrays (aka numpy arrays).+- `Data.SRTree.Derivative` contains the symbolic derivatives of the functions and operators.+- `Data.SRTree.Eval` contains functions to evaluate the tree given a dataset and parameters.+- `Data.SRTree.Print` contains supporting functions for converting trees to different string representation.+- `Data.SRTree.Random` contains functions to generate random trees.++#### `Data.SRTree`++The `SRTree val` data structure is a sum type structure that can be either a variable index, a parameter index, a constant value (of type `Double`), an univariate function or a binary operator. The data type is implemented as a fixed point so all the algorithms act on `Fix SRTree`:++```haskell +t = "x0" + "t0" * sin("x1" + "t1"**2) :: Fix SRTree +```++When creating the expression in a more natural notation, the variables and parameters are `String` composed of the first letter either `x`, for variables, or `t` for parameters (as in theta), and an integer corresponding to the index of the variable or parameter. The fixed point notation, allows us to implment recursive processing of a tree without many of the common boilerplate:++```haskell+countNodes = + \case + Var _ = 1+ Const _ = 1+ Param _ = 1+ Uni _ t = 1 + t + Bin _ l r = 1 + l + r+```++The children are parameterized by the `val` type parameter. This allows us to create convenient partial structures, such as:++```haskell+-- + operator pointing to some structure+-- with index 1 and 2+Bin Add 1 2 ++-- canonical representation of + operator +Bin Add () ()+```++The main functions of this module are:++- `arity`: returns the arity of an operator.+- `getChildren`: returns the children of a `Fix SRTree` as a list +- `countNodes`: returns the number of nodes +- `countOccurrences`: counts the occurence of a given variable +- `countVars`: returns the number of unique variables appearing the expression +- `relabelParams`: relabels the parameters from the left leaves to the right +- `constsToParams`: replace `Const` nodes with `Param` nodes.++#### `Data.SRTree.Datasets` module ++This module exports only the `loadDataset` function which takes a filename and +returns the training and test sets together with the column labels.+The filename must follow the format:++`filename.ext:start_row:end_row:target:features`++where each ':' field is optional. The fields are:++- **start_row:end_row** is the range of the training rows (default 0:nrows-1).+ every other row not included in this range will be used as validation+- **target** is either the name of the PVector (if the datafile has headers) or the index+ of the target variable+- **features** is a comma separated list of SRMatrix names or indices to be used as+ input variables of the regression model.++Example of valid names: `dataset.csv`, `mydata.tsv`, `dataset.csv:20:100`, `dataset.tsv:20:100:price:m2,rooms,neighborhood`, `dataset.csv:::5:0,1,2`.++#### `Data.SRTree.Derivative` module ++Calculates symbolic derivatives of the expression w.r.t. the variables or the parameters.+The main functions of this module are:++- `deriveBy`: returns the symbolic derivative w.r.t. a certain variable or a certain parameter.+- `deriveByVar`: shortcut to `deriveBy` to derive by a variable.+- `deriveByParam`: shortcut to `deriveBy` to derive by a parameter.++#### `Data.SRTree.Eval` module ++Evaluates an expression given a dataset.+The main functions of this module are:++- `evalTree`: given a data matrix and a vector of parameters, evaluates the expression tree.+- `evalInverse`: evaluates the inverse of a function. +- `invright`: evaluates the right inverse of an operator.+- `invleft`: evaluates the left inverse of an operator.++#### `Data.SRTree.Print` module ++Support functions to convert an expression tree into a `String`.+The main functions of this module are: ++- `showExpr` and `printExpr`: converts/print the expression into math notation .+- `showPython` and `printPython`: converts/print to a numpy notation.+- `showLatex` and `printLatex`: converts/print to a LaTeX notation.+- `showTikz` and `printTikz`: converts/print to a TikZ notation.++#### `Data.SRTree.Random` module ++Auxiliary functions to create random trees. +The main functions of this module are:++- `randomTree`: creates a random tree with a certain number of nodes.+- `randomTreeBalanced`: creates a (almost) balanced random tree with a certain number of nodes.++### `Text` modules ++The `Text` module is split into $2$ modules:++- `Text.ParseSR`: contains the main parsers for different SR algorithms output.+- `Text.ParseSR.IO`: auxiliary functions to handle files containing many expressions.++#### `Text.ParseSR` module ++The only important function of this module is `parseSR` that parses an string expression from a given algorithm to a certain output. It also converts variable names to x0, x1,...++#### `Text.ParseSR.IO` module ++The two main functions of this module are: ++- `withInput`: that reads the stdin or a text file and parse all expressions +- `withOutput`: that writes the parsed expression into stdout or a file with one of the choices of output format. ++These functions handle any errors with an `Either` type and they can be safely pipelined together. Any invalid expression will be printed as "invalid expression <error message>".++### `Algorithm` modules ++The `Algorithm` modules are split into $5$ submodules:++- `Algorithm.SRTree.AD` contains automatic differentiation functions.+- `Algorithm.SRTree.ConfidenceIntervals` contains functions to calculate the confidence intervals of parameters and predictions of a symbolic expression using Laplace approximation or profile likelihood.+- `Algorithm.SRTree.Likelihood` contains functions support different likelihood functions and their derivatives (gradient and hessian).+- `Algorithm.SRTree.ModelSelection` implements different model selection criteria such as AIC, BIC, MDL.+- `Algorithm.SRTree.Opt` implements functions to optimize the parameters of an expression supporting different likelihood functions.++#### `Algorithm.SRTree.AD` module ++The main functions of this module are:++- `forwardMode`: returns the prediction errors vector multiplied by the Jacobian matrix using forward mode AD.+- `forwardModeUnique`: same as above, but assuming each parameter index appear only once in the tree. +- `reverseModeUnique`: same as above, but using reverse mode +- `forwardModeUniqueJac`: same as `forwardModeUnique` but returns the Jacobian (does not mutiply by the error).++#### `Algorithm.SRTree.Likelihood` module ++The main functions of this module are: ++- `sse, mse, rmse`: calculates the sum-of-square, mean squared, root of mean squared errors.+- `nll`: returns the negative log-likelihood given a distribution and the associated error (`Nothing` if unknown)+- `gradNLL`: returns the gradient of the negative log-likelihood. +- `gradNLLNonUnique`: same as above but assumes non-unique parameters +- `hessianNLL`: returns the hessian of the neg log-likelihood.++#### `Algorithm.SRTree.Opt` module ++The main functions of this module are:++- `minimizeNLL`: minimizes the negative log-likelihood of a distribution.+- `minimizeNLLNonUnique`: same as above but assumes repeated occurrences of parameters.+- `minimizeNLLWithFixedParam`: minimizes the neg log-likelihood but fixing the value of a single parameter.+- `minimizeGaussian`, `minimizePoisson`, `minimizeBinomial`: shortcut to minimize these three distributions.++#### `Algorithm.SRTree.ModelSelection` module ++The main functions of this module are:++- `bic`: Bayesian Information Criteria +- `aic`: Akaike Information Criteria +- `mdl`: Minimum Description Length as described in Bartlett, Deaglan J., Harry Desmond, and Pedro G. Ferreira. "Exhaustive symbolic regression." IEEE Transactions on Evolutionary Computation (2023)+- `mdlLattice`: as described in Bartlett, Deaglan, Harry Desmond, and Pedro Ferreira. "Priors for symbolic regression." Proceedings of the Companion Conference on Genetic and Evolutionary Computation. 2023.+- `mdlFreq` : MDL weighted by the frequency of occurrence of functions ++#### `Algorithm.SRTree.ConfidenceIntervals` module ++The main functions of this module are: ++- `paramCI`: calculates the parameters confidence intervals. +- `predictionCI`: calculates the predictions confidence intervals ++### `EqSat` modules ++The `EqSat` modules are split into $4$ submodules:++- `Algorithm.EqSat.Simplify` contains function supporting algebraic simplification with equality saturation.+- `Algorithm.EqSat` contains the main equality saturation function.+- `Algorithm.EqSat.EGraph` contains the e-graph data structure and supporting functions.+- `Algorithm.EqSat.EqSatDB` contains supporting functions to pattern matching and insert equivalent expressions into an e-graph. ++#### `Algorithm.EqSat` module++The main functions of this module are: ++- `eqSat` : runs equality saturation over a single expression. +- `getBest` : returns the best expression given the cost function used to generate the e-graph +- `recalculateBest` : recalculates the cost of each e-class using a new cost function +- `runEqSat` : runs equality saturation inside `EGraphST` monad. Use this if you want to return the e-graph. ++#### `Algorithm.EqSat.EGraph` module++The main functions of this module are: ++- `fromTree` : creates an e-graph from an expression tree.+- `fromTrees` : creates an e-graph from multiple expressions +- `fromTreeWith` : inserts a new expression into the e-graph +- `findRootClasses` : returns the roots of the e-graph, if any .+- `getExpressionFrom` : returns a single expression from a given e-class always picking the first e-node as the path +- `getAllExpressionsFrom` : returns all expressions from the given e-class +- `getRndExpressionFrom` : returns a random expression from this e-class +++#### `Algorithm.EqSat.EqSatDB` module++The main functions of this module are: ++- TODO: create auxiliary functions to apply substution rules inside an EGraphST monad . ++#### `Algorithm.EqSat.Simplify` module++The main functions of this module are: ++- `simplifyEqSatDefault` : simplifies an expression using the default parameters +- `simplifyEqSat` : simplifies with custom parameters ## TODO:
+ apps/egraphGP/Main.hs view
@@ -0,0 +1,598 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE BangPatterns #-}++module Main where ++import Algorithm.EqSat.Egraph+import Algorithm.EqSat.Simplify+import Algorithm.EqSat.Build+import Algorithm.EqSat.Queries+import Algorithm.EqSat.Info+import Algorithm.EqSat.DB+import Algorithm.SRTree.Likelihoods+import Algorithm.SRTree.Opt+import Control.Lens (element, makeLenses, over, (&), (+~), (-~), (.~), (^.))+import Control.Monad (foldM, forM_, forM, when, unless, filterM, (>=>), replicateM, replicateM_)+import Control.Monad.State.Strict+import qualified Data.IntMap.Strict as IM+import Data.Massiv.Array as MA hiding (forM_, forM)+import Data.Maybe (fromJust, isNothing, isJust)+import Data.SRTree+import Data.SRTree.Datasets+import Data.SRTree.Eval+import Data.SRTree.Random (randomTree)+import Data.SRTree.Print+import Options.Applicative as Opt hiding (Const)+import Random+import System.Random+import qualified Data.HashSet as Set+import Data.List ( sort, maximumBy, intercalate, sortOn )+import Data.IntSet (IntSet)+import qualified Data.IntSet as IntSet+import qualified Data.Sequence as FingerTree+import Data.Function ( on )+import qualified Data.Foldable as Foldable+import qualified Data.IntMap as IntMap+import List.Shuffle ( shuffle )++import Debug.Trace+import Algorithm.EqSat (runEqSat)++-- Insert random expression +-- Evaluate random subtree +-- Insert new random parent eNode ++type RndEGraph a = EGraphST (StateT StdGen IO) a++io = lift . lift+{-# INLINE io #-}+rnd = lift+{-# INLINE rnd #-}++myCost :: SRTree Int -> Int+myCost (Var _) = 1+myCost (Const _) = 1+myCost (Param _) = 1+myCost (Bin op l r) = 2 + l + r+myCost (Uni _ t) = 3 + t++data Alg = OnlyRandom | BestFirst deriving (Show, Read, Eq)++-- experiment 1 80/30+fitnessFun :: SRMatrix -> PVector -> SRMatrix -> PVector -> Fix SRTree -> RndEGraph (Double, PVector)+fitnessFun x y x_val y_val _tree = do+ let tree = relabelParams _tree+ nParams = countParams tree+ thetaOrig <- rnd $ randomVec nParams -- = MA.replicate Seq nParams 1.0+ let (theta, fit) = minimizeNLL Gaussian Nothing 50 x y tree thetaOrig+ tr = negate . mse x y tree $ if nParams == 0 then thetaOrig else theta+ val = negate . mse x_val y_val tree $ if nParams == 0 then thetaOrig else theta+ -- val = r2 x y tree $ if nParams == 0 then thetaOrig else theta+ pure $ if isNaN val || isNaN tr+ then (-1/0, theta) -- infinity+ else (min tr val, theta)+{-# INLINE fitnessFun #-}++fitnessFunRep :: SRMatrix -> PVector -> SRMatrix -> PVector -> Fix SRTree -> RndEGraph (Double, PVector)+fitnessFunRep x y x_val y_val _tree = do+ fits <- replicateM 1 (fitnessFun x y x_val y_val _tree)+ pure (maximumBy (compare `on` fst) fits)+{-# INLINE fitnessFunRep #-}++-- helper query functions+fitnessIs p = p . _fitness . _info+{-# INLINE fitnessIs #-}++getFitness :: EClassId -> RndEGraph (Maybe Double)+getFitness c = gets (_fitness . _info . (IM.! c) . _eClass)+{-# INLINE getFitness #-}+getSize :: EClassId -> RndEGraph Int+getSize c = gets (_size . _info . (IM.! c) . _eClass)+{-# INLINE getSize #-}+getSizeOf :: (Int -> Bool) -> [EClassId] -> RndEGraph [EClassId]+getSizeOf p = filterM (getSize >=> (pure . p))+{-# INLINE getSizeOf #-}++(&&&) p1 p2 x = p1 x && p2 x+{-# INLINE (&&&) #-}++isValidFitness = fitnessIs (isJust &&& (not . isNaN . fromJust) &&& (not . isInfinite . fromJust))+{-# INLINE isValidFitness #-}++evaluated = fitnessIs isJust+{-# INLINE evaluated #-}+unevaluated' = fitnessIs isNothing+{-# INLINE unevaluated' #-}++isSizeOf p = p . _size . _info+{-# INLINE isSizeOf #-}++funDoesNotExistWith node = Prelude.any (not . (`sameFunAs` node) . snd) . _parents+ where sameFunAs (Uni f _) (Uni g _) = f == g+ sameFunAs _ _ = False+{-# INLINE funDoesNotExistWith #-}++opDoesNotExistWith :: (SRTree ()) -> EClassId -> EClass -> Bool+opDoesNotExistWith node ecId = Prelude.any (not . (`sameOpAs` node) . snd) . _parents+ where sameOpAs (Bin op1 l _) (Bin op2 _ _) = op1 == op2 && ecId == l+ sameOpAs _ _ = False+{-# INLINE opDoesNotExistWith #-}++rewriteBasic2 :: [Rule]+rewriteBasic2 =+ [+ "x" * "y" :=> "y" * "x"+ , "x" + "y" :=> "y" + "x"+ , ("x" ** "y") * ("x" ** "z") :=> "x" ** ("y" + "z") -- :| isPositive "x"+ , ("x" + "y") + "z" :=> "x" + ("y" + "z")+ , ("x" * "y") * "z" :=> "x" * ("y" * "z")+ , ("x" * "y") + ("x" * "z") :=> "x" * ("y" + "z")+ , ("w" * "x") + ("z" * "x") :=> ("w" + "z") * "x" -- :| isConstPt "w" :| isConstPt "z"+ ]++egraphSearch alg x y x_val y_val x_te y_te terms nEvals maxSize = do+ ec <- insertRndExpr maxSize+ updateIfNothing ec+ insertTerms+ evaluateUnevaluated+ runEqSat myCost rewriteBasic2 1++ while (numberOfEvalClasses nEvals) 1 $+ \radius ->+ do+ --nEvs <- gets (FingerTree.size . _fitRangeDB . _eDB)+ nCls <- gets (IM.size . _eClass)+ nUnev <- gets (IntSet.size . _unevaluated . _eDB)+ let nEvs = nCls - nUnev+ --io . print $ (nCls, nEvs)+ bestF <- getBestFitness++ (ecN, b) <- case alg of+ OnlyRandom -> do let ratio = fromIntegral nEvs / fromIntegral nCls+ b <- rnd (tossBiased ratio)+ ec <- if b && ratio > 0.99 then insertRndExpr maxSize >>= canonical else evaluateRndUnevaluated >>= canonical+ pure (ec, False)+ BestFirst -> do+ ecsPareto <- getParetoEcsUpTo radius+ ecsBest <- getBestEcs (isSizeOf (<=maxSize)) radius++ ecPareto <- combineFrom ecsPareto+ curFitPareto <- getFitness ecPareto++ if isNothing curFitPareto+ then pure (ecPareto, False)+ else do ecBest <- combineFrom ecsBest+ curFitBest <- getFitness ecBest+ if isNothing curFitBest+ then pure (ecBest, False)+ else do ee <- evalRndSubTree+ case ee of+ Nothing -> do ec <- insertRndExpr maxSize >>= canonical+ pure (ec, True)+ Just c -> pure (c, False)++ upd <- updateIfNothing ecN+ when (upd)+ do runEqSat myCost rewriteBasic2 1+ cleanDB+ pure ()+ if b then pure (min 20 $ radius+1) else pure (max 1 $ radius-1)+ eclasses <- gets (IntMap.toList . _eClass)+ -- forM_ eclasses $ \(_, v) -> (io.print) (Set.size (_eNodes v), Set.size (_parents v))+ paretoFront+ --ft <- gets (_fitRangeDB . _eDB)+ --io . print $ Foldable.toList ft++ where+ numberOfEvalClasses :: Monad m => Int -> EGraphST m Bool+ numberOfEvalClasses nEvs =+ (subtract <$> gets (IntSet.size . _unevaluated . _eDB) <*> gets (IM.size . _eClass))+ >>= \n -> pure (n<nEvs)++ updateIfNothing ec = do+ mf <- getFitness ec+ case mf of+ Nothing -> do+ t <- getBest ec+ (f, p) <- fitnessFunRep x y x_val y_val t+ insertFitness ec f p+ pure True+ Just _ -> pure False++ getBestFitness = do+ bec <- (gets (snd . getGreatest . _fitRangeDB . _eDB) >>= canonical)+ gets (_fitness . _info . (IM.! bec) . _eClass)++ evalRndSubTree :: RndEGraph (Maybe EClassId)+ evalRndSubTree = do ecIds <- gets (IntSet.toList . _unevaluated . _eDB)+ if not (null ecIds)+ then do rndId <- rnd $ randomFrom ecIds+ Just <$> canonical rndId+ else pure Nothing+++ combineFrom ecs = do+ nt <- rnd rndNonTerm+ p1 <- rnd (randomFrom ecs)+ p2 <- rnd (randomFrom ecs)+ l1 <- rnd (randomFrom [1..maxSize-2])+ l2 <- rnd (randomFrom [1..(maxSize - l1 - 1)])+ e1 <- randomChildFrom p1 l1+ ml <- gets (_size . _info . (IM.! e1) . _eClass)+ e2 <- randomChildFrom p2 l2+ case nt of+ Uni Id () -> canonical e1+ Uni f () -> add myCost (Uni f e1) >>= canonical+ Bin op () () -> do b <- rnd toss+ if b+ then add myCost (Bin op e1 e2) >>= canonical+ else add myCost (Bin op e2 e1) >>= canonical++ getParetoEcsUpTo n = concat <$> (forM [1..maxSize] $ \i -> getBestEcsOfSize i n)++ getBestEcsOfSize i n = do+ ecs <- getTopECLassWithSize i n+ Prelude.mapM canonical (Prelude.take n ecs)++ getBestEcs p n = do+ ecs <- getTopECLassThat n p+ --fits <- Prelude.mapM getFitness ecs+ --let sorted = sort $ Prelude.zip (Prelude.map (fmap negate) fits) ecs+ Prelude.mapM canonical (Prelude.take n ecs)++ randomChildFrom ec maxL = do+ p <- rnd toss -- whether to go deeper or return this level+ l <- gets (_size . _info . (IM.! ec) . _eClass )++ if p || l >= maxL+ then do enodes <- gets (_eNodes . (IM.! ec) . _eClass)+ enode <- gets (_best . _info . (IM.! ec) . _eClass) -- we should return the best otherwise we may build larger+ case enode of+ Uni _ eci -> randomChildFrom eci maxL+ Bin _ ecl ecr -> do coin <- rnd toss+ if coin+ then randomChildFrom ecl maxL+ else randomChildFrom ecr maxL+ _ -> pure ec+ else pure ec++ nonTerms = [ Bin Add () (), Bin Sub () (), Bin Mul () (), Bin Div () ()+ , Bin PowerAbs () (), Uni Recip (), Uni LogAbs (), Uni Exp (), Uni Sin (), Uni SqrtAbs ()]+ rndTerm = Random.randomFrom terms+ rndNonTerm = Random.randomFrom $ (Uni Id ()) : nonTerms+ rndNonTerm2 = Random.randomFrom nonTerms++ insertTerms =+ forM terms $ \t -> do fromTree myCost t >>= canonical++ insertRndExpr :: Int -> RndEGraph EClassId+ insertRndExpr maxSize =+ do grow <- rnd toss+ t <- rnd $ Random.randomTree 2 8 maxSize rndTerm rndNonTerm2 grow+ fromTree myCost t >>= canonical++ insertBestExpr :: RndEGraph EClassId+ insertBestExpr = do --let t = "t0" / (recip ("t1" - "x0") + powabs "t2" "x0")+ let t = ((("t0" + (powabs "t0" "x0")) / "t0") * "x0")+ ecId <- fromTree myCost t >>= canonical+ (f, p) <- fitnessFunRep x y x_val y_val t+ insertFitness ecId f p+ io . putStrLn $ "Best fit global: " <> show f+ pure ecId+ where powabs l r = Fix (Bin PowerAbs l r)++ getBestEclassThat p =+ do ecIds <- getTopECLassThat 1 p -- isValidFitness+ --bestFit <- foldM (\acc -> getFitness >=> (pure . max acc . fromJust)) ((-1.0)/0.0) ecIds+ --ecIds' <- getEClassesThat (fitnessIs (== Just bestFit))+ Prelude.mapM canonical $ Prelude.take 1 ecIds++ getBestExprWithSize n =+ do ec <- getTopECLassWithSize n 1+ if (not (null ec))+ then do+ bestFit <- getFitness $ head ec+ bestP <- gets (_theta . _info . (IM.! (head ec)) . _eClass)+ (:[]) . (,bestP) . (,bestFit) <$> getBest (head ec)+ else pure []++ getBestExprThat p =+ do ec <- getBestEclassThat p+ if (not (null ec))+ then do+ bestFit <- getFitness $ head ec+ (:[]) . (,bestFit) <$> getBest (head ec)+ else pure []++ printAll = do+ ecs <- gets (IM.keys . _eClass)+ forM_ ecs $ \ec ->+ do t <- getBest ec+ f <- gets (_fitness . _info . (IM.! ec) . _eClass)+ io . putStrLn $ showExpr t <> " " <> show f++ paretoFront = go 1 (-1.0/0.0)+ where+ go n f+ | n > maxSize = pure ()+ | otherwise = do+ ecList <- getBestExprWithSize n+ if (not (null ecList))+ then do let ((best, mf), mtheta) = head ecList+ best' = relabelParams best+ x_tot <- MA.computeAs MA.S <$> (MA.concatOuterM $ Prelude.map MA.toLoadArray [x, x_val])+ y_tot <- MA.computeAs MA.S <$> (MA.concatOuterM $ Prelude.map MA.toLoadArray [y, y_val])++ --(fit_tr, theta) <- fitnessFunRep x_tot y_tot x_tot y_tot best'+ let fit = fromJust mf+ fit_tr = fit+ theta = fromJust mtheta+ fit_te = mse x_te y_te best' theta+ str_th = intercalate ";" $ Prelude.map show $ MA.toList theta++ when (fit > f) do+ io . putStrLn $ showExpr best <> "," <> str_th <> "," <> show (negate fit) <> "," <> show (negate fit_tr) <> "," <> show fit_te+ go (n+1) (max fit f)+ else go (n+1) f++ evaluateUnevaluated = do+ ec <- gets (IntSet.toList . _unevaluated . _eDB)+ forM_ ec $ \c -> do+ t <- getBest c+ (f, p) <- fitnessFun x y x_val y_val t+ insertFitness c f p++ evaluateRndUnevaluated = do+ ec <- gets (IntSet.toList . _unevaluated . _eDB)+ c <- rnd . randomFrom $ ec + t <- getBest c+ (f, p) <- fitnessFun x y x_val y_val t+ insertFitness c f p+ pure c++while p arg prog = do b <- p+ when b do arg' <- prog arg+ while p arg' prog++ {-+egraphGP :: SRMatrix -> PVector -> [Fix SRTree] -> Int -> RndEGraph (Fix SRTree, Double)+egraphGP x y terms nEvals = do+ replicateM_ 200 insertRndExpr+ getBestExpr+ runEqSat myCost rewrites 50+ evaluateUnevaluated+ paretoFront+ getBestExpr+ where+ paretoFront = do + forM_ [1..10] $ \i ->+ do (best, fit) <- getBestExprThat (evaluated &&& isSizeOf (==i))+ io . putStrLn $ showExpr best <> " " <> show fit ++ evaluateUnevaluated = do + ec <- getEClassesThat unevaluated+ forM_ ec $ \c -> do + t <- getBest c + f <- fitnessFun x y t+ updateFitness f c +++++++++++++++++------------------- GARBAGE CODE -------------------+ go i = do n <- getEClassesThat isValidFitness+ unless (length n >= nEvals)+ do gpStep+ when (i `mod` 1000 == 0) (getBestExpr >>= (io . print . snd))+ when (i `mod` 1000000 == 0) $ do+ n <- gets (IM.size . _eClass)+ --io $ putStrLn ("before: " <> show n)+ applyMergeOnlyDftl myCost+ n1 <- gets (IM.size . _eClass)+ when (n1 < n) $ io $ print (n,n1)+ --io $ putStrLn ("after: " <> show n1)+ go (i+1)++ rndTerm = Random.randomFrom terms+ rndNonTerm = Random.randomFrom [Bin Add () (), Bin Sub () (), Bin Mul () (), Bin Div () ()+ , Bin PowerAbs () (), Uni Recip ()]++ getBestExpr :: RndEGraph (Fix SRTree, Double) + getBestExpr = do ecIds <- getEClassesThat evaluated -- isValidFitness+ nc <- gets (IM.size . _eClass)+ io . putStrLn $ "Evaluated expressions: " <> show (length ecIds) <> " / " <> show nc+ bestFit <- foldM (\acc -> getFitness >=> (pure . max acc . fromJust)) ((-1.0)/0.0) ecIds+ ecIds' <- getEClassesThat (fitnessIs (== Just bestFit))+ (,bestFit) <$> getBest (head ecIds')++ getBestExprThat p = + do ecIds <- getEClassesThat p -- isValidFitness+ nc <- gets (IM.size . _eClass)+ bestFit <- foldM (\acc -> getFitness >=> (pure . max acc . fromJust)) ((-1.0)/0.0) ecIds+ ecIds' <- getEClassesThat (fitnessIs (== Just bestFit))+ (,bestFit) <$> getBest (head ecIds')++ insertRndExpr :: RndEGraph () + insertRndExpr = do grow <- rnd toss+ t <- rnd $ Random.randomTree 2 6 10 rndTerm rndNonTerm grow+ f <- fitnessFun x y t+ ecId <- fromTree myCost t >>= canonical+ -- io $ print ('i', showExpr t, f)+ updateFitness f ecId++ evalRndSubTree :: RndEGraph ()+ evalRndSubTree = do ecIds <- getEClassesThat unevaluated+ unless (null ecIds) do+ rndId <- rnd $ randomFrom ecIds+ rndId' <- canonical rndId + t <- getBest rndId'+ f <- fitnessFun x y t+ -- io $ print ('e', showExpr t, f)+ updateFitness f rndId'++ tournament :: Int -> [EClassId] -> RndEGraph EClassId+ tournament n ecIds = do + (c0:cs) <- replicateM n (rnd (randomFrom ecIds))+ f0 <- gets (_fitness . _info . (IM.! c0) . _eClass)+ snd <$> foldM (\(facc, acc) c -> gets (_fitness . _info . (IM.! c) . _eClass)+ >>= \f -> if f > facc+ then pure (f, c)+ else pure (facc, acc)) (f0, c0) cs++ insertRndParent :: RndEGraph ()+ insertRndParent = do nt <- rnd rndNonTerm+ meId <- case nt of+ Uni f _ -> do ecIds <- getEClassesThat (isSizeOf (<10) &&& isValidFitness &&& funDoesNotExistWith nt)+ if null ecIds+ then pure Nothing + else do rndId <- tournament 5 ecIds+ sz <- getSize rndId+ if sz < 10 + then do node <- canonize (Uni f rndId)+ Just <$> add myCost node+ else pure Nothing+ Bin op _ _ -> do ecIds <- getEClassesThat (isSizeOf (<9) &&& isValidFitness)+ if null ecIds+ then pure Nothing+ else do rndIdLeft <- rnd $ randomFrom ecIds+ sz1 <- getSize rndIdLeft+ ecIds' <- getEClassesThat (isSizeOf (< (10 - sz1)) &&& isValidFitness &&& opDoesNotExistWith nt rndIdLeft)+ if null ecIds'+ then pure Nothing+ else do rndIdRight <- rnd $ randomFrom ecIds'+ rndIdRight <- tournament 5 ecIds'+ sz2 <- getSize rndIdRight+ if sz1 + sz2 < 10+ then Just <$> (canonize (Bin op rndIdLeft rndIdRight)+ >>= add myCost)+ else pure Nothing+ when (isJust meId) do+ let eId = fromJust meId+ eId' <- canonical eId+ curFit <- gets (_fitness . _info . (IM.! eId') . _eClass)+ when (isNothing curFit) do+ t <- getBest eId'+ f <- fitnessFun x y t+ updateFitness f eId'+ -- io $ print ('p', showExpr t, f)++ gpStep :: RndEGraph () + gpStep = do choice <- rnd $ randomFrom [2,2,3,3,3]+ if | choice == 1 -> insertRndExpr+ | choice == 2 -> insertRndParent+ | otherwise -> evalRndSubTree+ rebuild myCost+ -}+data Args = Args+ { dataset :: String,+ gens :: Int,+ _alg :: Alg,+ _maxSize :: Int,+ _split :: Int+ }+ deriving (Show)++-- parser of command line arguments+opt :: Parser Args+opt = Args+ <$> strOption+ ( long "dataset"+ <> short 'd'+ <> metavar "INPUT-FILE"+ <> help "CSV dataset." )+ <*> option auto+ ( long "generations"+ <> short 'g'+ <> metavar "GENS"+ <> showDefault+ <> value 100+ <> help "Number of generations." )+ <*> option auto+ ( long "algorithm"+ <> short 'a'+ <> metavar "ALG"+ <> help "Algorithm." )+ <*> option auto+ ( long "maxSize"+ <> short 's'+ <> help "max-size." )+ <*> option auto+ ( long "split"+ <> short 'k'+ <> help "k-split ratio training-test")++chunksOf :: Int -> [e] -> [[e]]+chunksOf i ls = Prelude.map (Prelude.take i) (build (splitter ls))+ where+ splitter :: [e] -> ([e] -> a -> a) -> a -> a+ splitter [] _ n = n+ splitter l c n = l `c` splitter (Prelude.drop i l) c n+ build :: ((a -> [a] -> [a]) -> [a] -> [a]) -> [a]+ build g = g (:) []++splitData :: SRMatrix -> PVector -> Int -> State StdGen (SRMatrix, SRMatrix, PVector, PVector)+splitData x y k = do if k == 1+ then pure (x, x, y, y)+ else do+ ixs' <- (state . shuffle) [0 .. sz-1]+ let ixs = chunksOf k ixs' -- $ sortOn (\ix -> y MA.! ix) [0 .. sz-1]+ --ixs <- forM sortedIxs $ \is -> state (shuffle is)+ let xl = MA.toLists x :: [MA.ListItem MA.Ix2 Double]+ x_tr = MA.fromLists' comp_x [xl !! ix | ixs_i <- ixs, ix <- Prelude.tail ixs_i]+ x_te = MA.fromLists' comp_x [xl !! ix | ixs_i <- ixs, let ix = Prelude.head ixs_i]+ y_tr = MA.fromList comp_y [y MA.! ix | ixs_i <- ixs, ix <- Prelude.tail ixs_i]+ y_te = MA.fromList comp_y [y MA.! ix | ixs_i <- ixs, let ix = Prelude.head ixs_i]++ {-+ ixs <- state (shuffle [0 .. sz-1])+ let ixs_tr = sort $ Prelude.take qty_tr ixs+ ixs_te = sort $ Prelude.drop qty_tr ixs++ x_tr = MA.fromLists' comp_x [xl !! ix | ix <- ixs_tr]+ x_te = MA.fromLists' comp_x [xl !! ix | ix <- ixs_te]+ y_tr = MA.fromList comp_y [y MA.! ix | ix <- ixs_tr]+ y_te = MA.fromList comp_y [y MA.! ix | ix <- ixs_te]+ -}+ pure (x_tr, x_te, y_tr, y_te)+ where+ (MA.Sz sz) = MA.size y+ --qty_tr = round (thr * fromIntegral sz)+ --qty_te = sz - qty_tr+ comp_x = MA.getComp x+ comp_y = MA.getComp y++main :: IO ()+main = do+ --args <- pure (Args "nikuradse_2.csv" 100) -- execParser opts+ args <- execParser opts+ g <- getStdGen+ ((x, y, _, _), _, _) <- loadDataset (dataset args) True+ let ((x', x_te, y', y_te),g') = runState (splitData x y $ _split args) g+ ((x_tr, x_val, y_tr, y_val),g'') = runState (splitData x' y' 2) g'+ let (Sz2 _ nFeats) = MA.size x+ terms = [var ix | ix <- [0 .. nFeats-1]] <> [param 0] -- [param ix | ix <- [0 .. 5]]+ alg = evalStateT (egraphSearch (_alg args) x_tr y_tr x_val y_val x_te y_te terms (gens args) (_maxSize args)) emptyGraph+ --(bestExpr, fit) <- evalStateT alg g+ --printExpr bestExpr+ --print fit+ evalStateT alg g''++ where+ opts = Opt.info (opt <**> helper)+ ( fullDesc <> progDesc "Very simple example of GP using SRTree."+ <> header "tinyGP - a very simple example of GP using SRTRee." )
+ apps/egraphGP/Random.hs view
@@ -0,0 +1,54 @@+module Random where ++import System.Random +import Control.Monad.State.Strict+import Control.Monad+import Data.SRTree +import Data.SRTree.Eval+import Data.Massiv.Array as MA++type Rng a = StateT StdGen IO a ++toss :: Rng Bool+toss = state random+{-# INLINE toss #-}++tossBiased :: Double -> Rng Bool+tossBiased p = do r <- state random + pure (r < p)++randomVal :: Rng Double+randomVal = state random++randomRange :: (Ord val, Random val) => (val, val) -> Rng val+randomRange rng = state (randomR rng)+{-# INLINE randomRange #-}++randomFrom :: [a] -> Rng a+randomFrom funs = do n <- randomRange (0, length funs - 1)+ pure $ funs !! n+{-# INLINE randomFrom #-}++randomVec :: Int -> Rng PVector+randomVec n = MA.fromList compMode <$> replicateM n (randomRange (-3.0, 3.0))++randomTree :: Int -> Int -> Int -> Rng (Fix SRTree) -> Rng (SRTree ()) -> Bool -> Rng (Fix SRTree)+randomTree minDepth maxDepth maxSize genTerm genNonTerm grow + | noSpaceLeft = genTerm+ | needNonTerm = genRecursion + | otherwise = do r <- toss+ if r + then genTerm + else genRecursion+ where + noSpaceLeft = maxDepth <= 1 || maxSize <= 2+ needNonTerm = (minDepth >= 0 || (maxDepth > 2 && not grow)) -- && maxSize > 2++ genRecursion = do + node <- genNonTerm+ case node of + Uni f _ -> Fix . Uni f <$> randomTree (minDepth - 1) (maxDepth - 1) (maxSize - 1) genTerm genNonTerm grow + Bin op _ _ -> do l <- randomTree (minDepth - 1) (maxDepth - 1) (maxSize - 2) genTerm genNonTerm grow+ r <- randomTree (minDepth - 1) (maxDepth - 1) (maxSize - 1 - countNodes l) genTerm genNonTerm grow + pure . Fix $ Bin op l r+{-# INLINE randomTree #-}
+ apps/eqsatrepr/Main.hs view
@@ -0,0 +1,302 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Data.SRTree +import Algorithm.EqSat.Egraph+import Data.SRTree.Print +import qualified Data.Map as Map+import qualified Data.IntMap as IM+import Control.Monad.State.Strict+import System.Random+import Data.SRTree.Recursion ( cata )+import Control.Monad+import Control.Monad.Reader+import qualified Data.SRTree.Random as RT+import Data.List ( nub )+import Algorithm.EqSat.DB+import Algorithm.EqSat.Info+import Algorithm.EqSat.Build+import Algorithm.EqSat.Queries+import Algorithm.EqSat++isConstPt :: Pattern -> Map.Map ClassOrVar ClassOrVar -> EGraph -> Bool+isConstPt (VarPat c) subst eg =+ let cid = getInt $ subst Map.! (Right $ fromEnum c)+ in case (_consts . _info) (_eClass eg IM.! cid) of+ ConstVal x -> True+ _ -> False+isConstPt _ _ _ = False++notZero (VarPat c) subst eg =+ let cid = getInt $ subst Map.! (Right $ fromEnum c)+ in case (_consts . _info) (_eClass eg IM.! cid) of+ ConstVal x -> x /= 0+ _ -> True+notZero _ _ _ = True++rewriteBasic =+ [+ "x" * "x" :=> "x" ** 2+ , "x" * "y" :=> "y" * "x"+ , "x" + "y" :=> "y" + "x"+ , ("x" ** "y") * "x" :=> "x" ** ("y" + 1) :| isConstPt "y"+ -- , ("x" * "y") / "x" :=> "y"+ , ("x" ** "y") * ("x" ** "z") :=> "x" ** ("y" + "z")+ , ("x" + "y") + "z" :=> "x" + ("y" + "z")+ , ("x" + "y") - "z" :=> "x" + ("y" - "z")+ , ("x" * "y") * "z" :=> "x" * ("y" * "z")+ , ("x" * "y") + ("x" * "z") :=> "x" * ("y" + "z")+ , "x" - ("y" + "z") :=> ("x" - "y") - "z"+ , "x" - ("y" - "z") :=> ("x" - "y") + "z"+ , ("x" * "y") / "z" :=> ("x" / "z") * "y"+ , (("w" * "x") / ("z" * "y") :=> ("w" / "z") * ("x" / "y") :| isConstPt "w") :| isConstPt "z"+ , ((("x" * "y") + ("z" * "w")) :=> "x" * ("y" + ("z" / "x") * "w") :| isConstPt "x") :| isConstPt "z"+ , ((("x" * "y") - ("z" * "w")) :=> "x" * ("y" - ("z" / "x") * "w") :| isConstPt "x") :| isConstPt "z"+ , ((("x" * "y") * ("z" * "w")) :=> ("x" * "z") * ("y" * "w") :| isConstPt "x") :| isConstPt "z"+ ]++rewritesFun =+ [+ log (sqrt "x") :=> 0.5 * log "x"+ , log (exp "x") :=> "x"+ , exp (log "x") :=> "x"+ , "x" ** (1/2) :=> sqrt "x"+ , log ("x" * "y") :=> log "x" + log "y"+ , log ("x" / "y") :=> log "x" - log "y"+ , log ("x" ** "y") :=> "y" * log "x"+ , sqrt ("y" * "x") :=> sqrt "y" * sqrt "x"+ , sqrt ("y" / "x") :=> sqrt "y" / sqrt "x"+ , abs ("x" * "y") :=> abs "x" * abs "y"+ , sqrt ("z" * ("x" - "y")) :=> sqrt (negate "z") * sqrt ("y" - "x")+ , sqrt ("z" * ("x" + "y")) :=> sqrt "z" * sqrt ("x" + "y")+ ]++-- Rules that reduces redundant parameters+constReduction =+ [+ 0 + "x" :=> "x"+ , "x" - 0 :=> "x"+ , 1 * "x" :=> "x"+ , 0 * "x" :=> 0+ , 0 / "x" :=> 0 :| notZero "x"+ , "x" - "x" :=> 0+ , "x" / "x" :=> 1 :| notZero "x"+ , "x" ** 1 :=> "x"+ , 0 ** "x" :=> 0+ , 1 ** "x" :=> 1+ , "x" * (1 / "x") :=> 1+ , 0 - "x" :=> negate "x"+ , "x" + negate "y" :=> "x" - "y"+ , negate ("x" * "y") :=> (negate "x") * "y" :| isConstPt "x"+ ]+++x0 = var 0+x1 = var 1+x2 = var 2+x3 = var 3+x4 = var 4+x5 = var 5+x6 = var 6+x7 = var 7+x8 = var 8++trees :: [Fix SRTree]+trees = [ (4.059e-3 + (0.988153 * (((1.923901 * x1) * ((-1.228652 * x0) * (-0.278891 * x2))) * ((((((-0.35119 * x5) + (-0.354523 * x3)) - (-0.369148 * x6)) + ((0.342012 * x4) + (2.054e-2 * x2))) - ((0.349297 * x7) - (0.336081 * x8)))))))+ , ((14.316036 * (((((0.975231 * x4)) * (1.259663 * x3)) * (0.314221 * x0)) * (0.178249 * x2))))+ , (1.2 * (3.4 * x1 * 4.2 * x0) / ((3.2 * x2) * ((1.1 * x3) * (3.5 * x4))))+ , ((1.002563 * (((0.428416 * x1) * (2.554566 * x0)) / (((2.53743 * x2) * (2.327917 * x3)) * (2.320736 * x3)))))+ , (2.82238 + (3.092415 * (sin(log(abs(0.0))) * ((-0.162842 * x2) - (0.116404 * x1)))))+ , log(0.0) * ((1.2 * x2) - (0.116404 * x1))+ , ((x0 - x0) * x0)+ , (1 + 1) - 1+ , (x0 + x0) - x0+ , (x0/x0 + 1) - 1+ , (x0 * x0) / x0+ , sin(log(0.0))+ , -1 * exp(log(abs(-1.3 * (x1 - 1.2 * x2))))+ , -1 * exp(log(abs((1.3 * x1 + 1.56 * x2))))+ , -1 * exp(log(abs((-1.3 * x1 + 1.56 * x2))))+ , -1 * exp(log(abs(((0.256 * x3) + (-0.2561 * x2)))))+ , log(abs(-1.199026) * abs((x2 + (1.191617 * x3))))+ , log(abs((1.199026 * x2) + (-1.191617 * x3)))+ , 1 * x0+ , x0 * 1+ , x0 + x1+ , x1 + x0+ , x0 + sin(x1)+ , x0 * (1 + x1)+ , (1 + x1) * x0+ , x0 + x0 * x1+ , (x0 + x1) + 2+ , x0 + (x1 + 2)+ , x0 + (2 + x1)+ , log(abs(0)) + x0+ , abs(((1.3 * x1) + (-1.56 * x2))) * (-1.0)+ ]+++myCost :: SRTree Int -> Int+myCost (Var _) = 1+myCost (Const _) = 1+myCost (Param _) = 1+myCost (Bin op l r) = 2 + l + r+myCost (Uni _ t) = 3 + t++rewrites = rewriteBasic <> constReduction <> rewritesFun++testEqSat :: Fix SRTree -> IO ()+testEqSat t = do+ let e = eqSat t rewrites myCost 30 `evalState` emptyGraph+ putStr $ (showExpr t) <> " == " <> (showExpr e) <> "\n"++testEqSats :: IO ()+testEqSats = mapM_ testEqSat trees++++initialPop :: HyperParams -> Rng [Fix SRTree]+initialPop hyperparams = do+ let depths = [3 .. _maxDepth hyperparams]+ pop <- forM depths $ \md ->+ do let m = _popSize hyperparams `div` (_maxDepth hyperparams - 3 + 1)+ g = take m $ cycle [True, False]+ mapM (randomTree hyperparams{ _maxDepth = md}) g+ pure (concat pop)+{-# INLINE initialPop #-}++data Method = Grow | Full++type Rng a = StateT StdGen IO a+type GenUni = Fix SRTree -> Fix SRTree+type GenBin = Fix SRTree -> Fix SRTree -> Fix SRTree++toss :: Rng Bool+toss = state random+{-# INLINE toss #-}++randomRange :: (Ord val, Random val) => (val, val) -> Rng val+randomRange rng = state (randomR rng)+{-# INLINE randomRange #-}++randomFrom :: [a] -> Rng a+randomFrom funs = do n <- randomRange (0, length funs - 1)+ pure $ funs !! n+{-# INLINE randomFrom #-}++countNodes' :: Fix SRTree -> Int+countNodes' = cata alg+ where+ alg (Var _) = 1+ alg (Param _) = 1+ alg (Const _) = 0+ alg (Bin _ l r) = 1 + l + r+ alg (Uni Abs t) = t+ alg (Uni _ t) = 1 + t+{-# INLINE countNodes' #-}+++randomTree :: HyperParams -> Bool -> Rng (Fix SRTree)+randomTree hp grow+ | depth <= 1 || size <= 2 = randomFrom term+ | (min_depth >= 0 || (depth > 2 && not grow)) && size > 2 = genNonTerm+ | otherwise = genTermOrNon+ where+ min_depth = _minDepth hp+ depth = _maxDepth hp+ size = _maxSize hp+ term = _term hp+ nonterm = _nonterm hp++ genNonTerm =+ do et <- randomFrom nonterm+ case et of+ Left uniT -> uniT <$> randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1} grow+ Right binT -> do l <- randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1} grow+ r <- randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1 - countNodes' l} grow+ pure (binT l r)+ genTermOrNon = do r <- toss+ if r+ then randomFrom term+ else genNonTerm+{-# INLINE randomTree #-}++data HyperParams =+ HP { _minDepth :: Int+ , _maxDepth :: Int+ , _maxSize :: Int+ , _popSize :: Int+ , _tournSize :: Int+ , _pc :: Double+ , _pm :: Double+ , _term :: [Fix SRTree]+ , _nonterm :: [Either GenUni GenBin]+ }+++countSubTrees = do ecs <- gets (IM.keys . _eClass) + subs <- mapM (\ec -> getAllExpressionsFrom ec >>= pure . length) ecs + pure $ sum subs +countRootTrees rs = do subs <- mapM (\ec -> getAllExpressionsFrom ec >>= pure . length) rs+ pure $ sum subs++terms = [var 0, var 1, var 2, param 0, param 1, param 2, param 3]+nonterms = [Right (+), Right (-), Right (*), Right (/), Right (\l r -> abs l ** r), Left (1/)]++calcRedundancy :: Int -> IO ()+calcRedundancy nPop = do+ let hp = HP 2 4 10 nPop 2 1.0 0.25 terms nonterms+ p = RT.P [0, 1, 2, 3, 4, 5] (0, 3) (1, 3) [Log]+ g <- getStdGen+ pop <- (`evalStateT` g) <$> replicateM nPop $ runReaderT (RT.randomTree 10) p+ let nSubsSingle = sum $ map (\p -> (fromTrees myCost [p] >> countSubTrees) `evalState` emptyGraph) pop + myEqPop = do rs <- fromTrees myCost pop+ let rsN = nub rs + cnt <- countSubTrees+ pure (cnt, rsN)+ (nSubs, rsN) = myEqPop `evalState` emptyGraph + putStr "Ratio of subtrees: "+ putStrLn $ show nSubsSingle <> "/" <> show nSubs <> " = " <> show (fromIntegral nSubsSingle / fromIntegral nSubs)+ let nSubsR = sum $ map (\p -> (fromTree myCost p >>= \r -> countRootTrees [r]) `evalState` emptyGraph) pop+ nSubsSingleR = (fromTrees myCost pop >>= countRootTrees) `evalState` emptyGraph+ putStr "Ratio of rooted trees: "+ putStrLn $ show nSubsSingleR <> "/" <> show nSubsR <> " = " <> show (fromIntegral nSubsSingleR / fromIntegral nSubsR)++main :: IO ()+main = do + let t1 = var 0 + 12.0+ t2 = 3.2 * var 0+ t3 = 3.2 * var 0 / (var 0 + 12.0)+ t4 = var 0 + sin (var 0)+ t5 = 1.5 + exp 5.2+ egraphRun :: EGraphST IO ()+ egraphRun = do v <- fromTrees myCost [t3,t1,t2,t4]+ roots <- findRootClasses+ ecId <- gets ((Map.! (Var 0)) . _eNodeToEClass)+ calculateHeights + h <- gets (map _height . IM.elems . _eClass)+ v <- gets (map (_consts . _info) . IM.elems . _eClass)+ c <- gets (map (_cost . _info) . IM.elems . _eClass)+ parents <- gets (_parents . (IM.! ecId) . _eClass)+ exprs <- mapM getExpressionFrom roots+ exprs' <- gets (IM.keys . _eClass) >>= mapM getExpressionFrom ++ lift $ do putStr "Parents of x0: "+ print parents + putStrLn "\nexpressions from root: "+ mapM_ (putStrLn . showExpr) exprs+ putStrLn "\nexpressions from each e-class: "+ mapM_ (putStrLn . showExpr) exprs'+ putStrLn "heights: "+ mapM_ print h -- (print . _height) (IM.elems $ _eClass eg')+ putStrLn "values: "+ mapM_ print v -- (print . _consts . _info) (IM.elems $ _eClass eg')+ putStrLn "costs: "+ mapM_ print c -- (print . _cost . _info) (IM.elems $ _eClass eg')+ nPop = 10000+ hp = HP 3 7 100 nPop 2 1.0 0.25 terms nonterms+ p = RT.P [0] (-3, 3) (-3, 3) []+ egraphRun `evalStateT` emptyGraph+ g <- getStdGen+ pop <- evalStateT (initialPop hp) g+ mapM_ (\nP -> putStr "pop " >> print nP >> calcRedundancy nP >> putStrLn "") [100, 200, 500, 1000, 5000, 10000, 20000, 100000]
+ apps/ieeexplore/Main.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}++module Main where ++import Algorithm.EqSat.Egraph+import Algorithm.EqSat.Simplify+import Algorithm.SRTree.Likelihoods+import Algorithm.SRTree.Opt+import Control.Lens (element, makeLenses, over, (&), (+~), (-~), (.~), (^.))+import Control.Monad (foldM, forM_, forM, when, unless, filterM, (>=>), replicateM, replicateM_)+import Control.Monad.State+import qualified Data.IntMap as IM+import Data.Massiv.Array as MA hiding (forM_, forM)+import Data.Maybe (fromJust, isNothing, isJust)+import Data.SRTree+import Data.SRTree.Datasets+import Data.SRTree.Eval+import Data.SRTree.Random (randomTree)+import Data.SRTree.Print+import Options.Applicative as Opt hiding (Const)+import System.Random+import qualified Data.Set as Set+import Data.List ( sort )++import Debug.Trace+import Algorithm.EqSat (runEqSat)+++type RangedTree a = a ++data QueryDB = QDB { _mseDB :: RangedTree Double + , _maeDB :: RangedTree Double + , _r2DB :: RangedTree Double + , _mdlDB :: RangedTree Double + , _bicDB :: RangedTree Double + , _aicDB :: RangedTree Double + , _lenDB :: RangedTree Int+ , _ptDB :: RangedTree Int -- DB + } ++data ExtraInfo = ExtraInfo { _thetaMap :: IM.IntMap PVector+ , _qdb :: QueryDB+ }++type InfoEGraph a = EGraphST (StateT ExtraInfo IO) a++--io = lift . lift+--{-# INLINE io #-}+--ext = lift+--{-# INLINE ext #-}++myCost :: SRTree Int -> Int+myCost (Var _) = 1+myCost (Const _) = 1+myCost (Param _) = 1+myCost (Bin op l r) = 2 + l + r+myCost (Uni _ t) = 3 + t++++data Args = Args+ { dataset :: String,+ gens :: Int,+ _maxSize :: Int+ }+ deriving (Show)++-- parser of command line arguments+opt :: Parser Args+opt = Args+ <$> strOption+ ( long "dataset"+ <> short 'd'+ <> metavar "INPUT-FILE"+ <> help "CSV dataset." )+ <*> option auto+ ( long "generations"+ <> short 'g'+ <> metavar "GENS"+ <> showDefault+ <> value 100+ <> help "Number of generations." )+ <*> option auto+ ( long "maxSize"+ <> short 's'+ <> help "max-size." )++main :: IO ()+main = do+ --args <- pure (Args "nikuradse_2.csv" 100) -- execParser opts+ args <- execParser opts+ ((x, y, _, _), _, _) <- loadDataset (dataset args) True+ print "opa"++ where+ opts = Opt.info (opt <**> helper)+ ( fullDesc <> progDesc "Very simple example of GP using SRTree."+ <> header "tinyGP - a very simple example of GP using SRTRee." )
+ apps/srsimplify/Main.hs view
@@ -0,0 +1,103 @@+module Main (main) where++import Options.Applicative+import Text.ParseSR.IO ( withInput, withOutput )+import Text.ParseSR ( SRAlgs (..), Output (..) )+import System.Random ( getStdGen, mkStdGen )+import Text.Read ( readMaybe )+import Data.Char ( toLower, toUpper )+import Data.List ( intercalate )++-- Data type to store command line arguments+data Args = Args+ { from :: SRAlgs+ , to :: Output+ , infile :: String+ , outfile :: String+ , varnames :: String+ } deriving Show++-- parser of command line arguments+opt :: Parser Args+opt = Args+ <$> option sralgsReader+ ( long "from"+ <> short 'f'+ <> metavar ("[" <> intercalate "|" sralgsHelp <> "]")+ <> help "Input expression format" )+ <*> option srtoReader -- TODO+ ( long "to"+ <> short 't'+ <> metavar ("[" <> intercalate "|" srtoHelp <> "]")+ <> help "Output expression format" )+ <*> strOption+ ( long "input"+ <> short 'i'+ <> metavar "INPUT-FILE"+ <> showDefault+ <> value ""+ <> help "Input file containing expressions. \+ \ Empty string gets expression from stdin." )+ <*> strOption+ ( long "output"+ <> short 'o'+ <> metavar "OUTPUT-FILE"+ <> showDefault+ <> value ""+ <> help "Output file to store the stats in CSV format. \+ \ Empty string prints expressions to stdout." )+ <*> strOption+ ( long "varnames"+ <> short 'v'+ <> metavar "VARNAMES"+ <> showDefault+ <> value ""+ <> help "Comma separated string of variable names. \+ \ Empty string defaults to the algorithm default (x0, x1,..)." )++-- helper functions to show the possible options+mkDescription :: Show a => [a] -> [String]+mkDescription = map (envelope '\'' . map toLower . show)+ where+ envelope :: a -> [a] -> [a]+ envelope c xs = c : xs <> [c]+{-# INLINE mkDescription #-}++sralgsHelp :: [String]+sralgsHelp = mkDescription [toEnum 0 :: SRAlgs ..]+{-# INLINE sralgsHelp #-}++srtoHelp :: [String]+srtoHelp = mkDescription [toEnum 0 :: Output ..]+{-# INLINE srtoHelp #-}++-- helper functions to parse the options+mkReader :: Read a => String -> (a -> b) -> String -> ReadM b+mkReader err val sr = eitherReader+ $ case readMaybe sr of+ Nothing -> pure (Left err)+ Just x -> pure (Right (val x))++sralgsReader :: ReadM SRAlgs+sralgsReader =+ str >>= (mkReader errMsg id . map toUpper)+ where+ errMsg = "unknown algorithm. Available options are " <> intercalate "," sralgsHelp++srtoReader :: ReadM Output+srtoReader =+ str >>= (mkReader errMsg id . map toUpper)+ where+ errMsg = "unknown algorithm. Available options are " <> intercalate "," srtoHelp++main :: IO ()+main = do+ args <- execParser opts+ withInput (infile args) (from args) (varnames args) False True+ >>= withOutput (outfile args) (to args)+ where+ opts = info (opt <**> helper)+ ( fullDesc <> progDesc "Simplify an expression\+ \ using equality saturation."+ <> header "srsimplify - a CLI tool to simplify\+ \ symbolic regression expressions with equality saturation." )
+ apps/srtools/Args.hs view
@@ -0,0 +1,178 @@+module Args where++import Data.Char ( toLower, toUpper )+import Data.List ( intercalate )+import Algorithm.SRTree.Likelihoods ( Distribution (..) )+import Algorithm.SRTree.ConfidenceIntervals ( PType (..) )+import Options.Applicative+import Text.ParseSR ( SRAlgs (..) )+import Text.Read ( readMaybe )++-- Data type to store command line arguments+data Args = Args+ { from :: SRAlgs+ , infile :: String+ , outfile :: String+ , dataset :: String+ , test :: String+ , niter :: Int+ , hasHeader :: Bool+ , simpl :: Bool+ , dist :: Distribution+ , msErr :: Maybe Double+ , restart :: Bool+ , rseed :: Int+ , toScreen :: Bool+ , useProfile :: Bool+ , alpha :: Double+ , ptype :: PType+ } deriving Show++-- parser of command line arguments+opt :: Parser Args+opt = Args+ <$> option sralgsReader+ ( long "from"+ <> short 'f'+ <> metavar ("[" <> intercalate "|" sralgsHelp <> "]")+ <> help "Input expression format" )+ <*> strOption+ ( long "input"+ <> short 'i'+ <> metavar "INPUT-FILE"+ <> showDefault+ <> value ""+ <> help "Input file containing expressions. \+ \ Empty string gets expression from stdin." )+ <*> strOption+ ( long "output"+ <> short 'o'+ <> metavar "OUTPUT-FILE"+ <> showDefault+ <> value ""+ <> help "Output file to store the stats in CSV format. \+ \ Empty string prints expressions to stdout." )+ <*> strOption+ ( long "dataset"+ <> short 'd'+ <> metavar "DATASET-FILENAME"+ <> help "Filename of the dataset used for optimizing the parameters. \+ \ Empty string omits stats that make use of the training data. \+ \ It will auto-detect and handle gzipped file based on gz extension. \+ \ It will also auto-detect the delimiter.\n\+ \ The filename can include extra information: \+ \ filename.csv:start:end:target:vars where start and end \+ \ corresponds to the range of rows that should be used for fitting,\+ \ target is the column index (or name) of the target variable and cols\+ \ is a comma separated list of column indeces or names of the variables\+ \ in the same order as used by the symbolic model." )+ <*> strOption+ ( long "test"+ <> metavar "TEST"+ <> showDefault+ <> value ""+ <> help "Filename of the test dataset.\+ \ Empty string omits stats that make use of the training data.\+ \ It can have additional information as in the training set,\+ \ but the validation range will be discarded." )+ <*> option auto+ ( long "niter"+ <> metavar "NITER"+ <> showDefault+ <> value 10+ <> help "Number of iterations for the optimization algorithm.")+ <*> switch+ ( long "hasheader"+ <> help "Uses the first row of the csv file as header.")+ <*> switch+ ( long "simplify"+ <> help "Apply basic simplification." )+ <*> option distRead+ ( long "distribution"+ <> metavar ("[" <> intercalate "|" distHelp <> "]")+ <> showDefault+ <> value Gaussian+ <> help "Minimize negative log-likelihood following one of\+ \ the avaliable distributions.\+ \ The default is Gaussian."+ )+ <*> option s2Reader+ ( long "sErr"+ <> metavar "Serr"+ <> showDefault+ <> value Nothing+ <> help "Estimated standard error of the data.\+ \ If not passed, it uses the model MSE.")+ <*> switch+ ( long "restart"+ <> help "If set, it samples the initial values of\+ \ the parameters using a Gaussian distribution N(0, 1),\+ \ otherwise it uses the original values of the expression." )+ <*> option auto+ ( long "seed"+ <> metavar "SEED"+ <> showDefault+ <> value (-1)+ <> help "Random seed to initialize the parameters values.\+ \ Used only if restart is enabled.")+ <*> switch+ ( long "report"+ <> help "If set, reports the analysis in a user-friendly\+ \ format instead of csv. It will also include\+ \ confidence interval for the parameters and predictions" )+ <*> switch+ ( long "profile"+ <> help "If set, it will use profile likelihood to calculate the CIs." )+ <*> option auto+ ( long "alpha"+ <> metavar "ALPHA"+ <> showDefault+ <> value 0.05+ <> help "Significance level for confidence intervals.")+ <*> option auto+ ( long "ptype"+ <> metavar "[Bates | ODE | Constrained]"+ <> showDefault+ <> value Constrained+ <> help "Profile Likelihood method. Default: Constrained. NOTE: Constrained method only calculates the endpoint."+ )++-- helper functions to show the possible options+mkDescription :: Show a => [a] -> [String]+mkDescription = map (envelope '\'' . map toLower . show) + where+ envelope :: a -> [a] -> [a]+ envelope c xs = c : xs <> [c]+{-# INLINE mkDescription #-}++sralgsHelp :: [String]+sralgsHelp = mkDescription [toEnum 0 :: SRAlgs ..]+{-# INLINE sralgsHelp #-}++distHelp :: [String]+distHelp = mkDescription [toEnum 0 :: Distribution ..]+{-# INLINE distHelp #-}++-- helper functions to parse the options+mkReader :: Read a => String -> (a -> b) -> String -> ReadM b+mkReader err val sr = eitherReader + $ case readMaybe sr of+ Nothing -> pure (Left err)+ Just x -> pure (Right (val x))++sralgsReader :: ReadM SRAlgs+sralgsReader =+ str >>= (mkReader errMsg id . map toUpper)+ where+ errMsg = "unknown algorithm. Available options are " <> intercalate "," sralgsHelp++s2Reader :: ReadM (Maybe Double)+s2Reader =+ str >>= \s -> mkReader ("wrong format " <> s) Just s++distRead :: ReadM Distribution+distRead =+ str >>= \s -> mkReader ("unsupported distribution " <> s) id (capitalize s)+ where+ capitalize "" = ""+ capitalize (c:cs) = toUpper c : map toLower cs
+ apps/srtools/IO.hs view
@@ -0,0 +1,181 @@+{-# language BlockArguments #-}+{-# language LambdaCase #-}+module IO where++import System.IO ( hClose, hPutStrLn, openFile, stderr, stdout, IOMode(WriteMode), Handle )+import qualified Data.Massiv.Array as A+import Data.List ( intercalate )+import Control.Monad ( unless, forM_ )+import System.Random ( StdGen )++import Data.SRTree ( SRTree (..), Fix (..), var, floatConstsToParam, relabelVars )+import Algorithm.SRTree.Opt ( estimateSErr )+import Algorithm.SRTree.Likelihoods ( Distribution (..) )+import Algorithm.SRTree.ConfidenceIntervals ( printCI, BasicStats(_stdErr, _corr), CI )+import qualified Data.SRTree.Print as P+import Data.SRTree.Eval ( compMode )++import Args ( Args(outfile, alpha,msErr,dist,niter) )+import Report+import Data.SRTree.Recursion ( cata )++import Debug.Trace ( trace, traceShow )++-- Header of CSV file+csvHeader :: String+csvHeader = intercalate "," (basicFields <> optFields <> modelFields)+{-# inline csvHeader #-}++-- Open file if filename is not empty+openWriteWithDefault :: Handle -> String -> IO Handle+openWriteWithDefault dflt "" = pure dflt+openWriteWithDefault _ fname = openFile fname WriteMode+{-# INLINE openWriteWithDefault #-}++-- procecss a single tree and return all the available stats+processTree :: Args -- command line arguments+ -> StdGen -- random number generator+ -> Datasets -- datasets+ -> Fix SRTree -- expression in tree format+ -> Int -- index of the parsed expression + -> (BasicInfo, SSE, SSE, Info, (BasicStats, [CI], [CI], [CI], [CI]))+processTree args seed dset t ix = (basic, sseOrig, sseOpt, info, cis)+ where+ (tree, theta0) = floatConstsToParam t+ mSErr' = case dist args of+ Gaussian -> estimateSErr Gaussian (msErr args) (_xTr dset) (_yTr dset) (A.fromList compMode theta0) tree (niter args)+ _ -> Nothing+ args' = args{ msErr = mSErr' }+ basic = getBasicStats args' seed dset tree theta0 ix+ treeVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> _expr basic+ (_, Nothing) -> _expr basic+ (Just xV, Just yV) -> _expr $ getBasicStats args' seed dset{_xTr = xV, _yTr = yV} tree theta0 ix+ sseOrig = getSSE dset tree+ sseOpt = getSSE dset (_expr basic)+ info = getInfo args' dset (_expr basic) treeVal+ cis = getCI args' dset basic (alpha args')++-- print the results to a csv format (except CI)+printResults :: Args -> StdGen -> Datasets -> [String] -> [Either String (Fix SRTree)] -> IO ()+printResults args seed dset varnames exprs = do+ hStat <- openWriteWithDefault stdout (outfile args)+ hPutStrLn hStat csvHeader + forM_ (zip [0..] exprs) + \(ix, tree) -> + case tree of+ Left err -> hPutStrLn stderr ("invalid expression: " <> err)+ Right t -> let treeData = processTree args seed dset t ix+ in hPutStrLn hStat (toCsv treeData varnames)+ unless (null (outfile args)) (hClose hStat)++-- change the stats into a string+toCsv :: (BasicInfo, SSE, SSE, Info, e) -> [String] -> String+toCsv (basic, sseOrig, sseOpt, info, _) varnames = intercalate "," (sBasic <> sSSEOrig <> sSSEOpt <> sInfo)+ where+ sBasic = [ show (_index basic), show (_fname basic), P.showExprWithVars varnames (_expr basic)+ , show (_nNodes basic), show (_nParams basic)+ , intercalate ";" (map show (_params basic))+ ]+ sSSEOrig = map (showF sseOrig) [_sseTr, _sseVal, _sseTe]+ sSSEOpt = map (showF sseOpt) [_sseTr, _sseVal, _sseTe]+ sInfo = map (showF info) [_bic, _bicVal, _aic, _aicVal, _evidence, _evidenceVal, _mdl, _mdlFreq, _mdlLatt, _mdlVal, _mdlFreqVal, _mdlLattVal, _nllTr, _nllVal, _nllTe, _cc, _cp]+ <> [intercalate ";" (map show (_fisher info))]+ showF p f = show (f p)++-- get trees of transformed features+getTransformedFeatures :: Fix SRTree -> (Fix SRTree, [Fix SRTree])+getTransformedFeatures = cata $+ \case+ Var ix -> (Fix $ Var ix, [])+ Param ix -> (Fix $ Param ix, [])+ Const x -> (Fix $ Const x, [])+ Uni f (t, vars) -> (Fix $ Uni f t, vars)+ Bin op (l, vs1) (r, vs2) -> case (hasNoParam l, hasNoParam r) of+ (False, True) -> let vs = vs1 <> vs2+ in (Fix $ Bin op l (var $ length vs), vs <> [r])+ (True, False) -> let vs = vs1 <> vs2+ in (Fix $ Bin op (var $ length vs) r, vs <> [l])+ ( _, _) -> (Fix $ Bin op l r, vs1 <> vs2) -- vs1 == vs2 == []++ where+ hasNoParam = cata $+ \case+ Var ix -> True+ Param ix -> False+ Const x -> if floor x == ceiling x then True else False+ Uni f t -> t+ Bin op l r -> l && r++allAreVars :: [Fix SRTree] -> Bool+allAreVars = all isOnlyVar+ where+ isOnlyVar (Fix (Var _)) = True+ isOnlyVar _ = False++-- print the information on screen (including CIs)+printResultsScreen :: Args -> StdGen -> Datasets -> [String] -> String -> [Either String (Fix SRTree)] -> IO ()+printResultsScreen args seed dset varnames targt exprs = do+ forM_ (zip [0..] exprs) + \(ix, tree) -> + case tree of+ Left err -> do putStrLn ("invalid expression: " <> err)+ Right t -> let treeData = processTree args seed dset t ix+ in printToScreen ix treeData+ where+ decim :: Int -> Double -> Double+ decim n x = (fromIntegral . (round :: Double -> Integer)) (x * 10^n) / 10^n+ sdecim n = show . decim n+ nplaces = 4+++ printToScreen ix (basic, _, sseOpt, info, (sts, cis, pis_tr, pis_val, pis_te)) =+ do let (transformedT, newvars) = getTransformedFeatures (_expr basic)+ varnames' = ['z': show ix | ix <- [0 .. length newvars - 1]]+ putStrLn $ "=================== EXPR " <> show ix <> " =================="+ putStr $ targt <> " ~ f(" <> intercalate ", " varnames <> ") = "+ putStrLn $ P.showExprWithVars varnames (_expr basic)++ unless (allAreVars newvars) do+ putStrLn "\nExpression and transformed features: "+ putStr $ targt <> " ~ f(" <> intercalate ", " varnames' <> ") = "+ putStrLn $ P.showExprWithVars varnames' (relabelVars transformedT)+ forM_ (zip varnames' newvars) \(vn, tv) -> do+ putStrLn $ vn <> " = " <> P.showExprWithVars varnames tv++ putStrLn "\n---------General stats:---------\n"+ putStrLn $ "Number of nodes: " <> show (_nNodes basic)+ putStrLn $ "Number of params: " <> show (_nParams basic)+ putStrLn $ "theta = " <> show (_params basic)++ putStrLn "\n----------Performance:--------\n"+ putStrLn $ "SSE (train.): " <> sdecim nplaces (_sseTr sseOpt)+ putStrLn $ "SSE (val.): " <> sdecim nplaces (_sseVal sseOpt)+ putStrLn $ "SSE (test): " <> sdecim nplaces (_sseTe sseOpt)+ putStrLn $ "NegLogLiklihood (train.): " <> sdecim nplaces (_nllTr info)+ putStrLn $ "NegLogLiklihood (val.): " <> sdecim nplaces (_nllVal info)+ putStrLn $ "NegLogLiklihood (test): " <> sdecim nplaces (_nllTe info)++ putStrLn "\n------Selection criteria:-----\n"+ putStrLn $ "BIC: " <> sdecim nplaces (_bic info)+ putStrLn $ "AIC: " <> sdecim nplaces (_aic info)+ putStrLn $ "MDL: " <> sdecim nplaces (_mdl info)+ putStrLn $ "MDL (freq.): " <> sdecim nplaces (_mdlFreq info)+ putStrLn $ "Functional complexity: " <> sdecim nplaces (_cc info)+ putStrLn $ "Parameter complexity: " <> sdecim nplaces (_cp info)++ putStrLn "\n---------Uncertainties:----------\n"+ putStrLn "Correlation of parameters: " + putStrLn $ show $ A.map (decim 2) (_corr sts)+ putStrLn $ "Std. Err.: " <> show (A.map (decim nplaces) (_stdErr sts))+ putStrLn "\nConfidence intervals:\n\nlower <= val <= upper"+ mapM_ (printCI nplaces) cis+ putStrLn "\nConfidence intervals (predictions training):\n\nlower <= val <= upper"+ mapM_ (printCI nplaces) pis_tr+ unless (null pis_val) do+ putStrLn "\nConfidence intervals (predictions validation):\n\nlower <= val <= upper"+ mapM_ (printCI nplaces) pis_val+ unless (null pis_te) do+ putStrLn "\nConfidence intervals (predictions test):\n\nlower <= val <= upper"+ mapM_ (printCI nplaces) pis_te+ putStrLn "============================================================="
+ apps/srtools/Main.hs view
@@ -0,0 +1,31 @@+module Main (main) where++import Data.ByteString.Char8 ( pack, unpack, split )+import Options.Applicative+import System.Random ( getStdGen, mkStdGen )+import Text.ParseSR.IO ( withInput )++import Args+import IO+import Report++main :: IO ()+main = do+ args <- execParser opts+ g <- getStdGen+ (dset, varnames, tgname) <- getDataset args+ let seed = if rseed args < 0 + then g + else mkStdGen (rseed args)+ varnames' = map unpack $ split ',' $ pack varnames+ withInput (infile args) (from args) varnames False (simpl args)+ >>= if toScreen args+ then printResultsScreen args seed dset varnames' tgname -- full report on screne+ else printResults args seed dset varnames' -- csv file+ where + opts = info (opt <**> helper)+ ( fullDesc <> progDesc "Optimize the parameters of\+ \ Symbolic Regression expressions."+ <> header "srtools - a CLI tool to (re)optimize the numeric\+ \ parameters of symbolic regression expressions"+ )
+ apps/srtools/Report.hs view
@@ -0,0 +1,271 @@+module Report where++import qualified Data.Vector.Storable as VS+import qualified Data.Massiv.Array as A+import Data.Massiv.Array ( Sz(..) )+import Data.Maybe ( fromMaybe )+import Statistics.Distribution.FDistribution ( fDistribution )+import Statistics.Distribution.ChiSquared ( chiSquared )+import Statistics.Distribution ( quantile )+import System.Random ( StdGen, split )+import Data.Random.Normal ( normals )++import Data.SRTree ( SRTree, Fix (..), floatConstsToParam, paramsToConst, countNodes )+import Data.SRTree.Eval+import Algorithm.SRTree.AD ( reverseModeUnique, forwardModeUniqueJac )+import Algorithm.SRTree.Likelihoods+import Algorithm.SRTree.ModelSelection ( aic, bic, evidence, logFunctional, logParameters, mdl, mdlFreq, mdlLatt )+import Algorithm.SRTree.ConfidenceIntervals+import Algorithm.SRTree.Opt (minimizeNLLWithFixedParam, minimizeNLL, minimizeNLLNonUnique)+import Data.SRTree.Datasets ( loadDataset )+import Data.SRTree.Print ( showExpr )+import Debug.Trace ( trace, traceShow )++import Args++-- store the datasets split into training, validation and test+data Datasets = DS { _xTr :: SRMatrix+ , _yTr :: PVector+ , _xVal :: Maybe SRMatrix+ , _yVal :: Maybe PVector+ , _xTe :: Maybe SRMatrix+ , _yTe :: Maybe PVector+ }++-- basic fields name+basicFields :: [String]+basicFields = [ "Index"+ , "Filename"+ , "Expression"+ , "Number_of_nodes"+ , "Number_of_parameters"+ , "Parameters"+ ]++-- basic information about the tree+data BasicInfo = Basic { _index :: Int+ , _fname :: String+ , _expr :: Fix SRTree+ , _nNodes :: Int+ , _nParams :: Int+ , _params :: [Double]+ }++-- optimization fields+optFields :: [String]+optFields = [ "SSE_train_orig"+ , "SSE_val_orig"+ , "SSE_test_orig"+ , "SSE_train_opt"+ , "SSE_val_opt"+ , "SSE_test_opt"+ ]++-- optimization information+data SSE = SSE { _sseTr :: Double+ , _sseVal :: Double+ , _sseTe :: Double+ }++-- model selection fields+modelFields :: [String]+modelFields = [ "BIC"+ , "BIC_val"+ , "AIC"+ , "AIC_val"+ , "Evidence"+ , "EvidenceVal"+ , "MDL"+ , "MDL_Freq"+ , "MDL_Lattice"+ , "MDL_val"+ , "MDL_Freq_val"+ , "MDL_Lattice_val"+ , "NegLogLikelihood_train"+ , "NegLogLikelihood_val"+ , "NegLogLikelihood_test"+ , "LogFunctional"+ , "LogParameters"+ , "Fisher"+ ]++-- model selection information+data Info = Info { _bic :: Double+ , _bicVal :: Double+ , _aic :: Double+ , _aicVal :: Double+ , _evidence :: Double+ , _evidenceVal :: Double+ , _mdl :: Double+ , _mdlFreq :: Double+ , _mdlLatt :: Double+ , _mdlVal :: Double+ , _mdlFreqVal :: Double+ , _mdlLattVal :: Double+ , _nllTr :: Double+ , _nllVal :: Double+ , _nllTe :: Double+ , _cc :: Double+ , _cp :: Double+ , _fisher :: [Double]+ }++-- load the datasets+getDataset :: Args -> IO (Datasets, String, String)+getDataset args = do+ ((xTr, yTr, xVal, yVal), varnames, tgname) <- loadDataset (dataset args) (hasHeader args)+ let (A.Sz m) = A.size yVal+ let (mXVal, mYVal) = if m == 0+ then (Nothing, Nothing)+ else (Just xVal, Just yVal)+ (mXTe, mYTe) <- if null (test args)+ then pure (Nothing, Nothing)+ else do ((xTe, yTe, _, _), _, _) <- loadDataset (test args) (hasHeader args)+ pure (Just xTe, Just yTe)+ pure (DS xTr yTr mXVal mYVal mXTe mYTe, varnames, tgname)++getBasicStats :: Args -> StdGen -> Datasets -> Fix SRTree -> [Double] -> Int -> BasicInfo+getBasicStats args seed dset tree theta0 ix+ | anyNaN = getBasicStats args (snd $ split seed) dset tree theta0 ix+ | otherwise = Basic ix (infile args) tOpt nNodes nParams params+ where+ -- (tree', theta0) = floatConstsToParam tree+ thetas = if restart args+ then A.fromList compMode $ take nParams (normals seed)+ else A.fromList compMode theta0+ t = fst $ minimizeNLL (dist args) (msErr args) (niter args) (_xTr dset) (_yTr dset) tree thetas+ tOpt = paramsToConst (A.toList t) tree+ nNodes = countNodes tOpt :: Int+ nParams = length theta0+ params = A.toList t+ anyNaN = A.any isNaN t++getSSE :: Datasets -> Fix SRTree -> SSE+getSSE dset tree = SSE tr val te+ where+ (t, th) = floatConstsToParam tree+ tr = sse (_xTr dset) (_yTr dset) t (A.fromList compMode th)+ val = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ (Just xVal, Just yVal) -> sse xVal yVal t (A.fromList compMode th)+ te = case (_xTe dset, _yTe dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ (Just xTe, Just yTe) -> sse xTe yTe t (A.fromList compMode th)++getInfo :: Args -> Datasets -> Fix SRTree -> Fix SRTree -> Info+getInfo args dset tree treeVal =+ Info { _bic = bic dist' msErr' xTr yTr thetaOpt' tOpt+ , _bicVal = bicVal+ , _aic = aic dist' msErr' xTr yTr thetaOpt' tOpt+ , _aicVal = aicVal+ , _evidence = evidence dist' msErr' xTr yTr thetaOpt' tOpt+ , _evidenceVal = evidenceVal+ , _mdl = mdl dist' msErr' xTr yTr thetaOpt' tOpt+ , _mdlFreq = mdlFreq dist' msErr' xTr yTr thetaOpt' tOpt+ , _mdlLatt = mdlLatt dist' msErr' xTr yTr thetaOpt' tOpt+ , _mdlVal = mdlVal+ , _mdlFreqVal = mdlFreqVal+ , _mdlLattVal = mdlLattVal+ , _nllTr = nllTr+ , _nllVal = nllVal+ , _nllTe = nllTe+ , _cc = logFunctional tOpt+ , _cp = logParameters dist' msErr' xTr yTr thetaOpt' tOpt+ , _fisher = A.toList $ fisherNLL dist' (msErr args) xTr yTr tOpt thetaOpt'+ }+ where+ (xTr, yTr) = (_xTr dset, _yTr dset)+ (xVal, yVal) = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> (xTr, yTr)+ (_, Nothing) -> (xTr, yTr)+ (Just a, Just b) -> (a, b)+ (tOpt, thetaOpt) = floatConstsToParam tree+ thetaOpt' = A.fromList compMode thetaOpt++ (tOptVal, thetaOptVal) = floatConstsToParam treeVal+ thetaOptVal' = A.fromList compMode thetaOptVal++ dist' = dist args+ msErr' = msErr args+ nllTr = nll dist' msErr' (_xTr dset) (_yTr dset) tOpt (A.fromList compMode thetaOpt)+ bicVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ _ -> bic dist' msErr' xVal yVal thetaOptVal' tOptVal+ aicVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ _ -> aic dist' msErr' xVal yVal thetaOptVal' tOptVal+ evidenceVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ _ -> evidence dist' msErr' xVal yVal thetaOptVal' tOptVal+ nllVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ _ -> nll dist' msErr' xVal yVal tOptVal (A.fromList compMode thetaOptVal)+ mdlVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ _ -> mdl dist' msErr' xVal yVal thetaOptVal' tOptVal+ mdlFreqVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ _ -> mdlFreq dist' msErr' xVal yVal thetaOptVal' tOptVal+ mdlLattVal = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ _ -> mdlLatt dist' msErr' xVal yVal thetaOptVal' tOptVal+ nllTe = case (_xTe dset, _yTe dset) of+ (Nothing, _) -> 0.0+ (_, Nothing) -> 0.0+ (Just xTe, Just yTe) -> nll dist' msErr' xTe yTe tOpt (A.fromList compMode thetaOpt)++getCI :: Args -> Datasets -> BasicInfo -> Double -> (BasicStats, [CI], [CI], [CI], [CI])+getCI args dset basic alpha' = (stats', cis, pis_tr, pis_val, pis_te)+ where+ (Sz n) = A.size yTr+ (tree, _) = floatConstsToParam (_expr basic)+ theta = _params basic+ tau_max = (quantile (fDistribution (_nParams basic) (n - _nParams basic)) (1 - 0.01))+ tau_max' = sqrt $ quantile (fDistribution (_nParams basic) (n - _nParams basic)) (1 - alpha')+ (xTr, yTr) = (_xTr dset, _yTr dset)+ dist' = dist args+ msErr' = msErr args+ stats' = getStatsFromModel dist' msErr' xTr yTr tree (A.fromList compMode theta)+ profiles = getAllProfiles (ptype args) dist' msErr' xTr yTr tree (A.fromList compMode theta) (_stdErr stats') estCIs alpha'+ method = if useProfile args+ then Profile stats' profiles+ else Laplace stats'+ predFun = A.computeAs A.S . predict dist' tree (A.fromList compMode theta)++ prof estPi th t =+ let (thOpt, _) = minimizeNLLNonUnique dist' (Just 1) 100 xTr yTr t th+ ssr = sse xTr yTr t thOpt+ est = sqrt $ ssr / fromIntegral (n - _nParams basic)+ stdErr = _stdErr stats' A.! 0+ fun = case ptype args of+ Bates -> getProfile dist' (Just est) xTr yTr t thOpt stdErr tau_max 0+ ODE -> getProfileODE dist' (Just est) xTr yTr t thOpt stdErr estPi tau_max 0+ Constrained -> getProfileCnstr dist' (Just est) xTr yTr t thOpt stdErr tau_max' 0+ in case fun of+ Left th' -> trace "found better optima" $ prof estPi th' t+ Right p -> (_tau2theta p, _opt p)+ jac xss = forwardModeUniqueJac xss (A.fromList compMode theta) tree -- FIX++ estCIs = paramCI (Laplace stats') n (A.fromList compMode theta) 0.001+ cis = paramCI method n (A.fromList compMode theta) alpha'++ estPIS_tr = predictionCI (Laplace stats') dist' predFun jac prof xTr tree (A.fromList compMode theta) alpha' []+ estPIS_val = predictionCI (Laplace stats') dist' predFun jac prof xTr tree (A.fromList compMode theta) alpha' []+ estPIS_te = predictionCI (Laplace stats') dist' predFun jac prof xTr tree (A.fromList compMode theta) alpha' []++ pis_tr = predictionCI method dist' predFun jac prof xTr tree (A.fromList compMode theta) alpha' estPIS_tr+ pis_val = case (_xVal dset, _yVal dset) of+ (Nothing, _) -> []+ (Just xVal, _) -> predictionCI method dist' predFun jac prof xVal tree (A.fromList compMode theta) alpha' estPIS_val+ pis_te = case (_xTe dset, _yTe dset) of+ (Nothing, _) -> []+ (Just xTe, _) -> predictionCI method dist' predFun jac prof xTe tree (A.fromList compMode theta) alpha' estPIS_te
+ apps/tinygp/GP.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE BangPatterns #-}+module GP where++import Data.SRTree+import Algorithm.SRTree.Opt+import Algorithm.SRTree.Likelihoods+import Data.SRTree.Print+import Data.SRTree.Eval+import Data.SRTree.Recursion ( cata )+import System.Random+import Control.Monad.State+import Control.Monad+import Data.Vector qualified as V+import Control.Monad (when)+import Data.Massiv.Array qualified as M+import Debug.Trace ( traceShow, trace )++data Method = Grow | Full | BTC++type Rng a = StateT StdGen IO a +type GenUni = Fix SRTree -> Fix SRTree +type GenBin = Fix SRTree -> Fix SRTree -> Fix SRTree+type FitFun = Individual -> Individual ++data Individual = Individual { _tree :: Fix SRTree, _fit :: Double, _params :: PVector }++instance Show Individual where + show (Individual t f p) = showExpr t <> "," <> show f <> "," <> show p ++toss :: Rng Bool+toss = state random+{-# INLINE toss #-}++randomRange :: (Ord val, Random val) => (val, val) -> Rng val+randomRange rng = state (randomR rng)+{-# INLINE randomRange #-}++randomFrom :: [a] -> Rng a+randomFrom funs = do n <- randomRange (0, length funs - 1)+ pure $ funs !! n+{-# INLINE randomFrom #-}++randomFromV :: V.Vector a -> Rng a+randomFromV funs = do n <- randomRange (0, length funs - 1)+ pure $ funs V.! n+{-# INLINE randomFromV #-}++countNodes' :: Fix SRTree -> Int+countNodes' = cata alg + where + alg (Var _) = 1+ alg (Param _) = 1+ alg (Const _) = 0+ alg (Bin _ l r) = 1 + l + r+ alg (Uni Abs t) = t+ alg (Uni _ t) = 1 + t+{-# INLINE countNodes' #-}+++randomTree :: HyperParams -> Bool -> Rng (Fix SRTree)+randomTree hp grow + | depth <= 1 || size <= 2 = randomFrom term + | (min_depth >= 0 || (depth > 2 && not grow)) && size > 2 = genNonTerm + | otherwise = genTermOrNon+ where + min_depth = _minDepth hp+ depth = _maxDepth hp+ size = _maxSize hp+ term = _term hp+ nonterm = _nonterm hp++ genNonTerm =+ do et <- randomFrom nonterm+ case et of + Left uniT -> uniT <$> randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1} grow+ Right binT -> do l <- randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1} grow+ r <- randomTree hp{_minDepth = min_depth-1, _maxDepth = depth - 1, _maxSize = size - 1 - countNodes' l} grow+ pure (binT l r)+ genTermOrNon = do r <- toss+ if r+ then randomFrom term + else genNonTerm +{-# INLINE randomTree #-}++data HyperParams = + HP { _minDepth :: Int + , _maxDepth :: Int+ , _maxSize :: Int + , _popSize :: Int+ , _tournSize :: Int+ , _pc :: Double + , _pm :: Double + , _term :: [Fix SRTree]+ , _nonterm :: [Either GenUni GenBin] + }++tournament :: HyperParams -> V.Vector Individual -> Rng Individual+tournament hp pop = do+ selection <- replicateM (_tournSize hp) (randomFromV $ V.filter (not.isNaN._fit) pop)+ let maxFitness = maximum (fmap _fit selection)+ champions = V.filter ((== maxFitness) . _fit) pop+ if null selection+ then randomFromV pop+ else randomFromV champions+{-# INLINE tournament #-}++randomIndividual :: HyperParams -> FitFun -> Bool -> Rng Individual+randomIndividual hyperparams fitFun grow = do+ t <- randomTree hyperparams grow + let p = countParams t+ theta' <- replicateM p (randomRange (-1,1))+ let ind = fitFun $ Individual t 0.0 (M.fromList compMode theta' :: PVector)+ pure ind+ --if isInfinite (_fit ind)+ -- then randomIndividual hyperparams fitFun grow + -- else pure ind+{-# INLINE randomIndividual #-}++initialPop :: HyperParams -> FitFun -> Rng (V.Vector Individual)+initialPop hyperparams fitFun = do + let depths = [3 .. _maxDepth hyperparams]+ pop <- forM depths $ \md -> + do let m = _popSize hyperparams `div` (_maxDepth hyperparams - 3 + 1)+ g = V.fromList . take m $ cycle [True, False]+ mapM (randomIndividual hyperparams{ _maxDepth = md} fitFun) g+ pure (V.concat pop)+{-# INLINE initialPop #-}++fitness :: SRMatrix -> PVector -> Individual -> Individual+fitness x y ind =+ let + tree = relabelParams $ _tree ind+ thetaOrig = _params ind+ (theta, fit) = minimizeNLL Gaussian (Just 1) 10 x y tree thetaOrig+ --theta = _params ind+ fit' = negate $ mse x y tree thetaOrig -- nll Gaussian (Just 1) x y (relabelParams $ _tree ind) (_params ind)+ -- (fit, g) = gradNLL Gaussian Nothing x y (_tree ind) (_params ind)+ in if M.isNull (_params ind)+ then ind{_fit=fit'}+ else ind{_fit = negate (mse x y tree theta), _params = theta}+ --in ind{_fit = fit, _params = theta}+{-# INLINE fitness #-}++isAbs (Fix (Uni Abs _)) = True +isAbs _ = False +{-# INLINE isAbs #-}++isInv (Fix (Bin Div (Fix (Const 1.0)) _)) = True +isInv _ = False +{-# INLINE isInv #-}++mutate :: HyperParams -> Individual -> Rng Individual+mutate hp ind = do+ let sz = countNodes' (_tree ind)+ (t, b) <- go sz (_pm hp) (_tree ind)+ if b + then pure $ Individual t 0.0 M.empty+ else pure ind+ where+ go s p t+ | isAbs t = do let [x] = getChildren t+ (t', b) <- go s p x+ pure (Fix $ replaceChildren [t'] $ unfix t, b)+ | otherwise = do+ v <- state random + if v < p + then do let sz2 = countNodes' t + maxSz = _maxSize hp - s + sz2 - 2+ (, True) <$> randomTree hp{_maxSize = maxSz} True + else case arity t of + 0 -> pure (t, False)+ 1 -> do let [x] = getChildren t + (t', b) <- go s p x + pure (Fix $ replaceChildren [t'] $ unfix t, b)+ 2 -> do let [l,r] = getChildren t + (l', b) <- if isInv t + then pure (l, False)+ else go s p l+ if b + then pure (Fix $ replaceChildren [l', r] $ unfix t, b)+ else do (r', b') <- go s p r + pure (Fix $ replaceChildren [l, r'] $ unfix t, b')++crossover :: HyperParams -> Individual -> Individual -> Rng Individual+crossover hp ind1 ind2 = pure ind1++evolve :: HyperParams -> FitFun -> V.Vector Individual -> Rng Individual+evolve hp fitFun pop = do + parent1 <- tournament hp pop+ parent2 <- tournament hp pop + child <- crossover hp parent1 parent2+ child' <- mutate hp child+ let p = countParams (_tree child')+ theta' <- M.fromList compMode <$> replicateM p (randomRange (-1,1))+ pure $ fitFun child'{_params = theta'}+{-# INLINE evolve #-}++report :: Int -> V.Vector Individual -> IO ()+report gen = mapM_ reportOne+ where reportOne ind = do putStr (show gen)+ putStr ": "+ putStr (showExpr (_tree ind))+ putStr " - " + putStr (show (_fit ind))+ putStr " "+ print (M.toList $ _params ind)+{-# INLINE report #-}++evolution :: Int -> HyperParams -> FitFun -> Rng (V.Vector Individual)+evolution gen hp fitFun = do + pop <- initialPop hp fitFun+ liftIO $ report (-1) pop+ go gen pop + where + go 0 pop = pure pop + go n pop = do + let best = V.maximumOn _fit $ V.filter (not.isNaN._fit) pop+ pop' <- V.cons best <$> V.replicateM (_popSize hp - 1) (evolve hp fitFun pop)+ liftIO $ report (gen-n) pop'+ go (n-1) pop'
+ apps/tinygp/Initialization.hs view
@@ -0,0 +1,50 @@+module Initialization where++data InitiMethod = GROW | FULL | BTC | HALFHALF+{-+btc = undefined ++def btc(pset_, depth_, length_, type_=None):+ if type_ is None:+ type_ = pset_.ret++ expr = []++ arities = list(map(lambda x: x.arity, pset_.primitives[type_]))+ minFunctionArity = min(arities)+ maxFunctionArity = max(arities)++ # adapt length to restrictions of the primitive set+ if length_ % 2 == 0 and minFunctionArity > 1:+ length_ = length_ + 1 if np.random.random_sample(1) > 0.5 else length_ - 1++ targetLength = length_ - 1 # don't count the root node + maxFunctionArity = min(maxFunctionArity, targetLength)+ minFunctionArity = min(minFunctionArity, targetLength)+ root = sampleChild(pset_, minFunctionArity, maxFunctionArity, type_) ++ # inner lists of the form [node, depth, childIndex] + # childIndex is only used at the end to transform + # the representation from breadth to prefix+ expr.append([root, 0, 1])++ openSlots = root.arity ++ for i in range(0, length_):+ (node, nodeDepth, childIndex) = expr[i]+ childDepth = nodeDepth + 1+ + for j in range(0, getArity(node)):+ maxArity = 0 if childDepth == depth_ - 1 else min(maxFunctionArity, targetLength - openSlots)+ minArity = min(minFunctionArity, maxArity)+ child = sampleChild(pset_, minArity, maxArity, type_)++ if j == 0:+ expr[i][2] = len(expr)++ expr.append([child, childDepth, 0])+ openSlots += getArity(child) ++ nodes = breadthToPrefix(expr)+ return nodes+ -}
+ apps/tinygp/Main.hs view
@@ -0,0 +1,72 @@+module Main (main) where++import GP ( HyperParams(HP), fitness, evolution )+import Data.SRTree ( param, var )+import System.Random ( getStdGen )+import Control.Monad.State ( evalStateT )+import Data.SRTree.Datasets ( loadDataset ) +import Options.Applicative+import Data.Massiv.Array ++-- Data type to store command line arguments+data Args = Args+ { dataset :: String,+ popSize :: Int,+ gens :: Int,+ pc :: Double,+ pm :: Double+ }+ deriving (Show)++-- parser of command line arguments+opt :: Parser Args+opt = Args+ <$> strOption+ ( long "dataset"+ <> short 'd'+ <> metavar "INPUT-FILE"+ <> help "CSV dataset." )+ <*> option auto+ ( long "population"+ <> short 'p'+ <> metavar "POP-SIZE"+ <> showDefault+ <> value 100+ <> help "Population size." )+ <*> option auto+ ( long "generations"+ <> short 'g'+ <> metavar "GENS"+ <> showDefault+ <> value 100+ <> help "Number of generations." )+ <*> option auto+ ( long "probCx"+ <> metavar "PC"+ <> showDefault+ <> value 0.9+ <> help "Crossover probability." )+ <*> option auto+ ( long "probMut"+ <> metavar "PM"+ <> showDefault+ <> value 0.3+ <> help "Mutation probability." )++nonterms = [Right (+), Right (-), Right (*), Right (/), Right (\l r -> abs l ** r), Left (1/)]+--nonterms = [Right (+), Right (-), Right (*)]++main :: IO ()+main = do+ args <- execParser opts+ g <- getStdGen+ ((x, y, _, _), _, _) <- loadDataset (dataset args) True+ let hp = HP 2 4 25 (popSize args) 2 (pc args) (pm args) terms nonterms + (Sz2 _ nFeats) = size x+ terms = [var ix | ix <- [0 .. nFeats-1]] <> [param ix | ix <- [0 .. 5]]+ pop <- evalStateT (evolution (gens args) hp (fitness x y)) g+ putStrLn "Fin"+ where+ opts = info (opt <**> helper)+ ( fullDesc <> progDesc "Very simple example of GP using SRTree."+ <> header "tinyGP - a very simple example of GP using SRTRee." )
+ src/Algorithm/EqSat.hs view
@@ -0,0 +1,173 @@+{-# LANGUAGE TupleSections #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.EqSat+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability :+--+-- Equality Saturation for SRTree+-- Heavily based on hegg (https://github.com/alt-romes/hegg by alt-romes)+--+-----------------------------------------------------------------------------++module Algorithm.EqSat where++import Algorithm.EqSat.Egraph+import Algorithm.EqSat.DB+import Algorithm.EqSat.Info+import Algorithm.EqSat.Build+import Control.Lens (element, makeLenses, over, (&), (+~), (-~), (.~), (^.))+import Control.Monad.State+import Data.Function (on)+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import Data.List (intercalate, minimumBy)+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe (mapMaybe)+import Data.SRTree+import Data.HashSet (HashSet)+import qualified Data.HashSet as Set+import Control.Monad ( zipWithM )++import Debug.Trace++-- | The `Scheduler` stores a map with the banned iterations of a certain rule . +-- TODO: make it more customizable.+type Scheduler a = State (IntMap Int) a++-- to avoid importing+fromJust :: Maybe a -> a+fromJust (Just x) = x+fromJust _ = error "fromJust called with Nothing"+{-# INLINE fromJust #-}++-- | runs equality saturation from an expression tree,+-- a given set of rules, and a cost function.+-- Returns the tree with the smallest cost.+eqSat :: Monad m => Fix SRTree -> [Rule] -> CostFun -> Int -> EGraphST m (Fix SRTree)+eqSat expr rules costFun maxIt =+ do root <- fromTree costFun expr+ (end, it) <- runEqSat costFun rules maxIt+ best <- getBest root+ --info <- gets ((IntMap.! root) . _eClass)+ --info2 <- gets ((IntMap.! 9) . _eClass)+ --traceShow (info, info2) $+ if not end -- if had an early stop+ then do eqSat best rules costFun it -- reapplies eqsat on the best so far + else pure best++type CostMap = Map EClassId (Int, Fix SRTree)++-- | recalculates the costs with a new cost function+recalculateBest :: Monad m => CostFun -> EClassId -> EGraphST m (Fix SRTree)+recalculateBest costFun eid =+ do classes <- gets _eClass+ let costs = fillUpCosts classes Map.empty+ eid' <- canonical eid+ pure $ snd $ costs Map.! eid'+ where+ nodeCost :: CostMap -> ENode -> Maybe (Int, Fix SRTree)+ nodeCost costMap enode =+ do optChildren <- traverse (costMap Map.!?) (childrenOf enode) -- | gets the cost of the children, if one is missing, returns Nothing+ let cc = map fst optChildren+ nc = map snd optChildren+ n = replaceChildren cc enode+ c = costFun n+ pure (c + sum cc, Fix $ replaceChildren nc enode) -- | otherwise, returns the cost of the node + children and the expression so far++ minimumBy' f [] = Nothing+ minimumBy' f xs = Just $ minimumBy f xs++ fillUpCosts :: IntMap EClass -> CostMap -> CostMap+ fillUpCosts classes m =+ case IntMap.foldrWithKey costOfClass (False, m) classes of -- applies costOfClass to each class+ (False, _) -> m+ (True, m') -> fillUpCosts classes m' -- | if something changed, recurse++ costOfClass :: EClassId -> EClass -> (Bool, CostMap) -> (Bool, CostMap)+ costOfClass eid ecl (b, m) =+ let currentCost = m Map.!? eid+ minCost = minimumBy' (compare `on` fst) -- get the minimum available cost of the nodes of this class+ $ mapMaybe (nodeCost m)+ $ map decodeEnode+ $ Set.toList (_eNodes ecl)+ in case (currentCost, minCost) of -- replace the costs accordingly+ (_, Nothing) -> (b, m)+ (Nothing, Just new) -> (True, Map.insert eid new m)+ (Just old, Just new) -> if fst old <= fst new+ then (b, m)+ else (True, Map.insert eid new m)++-- | run equality saturation for a number of iterations+runEqSat :: Monad m => CostFun -> [Rule] -> Int -> EGraphST m (Bool, Int)+runEqSat costFun rules maxIter = go maxIter IntMap.empty+ where+ rules' = concatMap replaceEqRules rules++ -- replaces the equality rules with two one-way rules+ replaceEqRules :: Rule -> [Rule]+ replaceEqRules (p1 :=> p2) = [p1 :=> p2]+ replaceEqRules (p1 :==: p2) = [p1 :=> p2, p2 :=> p1]+ replaceEqRules (r :| cond) = map (:| cond) $ replaceEqRules r++ go it sch = do eNodes <- gets _eNodeToEClass+ eClasses <- gets _eClass+ --createDB+ --db <- gets (_patDB . _eDB) -- createDB -- creates the DB++ -- step 1: match the rules+ let matchSch = matchWithScheduler it+ matchAll = zipWithM matchSch [0..]+ (rules, sch') = runState (matchAll rules') sch++ -- step 2: apply matches and rebuild+ matches <- mapM (\rule -> map (rule,) <$> match (source rule)) $ concat rules+ mapM_ (uncurry (applyMatch costFun)) $ concat matches+ rebuild costFun++ -- recalculate heights+ --calculateHeights+ eNodes' <- gets _eNodeToEClass+ eClasses' <- gets _eClass++ -- if nothing changed, return+ if it == 1 || (eNodes' == eNodes && eClasses' == eClasses)+ then pure (True, it)+ else if IntMap.size eClasses' > 500 -- maximum allowed number of e-classes. TODO: customize+ then pure (False, it)+ else go (it-1) sch'++-- | apply a single step of merge-only equality saturation+applySingleMergeOnlyEqSat :: Monad m => CostFun -> [Rule] -> EGraphST m ()+applySingleMergeOnlyEqSat costFun rules =+ do db <- gets (_patDB . _eDB) -- createDB+ let matchSch = matchWithScheduler 10+ matchAll = zipWithM matchSch [0..]+ (rules, sch') = runState (matchAll rules') IntMap.empty+ matches <- mapM (\rule -> map (rule,) <$> match (source rule)) $ concat rules+ mapM_ (uncurry (applyMergeOnlyMatch costFun)) $ concat matches+ rebuild costFun+ -- recalculate heights+ --calculateHeights+ where+ rules' = concatMap replaceEqRules rules++ -- replaces the equality rules with two one-way rules+ replaceEqRules :: Rule -> [Rule]+ replaceEqRules (p1 :=> p2) = [p1 :=> p2]+ replaceEqRules (p1 :==: p2) = [p1 :=> p2, p2 :=> p1]+ replaceEqRules (r :| cond) = map (:| cond) $ replaceEqRules r++-- | matches the rules given a scheduler+matchWithScheduler :: Int -> Int -> Rule -> Scheduler [Rule] -- [(Rule, (Map ClassOrVar ClassOrVar, ClassOrVar))]+matchWithScheduler it ruleNumber rule =+ do mbBan <- gets (IntMap.!? ruleNumber)+ if mbBan /= Nothing && fromJust mbBan <= it -- check if the rule is banned+ then pure []+ else do -- let matches = match db (source rule)+ modify (IntMap.insert ruleNumber (it+5))+ pure [rule] -- $ map (rule,) matches
+ src/Algorithm/EqSat/Build.hs view
@@ -0,0 +1,460 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE BangPatterns #-}++-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.EqSat.Build+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability :+--+-- Functions related to building and maintaining e-graphs+-- Heavily based on hegg (https://github.com/alt-romes/hegg by alt-romes)+--+-----------------------------------------------------------------------------++module Algorithm.EqSat.Build where++import System.Random (Random (randomR), StdGen)+import Control.Lens ( over )+import Control.Monad ( forM_, when, foldM, forM )+import Data.Maybe ( fromMaybe, catMaybes )+import Data.SRTree+import Algorithm.EqSat.Egraph+--import Algorithm.EqSat.Info+import Algorithm.EqSat.DB+import qualified Data.IntMap.Strict as IntMap+import Data.Map.Strict ( Map )+import qualified Data.Map.Strict as Map+import qualified Data.HashSet as Set+import Control.Monad.State.Strict+import Data.SRTree.Recursion (cataM)+import Algorithm.EqSat.Info+import qualified Data.IntSet as IntSet+import Data.Maybe+import Data.Sequence (Seq(..), (><))++import Debug.Trace (trace, traceShow)++-- | adds a new or existing e-node (merging if necessary)+add :: Monad m => CostFun -> ENode -> EGraphST m EClassId+add costFun enode =+ do enode'' <- canonize enode -- canonize e-node+ constEnode <- calculateConsts enode''+ let enode' = case constEnode of+ ConstVal x -> Const x+ ParamIx x -> Param x+ _ -> enode''+ maybeEid <- gets ((Map.!? enode') . _eNodeToEClass) -- check if canonical e-node exists+ case maybeEid of+ Just eid -> pure eid+ Nothing -> do+ curId <- gets (_nextId . _eDB) -- get the next available e-class id+ modify' $ over canonicalMap (IntMap.insert curId curId) -- insert e-class id into canon map+ . over eNodeToEClass (Map.insert enode' curId) -- associate new e-node with id+ . over (eDB . nextId) (+1) -- update next id+ . over (eDB . worklist) (Set.insert (curId, enode')) -- add e-node and id into worklist+ forM_ (childrenOf enode') (addParents curId enode') -- update the children's parent list+ info <- makeAnalysis costFun enode'+ h <- getChildrenMinHeight enode'+ let newClass = createEClass curId enode' info h -- create e-class+ modify' $ over eClass (IntMap.insert curId newClass) -- insert new e-class into e-graph+ --modifyEClass costFun curId -- simplify eclass if it evaluates to a number++ -- update database+ addToDB enode' curId -- add new node to db+ modify' $ over (eDB . sizeDB)+ $ IntMap.insertWith (IntSet.union) (_size info) (IntSet.singleton curId)+ modify' $ over (eDB . unevaluated) (IntSet.insert curId)+ pure curId+ where+ addParents :: Monad m => EClassId -> ENode -> EClassId -> EGraphST m ()+ addParents cId node c =+ do ec <- getEClass c+ let ec' = ec{ _parents = Set.insert (cId, node) (_parents ec) }+ modify' $ over eClass (IntMap.insert c ec')++-- | rebuilds the e-graph after inserting or merging+-- e-classes+rebuild :: Monad m => CostFun -> EGraphST m ()+rebuild costFun =+ do wl <- gets (_worklist . _eDB)+ al <- gets (_analysis . _eDB)+ modify' $ over (eDB . worklist) (const Set.empty)+ . over (eDB . analysis) (const Set.empty)+ forM_ wl (uncurry (repair costFun))+ forM_ al (uncurry (repairAnalysis costFun))++-- | repairs e-node by canonizing its children+-- if the canonized e-node already exists in+-- e-graph, merge the e-classes+repair :: Monad m => CostFun -> EClassId -> ENode -> EGraphST m ()+repair costFun ecId enode =+ do modify' $ over eNodeToEClass (Map.delete enode)+ enode' <- canonize enode+ ecId' <- canonical ecId+ doExist <- gets ((Map.!? enode') . _eNodeToEClass)+ case doExist of+ Just ecIdCanon -> do mergedId <- merge costFun ecIdCanon ecId'+ modify' $ over eNodeToEClass (Map.insert enode' mergedId)+ Nothing -> modify' $ over eNodeToEClass (Map.insert enode' ecId')+++-- | repair the analysis of the e-class+-- considering the new added e-node+repairAnalysis :: Monad m => CostFun -> EClassId -> ENode -> EGraphST m ()+repairAnalysis costFun ecId enode =+ do ecId' <- canonical ecId+ enode' <- canonize enode+ eclass <- getEClass ecId'+ info <- makeAnalysis costFun enode'+ let newData = joinData (_info eclass) info+ eclass' = eclass { _info = newData }+ when (_info eclass /= newData) $+ do modify' $ over (eDB . analysis) (_parents eclass <>)+ . over eClass (IntMap.insert ecId' eclass')+ _ <- modifyEClass costFun ecId'+ pure ()++-- | merge to equivalent e-classes+merge :: Monad m => CostFun -> EClassId -> EClassId -> EGraphST m EClassId+merge costFun c1 c2 =+ do c1' <- canonical c1+ c2' <- canonical c2+ if c1' == c2' -- if they are already merged, return canonical+ then pure c1'+ else do (led, ledC, ledOrig, sub, subC, subOrig) <- getLeaderSub c1' c1 c2' c2 -- the leader will be the e-class with more parents+ mergeClasses led ledC ledOrig sub subC subOrig -- merge sub into leader+ where+ mergeClasses :: Monad m => EClassId -> EClass -> EClassId -> EClassId -> EClass -> EClassId -> EGraphST m EClassId+ mergeClasses led ledC ledO sub subC subO =+ do modify' $ over canonicalMap (IntMap.insert sub led) -- points sub e-class to leader to maintain consistency+ let -- create new e-class with same id as led+ newC = EClass led+ (_eNodes ledC `Set.union` _eNodes subC)+ (_parents ledC <> _parents subC)+ (min (_height ledC) (_height subC))+ (joinData (_info ledC) (_info subC))++ modify' $ over eClass (IntMap.insert led newC . IntMap.delete sub) -- delete sub e-class and replace leader+ . over (eDB . worklist) (_parents subC <>) -- insert parents of sub into worklist+ when (_info newC /= _info ledC) -- if there was change in data,+ $ modify' $ over (eDB . analysis) (_parents ledC <>) -- insert parents into analysis+ when (_info newC /= _info subC)+ $ modify' $ over (eDB . analysis) (_parents subC <>)+ updateDBs newC led ledC ledO sub subC subO+ modifyEClass costFun led+ pure led++ getLeaderSub c1 c1O c2 c2O =+ do ec1 <- getEClass c1+ ec2 <- getEClass c2+ let n1 = length (_parents ec1)+ n2 = length (_parents ec2)+ pure $ if n1 >= n2+ then (c1, ec1, c1O, c2, ec2, c2O)+ else (c2, ec2, c2O, c1, ec1, c1O)++ updateDBs :: Monad m => EClass -> EClassId -> EClass -> EClassId -> EClassId -> EClass -> EClassId -> EGraphST m ()+ updateDBs newC led ledC ledO sub subC subO = do+ updateFitnessDB newC led ledC ledO sub subC subO+ updateSizeDB newC led ledC ledO sub subC subO++ updateSizeDB :: Monad m => EClass -> EClassId -> EClass -> EClassId -> EClassId -> EClass -> EClassId -> EGraphST m ()+ updateSizeDB newC led ledC ledO sub subC subO = do+ let sz = (_size . _info) newC+ szL = (_size . _info) ledC+ szS = (_size . _info) subC+ fun = IntMap.adjust (IntSet.insert led) sz . IntMap.adjust (IntSet.delete led . IntSet.delete ledO) szL . IntMap.adjust (IntSet.delete sub . IntSet.delete subO) szS+ modify' $ over (eDB . sizeDB) fun++ updateFitnessDB :: Monad m => EClass -> EClassId -> EClass -> EClassId -> EClassId -> EClass -> EClassId -> EGraphST m ()+ updateFitnessDB newC led ledC ledO sub subC subO =+ if (isJust fitNew)+ then do+ when (fitNew /= fitLed) $ do+ if isNothing fitLed+ then modify' $ over (eDB . unevaluated) (IntSet.delete led . IntSet.delete ledO)+ else modify' $ over (eDB . fitRangeDB) (removeRange led (fromJust fitLed) . removeRange ledO (fromJust fitLed))+ . over (eDB . sizeFitDB) (IntMap.adjust (removeRange ledO (fromJust fitLed) . removeRange led (fromJust fitLed)) szLed)+ modify' $ over (eDB . fitRangeDB) (insertRange led (fromJust fitNew))+ . over (eDB . sizeFitDB) (IntMap.adjust (insertRange led (fromJust fitNew)) szNew . IntMap.insertWith (><) szNew Empty)+ if isNothing fitSub+ then modify' $ over (eDB . unevaluated) (IntSet.delete sub . IntSet.delete subO)+ else modify' $ over (eDB . fitRangeDB) (removeRange sub (fromJust fitSub) . removeRange subO (fromJust fitSub))+ . over (eDB . sizeFitDB) (IntMap.adjust (removeRange subO (fromJust fitSub) . removeRange sub (fromJust fitSub)) szSub)+ else modify' $ over (eDB . unevaluated) (IntSet.insert led . IntSet.delete ledO . IntSet.delete sub . IntSet.delete subO)+ where+ fitNew = (_fitness . _info) newC+ fitLed = (_fitness . _info) ledC+ fitSub = (_fitness . _info) subC+ szNew = (_size . _info) newC+ szLed = (_size . _info) ledC+ szSub = (_size . _info) subC++-- | modify an e-class, e.g., add constant e-node and prune non-leaves+modifyEClass :: Monad m => CostFun -> EClassId -> EGraphST m EClassId+modifyEClass costFun ecId =+ do ec <- getEClass ecId+ -- let term = filter isTerm (Set.toList $ _eNodes ec)+ case (_consts . _info) ec of+ ConstVal x -> do+ let en = Const x+ c <- calculateCost costFun en+ let infoEc = (_info ec){ _cost = c, _best = en, _consts = toConst en }+ maybeEid <- gets ((Map.!? en) . _eNodeToEClass)+ modify' $ over eClass (IntMap.insert ecId ec{_eNodes = Set.singleton (encodeEnode en) , _info = infoEc})+ case maybeEid of+ Nothing -> pure ecId+ Just eid' -> merge costFun eid' ecId+ {-+ ParamIx x -> do+ let en = Param x+ c <- calculateCost costFun en+ ens <- gets (_eNodes . (IntMap.! ecId) . _eClass)+ let infoEc = (_info ec){ _cost = c, _best = en, _consts = toConst en }+ maybeEid <- gets ((Map.!? en) . _eNodeToEClass)+ modify' $ over eClass (IntMap.insert ecId ec{_eNodes = Set.insert (encodeEnode en) (_eNodes ec), _info = infoEc})+ -- TODO: what happen to the orphans?+ case maybeEid of+ Nothing -> pure ecId+ Just eid' -> trace "merge" $ merge costFun eid' ecId+ -}+ _ -> pure ecId++ where+ isTerm (Var _) = True+ isTerm (Const _) = True+ isTerm (Param _) = True+ isTerm _ = False++ toConst (Param ix) = ParamIx ix+ toConst (Const x) = ConstVal x+ toConst _ = NotConst++-- * DB++-- | `createDB` creates a database of patterns from an e-graph+-- it simply calls addToDB for every pair (e-node, e-class id) from+-- the e-graph.+createDB :: Monad m => EGraphST m DB+createDB = do modify' $ over (eDB . patDB) (const Map.empty)+ ecls <- gets (Map.toList . _eNodeToEClass)+ mapM_ (uncurry addToDB) ecls+ gets (_patDB . _eDB)++-- | `addToDB` adds an e-node and e-class id to the database+addToDB :: Monad m => ENode -> EClassId -> EGraphST m () -- State DB ()+addToDB enode eid = do+ let ids = eid : childrenOf enode -- we will add the e-class id and the children ids+ op = getOperator enode -- changes Bin op l r to Bin op () () so `op` as a single entry in the DB+ trie <- gets ((Map.!? op) . _patDB . _eDB) -- gets the entry for op, if it exists+ case populate trie ids of -- populates the trie+ Nothing -> pure ()+ Just t -> modify' $ over (eDB . patDB) (Map.insert op t) -- if something was created, insert back into the DB++-- | Populates an IntTrie with a sequence of e-class ids+populate :: Maybe IntTrie -> [EClassId] -> Maybe IntTrie+populate _ [] = Nothing+-- if it is a new entry, simply add the ids sequentially+populate Nothing eids = foldr f Nothing eids+ where+ f :: EClassId -> Maybe IntTrie -> Maybe IntTrie+ f eid (Just t) = Just $ trie eid (IntMap.singleton eid t)+ f eid Nothing = Just $ trie eid IntMap.empty+-- if the entry already exists, insert the new key+-- and populate the next child entry recursivelly+populate (Just tId) (eid:eids) = let keys = Set.insert eid (_keys tId)+ nextTrie = _trie tId IntMap.!? eid+ val = fromMaybe (trie eid IntMap.empty) $ populate nextTrie eids+ in Just $ IntTrie keys (IntMap.insert eid val (_trie tId))++canonizeMap :: Monad m => (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m (Map ClassOrVar ClassOrVar, ClassOrVar)+canonizeMap (subst, cv) = (,cv) <$> traverse g subst -- Map.fromList <$> traverse f (Map.toList subst)+ where+ g :: Monad m => ClassOrVar -> EGraphST m ClassOrVar+ g (Left e2) = Left <$> canonical e2+ g e2 = pure e2++ f :: Monad m => (ClassOrVar, ClassOrVar) -> EGraphST m (ClassOrVar, ClassOrVar)+ f (e1, Left e2) = do e2' <- canonical e2+ pure (e1, Left e2')+ f (e1, e2) = pure (e1, e2)++applyMatch :: Monad m => CostFun -> Rule -> (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m ()+applyMatch costFun rule match' =+ do let conds = getConditions rule+ match <- canonizeMap match'+ validHeight <- isValidHeight match+ validConds <- mapM (`isValidConditions` match) conds+ when (validHeight && and validConds) $+ do new_eclass <- reprPrat costFun (fst match) (target rule)+ merge costFun (getInt (snd match)) new_eclass+ pure ()++applyMergeOnlyMatch :: Monad m => CostFun -> Rule -> (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m ()+applyMergeOnlyMatch costFun rule match' =+ do let conds = getConditions rule+ match <- canonizeMap match'+ validHeight <- isValidHeight match+ validConds <- mapM (`isValidConditions` match) conds+ when (validHeight && and validConds) $+ do maybe_eid <- classOfENode costFun (fst match) (target rule)+ case maybe_eid of+ Nothing -> pure ()+ Just eid -> do merge costFun (getInt (snd match)) eid+ pure ()++-- | gets the e-node of the target of the rule+-- TODO: add consts and modify+classOfENode :: Monad m => CostFun -> Map ClassOrVar ClassOrVar -> Pattern -> EGraphST m (Maybe EClassId)+classOfENode costFun subst (VarPat c) = do let maybeEid = getInt <$> subst Map.!? Right (fromEnum c)+ case maybeEid of+ Nothing -> pure Nothing+ Just eid -> Just <$> canonical eid+classOfENode costFun subst (Fixed (Const x)) = Just <$> add costFun (Const x)+classOfENode costFun subst (Fixed target) = do newChildren <- mapM (classOfENode costFun subst) (getElems target)+ case sequence newChildren of+ Nothing -> pure Nothing+ Just cs -> do let new_enode = replaceChildren cs target+ cs' <- mapM canonical cs+ areConsts <- mapM isConst cs'+ if and areConsts+ then do eid <- add costFun new_enode+ rebuild costFun -- eid new_enode+ pure (Just eid)+ else gets ((Map.!? new_enode) . _eNodeToEClass)++-- | adds the target of the rule into the e-graph+reprPrat :: Monad m => CostFun -> Map ClassOrVar ClassOrVar -> Pattern -> EGraphST m EClassId+reprPrat costFun subst (VarPat c) = canonical $ getInt $ subst Map.! Right (fromEnum c)+reprPrat costFun subst (Fixed target) = do newChildren <- mapM (reprPrat costFun subst) (getElems target)+ add costFun (replaceChildren newChildren target)++isValidHeight :: Monad m => (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m Bool+isValidHeight match = do+ h <- case snd match of+ Left ec -> do ec' <- canonical ec+ gets (_height . (IntMap.! ec') . _eClass)+ Right _ -> pure 0+ pure $ h < 15++-- | returns `True` if the condition of a rule is valid for that match+isValidConditions :: Monad m => Condition -> (Map ClassOrVar ClassOrVar, ClassOrVar) -> EGraphST m Bool+isValidConditions cond match = gets $ cond (fst match)++-- * Tree to e-graph conversion and utility functions++-- | Creates an e-graph from an expression tree+fromTree :: Monad m => CostFun -> Fix SRTree -> EGraphST m EClassId+fromTree costFun = cataM sequence (add costFun)++-- | Builds an e-graph from multiple independent trees+fromTrees :: Monad m => CostFun -> [Fix SRTree] -> EGraphST m [EClassId]+fromTrees costFun = foldM (\rs t -> do eid <- fromTree costFun t; pure (eid:rs)) []+++-- | gets the best expression given the default cost function+getBest :: Monad m => EClassId -> EGraphST m (Fix SRTree)+getBest eid = do eid' <- canonical eid+ best <- gets (_best . _info . (IntMap.! eid') . _eClass)+ childs <- mapM getBest $ childrenOf best+ pure . Fix $ replaceChildren childs best++-- | returns one expression rooted at e-class `eId`+-- TODO: avoid loopings+getExpressionFrom :: Monad m => EClassId -> EGraphST m (Fix SRTree)+getExpressionFrom eId' = do+ eId <- canonical eId'+ nodes <- gets (Set.map decodeEnode . _eNodes . (IntMap.! eId) . _eClass)+ let hasTerm = any isTerm nodes+ cands = if hasTerm then filter isTerm (Set.toList nodes) else Set.toList nodes++ Fix <$> case head $ Set.toList nodes of+ Bin op l r -> Bin op <$> getExpressionFrom l <*> getExpressionFrom r+ Uni f t -> Uni f <$> getExpressionFrom t+ Var ix -> pure $ Var ix+ Const x -> pure $ Const x+ Param ix -> pure $ Param ix+ where+ isTerm (Var _) = True+ isTerm (Const _) = True+ isTerm (Param _) = True+ isTerm _ = False++-- | returns all expressions rooted at e-class `eId`+-- TODO: check for infinite list+getAllExpressionsFrom :: Monad m => EClassId -> EGraphST m [Fix SRTree]+getAllExpressionsFrom eId' = do+ eId <- canonical eId'+ nodes <- gets (map decodeEnode . Set.toList . _eNodes . (IntMap.! eId) . _eClass)+ let cands = filter isTerm nodes+ concat <$> go nodes+ --if null cands+ -- then concat <$> go nodes+ -- else pure [toTree $ head cands]+ where+ isTerm (Var _) = True+ isTerm (Const _) = True+ isTerm (Param _) = True+ isTerm _ = False+ toTree (Var ix) = Fix $ Var ix+ toTree (Const x) = Fix $ Const x+ toTree (Param ix) = Fix $ Param ix+ toTree _ = undefined++ go [] = pure []+ go (n:ns) = do+ t <- Prelude.map Fix <$> case n of+ Bin op l r -> do l' <- getAllExpressionsFrom l+ r' <- getAllExpressionsFrom r+ pure $ [Bin op li ri | li <- l', ri <- r']+ Uni f t -> Prelude.map (Uni f) <$> getAllExpressionsFrom t+ Var ix -> pure [Var ix]+ Const x -> pure [Const x]+ Param ix -> pure [Param ix]+ ts <- go ns+ pure (t:ts)++-- | returns a random expression rooted at e-class `eId`+getRndExpressionFrom :: EClassId -> EGraphST (State StdGen) (Fix SRTree)+getRndExpressionFrom eId' = do+ eId <- canonical eId'+ nodes <- gets (Set.toList . _eNodes . (IntMap.! eId) . _eClass)+ n <- lift $ randomFrom nodes+ Fix <$> case decodeEnode n of+ Bin op l r -> Bin op <$> getRndExpressionFrom l <*> getRndExpressionFrom r+ Uni f t -> Uni f <$> getRndExpressionFrom t+ Var ix -> pure $ Var ix+ Const x -> pure $ Const x+ Param ix -> pure $ Param ix+ where+ randomRange rng = state (randomR rng)+ randomFrom xs = do n <- randomRange (0, length xs - 1)+ pure $ xs !! n++cleanMaps :: Monad m => EGraphST m ()+cleanMaps = do+ enode2eclass <- gets _eNodeToEClass+ entries <- forM (Map.toList enode2eclass) $ \(k,v) -> do+ k' <- canonize k+ v' <- canonical v+ pure (k',v')+ let enode2eclass' = Map.fromList entries+ eclassMap <- gets _eClass+ entries' <- forM (IntMap.toList eclassMap) $ \(k,v) -> do+ k' <- canonical k+ pure $ if k==k' then (Just (k,v)) else Nothing+ let eclassMap' = IntMap.fromList (catMaybes entries')+ canon <- gets _canonicalMap+ entries'' <- forM (IntMap.toList canon) $ \(k,v) -> do+ pure $ if k==v then Just (k,v) else Nothing+ let canon' = IntMap.fromList (catMaybes entries'')+ eDB' <- gets _eDB+ put $ EGraph canon enode2eclass' eclassMap' eDB'+ forceState++forceState :: Monad m => StateT s m ()+forceState = get >>= \ !_ -> return ()
+ src/Algorithm/EqSat/DB.hs view
@@ -0,0 +1,351 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE FlexibleContexts #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.EqSat.EqSatDB+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability :+--+-- Pattern matching and rule application functions+-- Heavily based on hegg (https://github.com/alt-romes/hegg by alt-romes)+--+-----------------------------------------------------------------------------+module Algorithm.EqSat.DB where++import Algorithm.EqSat.Egraph+import Control.Lens ( over )+import Control.Monad (when, foldM, forM)+import Control.Monad.State+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import Data.List (intercalate, nub, sortBy)+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe (fromMaybe)+import Data.Ord (comparing)+import Data.SRTree+--import Data.Set (Set)+import Data.HashSet (HashSet)+import qualified Data.HashSet as Set+import Data.String (IsString (..))++import Debug.Trace++-- A Pattern is either a fixed-point of a tree or an+-- index to a pattern variable. The pattern variable matches anything. +data Pattern = Fixed (SRTree Pattern) | VarPat Char deriving Show -- Fixed structure of a pattern or a variable that matches anything++-- The instance for `IsString` for a `Pattern` is +-- valid only for a single letter char from a-zA-Z. +-- The patterns can be written as "x" + "y", for example,+-- and it will translate to `Fixed (Bin Add (VarPat 120) (VarPat 121)`.+instance IsString Pattern where+ fromString [] = error "empty string in VarPat"+ fromString [c] | n >= 65 && n <= 122 = VarPat c where n = fromEnum c+ fromString s = error $ "invalid string in VarPat: " <> s++-- A rule is either a directional rule where pat1 can be replaced by pat2, a bidirectional rule +-- where pat1 can be replaced or replace pat2, or a pattern with a conditional function +-- describing when to apply the rule +data Rule = Pattern :=> Pattern | Pattern :==: Pattern | Rule :| Condition++infix 3 :=>+infix 3 :==:+infixl 2 :|++instance Show Rule where+ show (a :=> b) = show a <> " => " <> show b+ show (a :==: b) = show a <> " == " <> show b+ show (a :| b) = show a <> " | <cond>"++-- A Query is a list of Atoms +type Query = [Atom]++-- A `Condition` is a function that takes a substution map,+-- an e-graph and returns whether the pattern attends the condition.+type Condition = Map ClassOrVar ClassOrVar -> EGraph -> Bool++-- An Atom is composed of either an e-class id or pattern variable id+-- and the tree that generated that pattern. Left is e-class id and Right is a VarPat.+type ClassOrVar = Either EClassId Int+data Atom = Atom ClassOrVar (SRTree ClassOrVar) deriving Show++unFixPat :: Pattern -> SRTree Pattern+unFixPat (Fixed p) = p+{-# INLINE unFixPat #-}+++instance Num Pattern where+ l + r = Fixed $ Bin Add l r+ {-# INLINE (+) #-}+ l - r = Fixed $ Bin Sub l r+ {-# INLINE (-) #-}+ l * r = Fixed $ Bin Mul l r+ {-# INLINE (*) #-}++ abs = Fixed . Uni Abs+ {-# INLINE abs #-}++ negate t = Fixed (Const (-1)) * t+ {-# INLINE negate #-}++ signum t = case t of+ Fixed (Const x) -> Fixed . Const $ signum x+ _ -> Fixed (Const 0)+ fromInteger x = Fixed $ Const (fromInteger x)+ {-# INLINE fromInteger #-}++instance Fractional Pattern where+ l / r = Fixed $ Bin Div l r+ {-# INLINE (/) #-}++ fromRational = Fixed . Const . fromRational+ {-# INLINE fromRational #-}++instance Floating Pattern where+ pi = Fixed $ Const pi+ {-# INLINE pi #-}+ exp = Fixed . Uni Exp+ {-# INLINE exp #-}+ log = Fixed . Uni Log+ {-# INLINE log #-}+ sqrt = Fixed . Uni Sqrt+ {-# INLINE sqrt #-}+ sin = Fixed . Uni Sin+ {-# INLINE sin #-}+ cos = Fixed . Uni Cos+ {-# INLINE cos #-}+ tan = Fixed . Uni Tan+ {-# INLINE tan #-}+ asin = Fixed . Uni ASin+ {-# INLINE asin #-}+ acos = Fixed . Uni ACos+ {-# INLINE acos #-}+ atan = Fixed . Uni ATan+ {-# INLINE atan #-}+ sinh = Fixed . Uni Sinh+ {-# INLINE sinh #-}+ cosh = Fixed . Uni Cosh+ {-# INLINE cosh #-}+ tanh = Fixed . Uni Tanh+ {-# INLINE tanh #-}+ asinh = Fixed . Uni ASinh+ {-# INLINE asinh #-}+ acosh = Fixed . Uni ACosh+ {-# INLINE acosh #-}+ atanh = Fixed . Uni ATanh+ {-# INLINE atanh #-}++ l ** r = Fixed $ Bin Power l r+ {-# INLINE (**) #-}++ logBase l r = log l / log r+ {-# INLINE logBase #-}++target :: Rule -> Pattern+target (r :| _) = target r+target (_ :=> t) = t+target (_ :==: t) = t++source :: Rule -> Pattern+source (r :| _) = source r+source (s :=> _) = s+source (s :==: _) = s++getConditions :: Rule -> [Condition]+getConditions (r :| c) = c : getConditions r+getConditions _ = []+++cleanDB :: Monad m => EGraphST m ()+cleanDB = modify' $ over (eDB. patDB) (const Map.empty)++-- | Returns the substitution rules+-- for every match of the pattern `source` inside the e-graph.+match :: Monad m => Pattern -> EGraphST m [(Map ClassOrVar ClassOrVar, ClassOrVar)]+match src = do+ let (q, root) = compileToQuery src -- compile the source of the pattern into a query+ substs <- genericJoin q root -- find the substituion rules for this pattern+ pure [(s, s Map.! root) | s <- substs, Map.size s > 0]++-- | Returns a Query (list of atoms) of a pattern+compileToQuery :: Pattern -> (Query, ClassOrVar)+compileToQuery pat = evalState (processPat pat) 256 -- returns (atoms, root)+ where+ -- creates the atoms of a pattern+ processPat :: Pattern -> State Int (Query, ClassOrVar)+ processPat (VarPat x) = pure ([], Right $ fromEnum x)+ processPat (Fixed pat) = do+ -- get the next available var id and add as root+ v <- get+ let root = Right v+ -- updates the next available id+ modify (+1)+ -- recursivelly process the children of the pattern+ patChilds <- mapM processPat (getElems pat)+ -- create an atom composed of the+ -- root and the tree with the children+ -- replaced by the childs roots+ -- add the child atoms to the list+ let atoms = concatMap fst patChilds+ roots = map snd patChilds+ atom = Atom root (replaceChildren roots pat)+ atoms' = atom:atoms+ pure (atoms', root)++-- get the value from the Either Int Int+getInt :: ClassOrVar -> Int+getInt (Left a) = a+getInt (Right a) = a++-- | returns the list of the children values+getElems :: SRTree a -> [a]+getElems (Bin _ l r) = [l,r]+getElems (Uni _ t) = [t]+getElems _ = []++-- | Creates the substituion map for+-- the pattern variables for each one of the+-- matched subgraph+genericJoin :: Monad m => Query -> ClassOrVar -> EGraphST m [Map ClassOrVar ClassOrVar]+genericJoin atoms root = do+ let vars = orderedVars atoms -- order the vars, starting with the most frequently occuring+ go atoms vars -- TODO: investigate why we need nub+ where+ -- for each variable+ -- for each possible e-class id for that variable+ -- replace the var id with this e-class id, and+ -- recurse to find the possible matches for the next atom+ go :: Monad m => Query -> [ClassOrVar] -> EGraphST m [Map ClassOrVar ClassOrVar]+ go atoms [] = pure [Map.empty] -- | _ <- atoms]+ go atoms (x:vars) = do cIds1 <- domainX x atoms root+ maps <- forM cIds1 $ \classId -> do+ map (Map.insert x classId) <$> go (updateVar x classId atoms) vars+ pure (concat maps)+++ -- [Map.insert x classId y | classId <- domainX db x atoms+ -- , y <- go (updateVar x classId atoms) vars]+++-- | returns the e-class id for a certain variable that+-- matches the pattern described by the atoms+domainX :: Monad m => ClassOrVar -> Query -> ClassOrVar -> EGraphST m [ClassOrVar]+domainX var atoms root = do+ let atoms' = filter (elemOfAtom var) atoms -- :: [ClassOrVar] -- look only in the atoms with this var+ map Left <$> intersectAtoms var atoms' root -- find the intersection of possible keys by each atom++ --let ss = (map Left+ -- $ intersectAtoms var db+ -- $+ -- in ss++-- | returns all e-class id that can matches this sequence of atoms+intersectAtoms :: Monad m => ClassOrVar -> Query -> ClassOrVar -> EGraphST m [EClassId]+intersectAtoms _ [] root = pure []+intersectAtoms var (a:atoms) root = do+ a0 <- go a+ Set.toList <$> (foldM (\acc atom -> Set.intersection acc <$> go atom) a0 atoms)+ where+ -- canonize everything except the root for consistency+ -- doing this here prevents traversing the map again+ toCanon x = if var==root+ then pure x+ else Set.fromList <$> (mapM canonical $ Set.toList x)++ go (Atom r t) = do+ let op = getOperator t+ mTrie <- gets ((Map.!? op) . _patDB . _eDB)+ case mTrie of+ Just trie -> pure (fromMaybe Set.empty $ intersectTries var Map.empty trie (r:getElems t))+ Nothing -> pure Set.empty+ -- TODO: remove FlexibleContexts+ --if op `Map.member` db -- if the e-graph contains the operator+ -- try to find an intersection of the tries that matches each atom of the pattern+ -- then+ -- else pure Set.empty++-- | searches for the intersection of e-class ids that+-- matches each part of the query.+-- Returns Nothing if the intersection is empty.+--+-- var is the current variable being investigated+-- xs is the map of ids being investigated and their corresponding e-class id+-- trie is the current trie of the pattern+-- (i:ids) sequence of root : children of the atom to investigate+-- NOTE: it must be Maybe Set to differentiate between empty set and no answer+intersectTries :: ClassOrVar -> Map ClassOrVar EClassId -> IntTrie -> [ClassOrVar] -> Maybe (HashSet EClassId)+intersectTries var xs trie [] = Just Set.empty+intersectTries var xs trie (i:ids) =+ case i of+ Left x -> if x `Set.member` _keys trie+ -- if the current investigated id is an e-class id and+ -- it is one of the keys of the trie...+ -- ..try to match the next id with the next trie+ then intersectTries var xs (_trie trie IntMap.! x) ids+ else Nothing+ Right x -> if i `Map.member` xs+ -- if it is a pattern variable under investigation+ -- and the e-class id is part of the trie+ then if xs Map.! i `Set.member` _keys trie+ -- match the next id with the next trie+ then intersectTries var xs (_trie trie IntMap.! (xs Map.! i)) ids+ else Nothing+ else if Right x == var+ -- not under investigation and is the var of interest+ then if all (isDiffFrom x) ids+ -- if there are no other occurrence of x in the next vars,+ -- the keys of the trie are all possible candidates+ then Just $ _keys trie+ -- oterwise, put i under investigation and check the next occurrences+ -- returning the intersection+ else Just $ IntMap.foldrWithKey (\k v acc ->+ case intersectTries var (Map.insert i k xs) v ids of+ Nothing -> acc+ _ -> Set.insert k acc) Set.empty (_trie trie)+ -- if it is not the var of interest+ -- assign and test all possible e-class ids to it+ -- and move forward+ else Just $ IntMap.foldrWithKey (\k v acc ->+ case intersectTries var (Map.insert i k xs) v ids of+ Nothing -> acc+ Just s -> Set.union acc s+ ) Set.empty (_trie trie)++-- | updates all occurrence of var with the new id x+updateVar :: ClassOrVar -> ClassOrVar -> Query -> Query+updateVar var x = map replace+ where+ replace (Atom r t) = let children = [if c == var then x else c | c <- getElems t]+ t' = replaceChildren children t+ in Atom (if r == var then x else r) t'++-- | checks whether two ClassOrVar are different+-- only check if it is a pattern variable, else returns true+isDiffFrom :: Int -> ClassOrVar -> Bool+isDiffFrom x y = case y of+ Left _ -> False+ Right z -> x /= z++-- | checks if v is an element of an atom+elemOfAtom :: ClassOrVar -> Atom -> Bool+elemOfAtom v (Atom root tree) =+ case root of+ Left _ -> v `elem` getElems tree+ Right x -> Right x == v || v `elem` getElems tree++-- | sorts the variables in a query by the most frequently occurring+orderedVars :: Query -> [ClassOrVar]+orderedVars atoms = sortBy (comparing varCost) $ nub [a | atom <- atoms, a <- getIdsFrom atom, isRight a]+ where+ getIdsFrom (Atom r t) = r : getElems t+ isRight (Right _) = True+ isRight _ = False++ varCost :: ClassOrVar -> Int+ varCost var = foldr (\a acc -> if elemOfAtom var a then acc - 100 + atomLen a else acc) 0 atoms++ atomLen (Atom _ t) = 1 + length (getElems t)
+ src/Algorithm/EqSat/Egraph.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.EqSat.Egraph+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability :+--+-- Equality Graph data structure +-- Heavily based on hegg (https://github.com/alt-romes/hegg by alt-romes)+--+-----------------------------------------------------------------------------++module Algorithm.EqSat.Egraph where++import Control.Lens (element, makeLenses, view, over, (&), (+~), (-~), (.~), (^.))+--import Control.Monad (forM, forM_, when, foldM, void)+import Data.List ( intercalate )+import Control.Monad.State.Strict+import Data.IntMap.Strict (IntMap)+import qualified Data.IntMap.Strict as IntMap+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.HashSet (HashSet)+import qualified Data.HashSet as Set+import Data.IntSet (IntSet)+import qualified Data.IntSet as IntSet+import Data.Sequence ( Seq(..), (><) )+import qualified Data.Sequence as FingerTree+import Data.Foldable ( toList )+import Data.SRTree+import Data.SRTree.Eval+import Data.Hashable++import Debug.Trace++type EClassId = Int -- NOTE: DO NOT CHANGE THIS, this will break the use of IntMap and IntSet+type ClassIdMap = IntMap+type ENode = SRTree EClassId+type ENodeEnc = (Int, Int, Int, Double)+type EGraphST m a = StateT EGraph m a+type Cost = Int+type CostFun = SRTree Cost -> Cost++instance Hashable ENode where+ hashWithSalt n enode = hashWithSalt n (encodeEnode enode)++type RangeTree a = Seq (a, EClassId)++-- | this assumes up to 999 variables and params+encodeEnode :: ENode -> ENodeEnc+--encodeEnode = id+{--}+encodeEnode (Var ix) = (0, ix, -1, 0)+encodeEnode (Param ix) = (1, ix, -1, 0)+encodeEnode (Const x) = (2, -1, -1, x)+encodeEnode (Uni f ed) = (300 + fromEnum f, ed, -1, 0)+encodeEnode (Bin op ed1 ed2) = (400 + fromEnum op, ed1, ed2, 0)+{--}+{-# INLINE encodeEnode #-}++decodeEnode :: ENodeEnc -> ENode+--decodeEnode = id+{--}+decodeEnode (0, ix, _, _) = Var ix+decodeEnode (1, ix, _, _) = Param ix+decodeEnode (2, _, _, x) = Const x+decodeEnode (opCode, arg1, arg2, arg3)+ | opCode < 400 = Uni (toEnum $ opCode-300) arg1+ | otherwise = Bin (toEnum $ opCode-400) arg1 arg2+ {--}+{-# INLINE decodeEnode #-}++insertRange :: (Ord a, Show a) => EClassId -> a -> RangeTree a -> RangeTree a+insertRange eid x Empty = FingerTree.singleton (x, eid)+insertRange eid x (y :<| _xs) | (x, eid) < y = (x, eid) :<| y :<| _xs+insertRange eid x (_xs :|> y) | (x, eid) > y = _xs :|> y :|> (x, eid)+insertRange eid x rt = go rt+ where+ entry = (x, eid)+ go root = case FingerTree.splitAt (n `div` 2) root of+ (Empty, Empty) -> FingerTree.singleton entry+ (Empty, z :<| zs) | entry < z -> entry :<| z :<| zs+ | otherwise -> z :<| (go zs)+ (ys :|> y, Empty) | entry > y -> ys :|> y :|> entry+ | otherwise -> (go ys) :|> y+ (ys :|> y, z :<| zs)+ | entry > y && entry < z -> (ys :|> y :|> entry) >< (z :<| zs)+ | entry > z -> (ys :|> y) >< go (z :<| zs)+ | entry < y -> go (ys :|> y) >< (z :<| zs)+ | otherwise -> root+ where+ n = FingerTree.length root++removeRange :: (Ord a, Show a) => EClassId -> a -> RangeTree a -> RangeTree a+removeRange eid x Empty = Empty+removeRange eid x (y :<| _xs) | (x, eid) < y = (y :<| _xs)+removeRange eid x (_xs :|> y) | (x, eid) > y = (_xs :|> y)+removeRange eid x rt = go rt+ where+ entry = (x, eid)+ go root = case FingerTree.splitAt (n `div` 2) root of+ (Empty, Empty) -> root+ (Empty, z :<| zs)+ | entry < z -> z :<| zs+ | entry == z -> zs+ | otherwise -> z :<| (go zs)+ (ys :|> y, Empty)+ | entry > y -> ys :|> y+ | entry == y -> ys+ | otherwise -> (go ys) :|> y+ (ys :|> y, z :<| zs)+ | entry > y && entry < z -> root+ | entry > z -> (ys :|> y) >< go (z :<| zs)+ | entry < y -> go (ys :|> y) >< (z :<| zs)+ | otherwise -> root++ where+ n = FingerTree.length root+++-- TODO: check this \/+getWithinRange :: Ord a => a -> a -> RangeTree a -> [EClassId]+getWithinRange lb ub rt = map snd . toList $ go rt+ where+ go Empty = Empty+ go root = case FingerTree.splitAt (n `div` 2) root of+ (Empty, Empty) -> Empty+ (ys :|> y, Empty)+ | fst y < lb -> Empty+ | otherwise -> go (ys :|> y)+ (Empty, z :<| zs)+ | fst z > ub -> Empty+ | otherwise -> go (z :<| zs)+ (ys :|> y, z :<| zs)+ | fst y < lb -> go (z :<| zs)+ | fst z > ub -> go (ys :|> y)+ | otherwise -> go (ys :|> y) >< go (z :<| zs)+ where+ n = FingerTree.length root+++getSmallest :: Ord a => RangeTree a -> (a, EClassId)+getSmallest rt = case rt of+ Empty -> error "empty finger"+ x :<| t -> x+getGreatest :: Ord a => RangeTree a -> (a, EClassId)+getGreatest rt = case rt of+ Empty -> error "empty finger"+ t :|> x -> x+++data EGraph = EGraph { _canonicalMap :: ClassIdMap EClassId -- maps an e-class id to its canonical form+ , _eNodeToEClass :: Map ENode EClassId -- maps an e-node to its e-class id+ , _eClass :: ClassIdMap EClass -- maps an e-class id to its e-class data+ , _eDB :: EGraphDB+ } deriving Show++data EGraphDB = EDB { _worklist :: HashSet (EClassId, ENode) -- e-nodes and e-class schedule for analysis+ , _analysis :: HashSet (EClassId, ENode) -- e-nodes and e-class that changed data+ , _patDB :: DB -- database of patterns+ , _fitRangeDB :: RangeTree Double -- database of valid fitness+ , _sizeDB :: IntMap IntSet -- database of model sizes+ , _sizeFitDB :: IntMap (RangeTree Double) -- hacky! Size x Fitness DB+ , _unevaluated :: IntSet -- set of not-evaluated e-classes+ , _nextId :: Int -- next available id+ } deriving Show++data EClass = EClass { _eClassId :: Int -- e-class id (maybe we don't need that here)+ , _eNodes :: HashSet ENodeEnc -- set of e-nodes inside this e-class+ , _parents :: HashSet (EClassId, ENode) -- parents (e-class, e-node)'s+ , _height :: Int -- height+ , _info :: EClassData -- data+ } deriving (Show, Eq)++data Consts = NotConst | ParamIx Int | ConstVal Double deriving (Show, Eq)+data Property = Positive | Negative | NonZero | Real deriving (Show, Eq) -- TODO: incorporate properties++data EClassData = EData { _cost :: Cost+ , _best :: ENode+ , _consts :: Consts+ , _fitness :: Maybe Double -- NOTE: this cannot be NaN+ , _theta :: Maybe PVector+ , _size :: Int+ -- , _properties :: Property+ -- TODO: include evaluation of expression from this e-class+ } deriving (Show)++instance Eq EClassData where+ EData c1 b1 cs1 ft1 _ s1 == EData c2 b2 cs2 ft2 _ s2 = c1==c2 && b1==b2 && cs1==cs2 && ft1==ft2 && s1==s2++-- The database maps a symbol to an IntTrie+-- The IntTrie stores the possible paths from a certain e-class+-- that matches a pattern+type DB = Map (SRTree ()) IntTrie+-- The IntTrie is composed of the set of available keys (for convenience)+-- and an IntMap that maps one e-class id to the first child IntTrie,+-- the first child IntTrie will point to the next child and so on+data IntTrie = IntTrie { _keys :: HashSet EClassId, _trie :: IntMap IntTrie } -- deriving Show++-- Shows the IntTrie as {keys} -> {show IntTries}+instance Show IntTrie where+ show (IntTrie k t) = let keys = intercalate "," (map show $ Set.toList k)+ tries = intercalate "," (map (\(k,v) -> show k <> " -> " <> show v) $ IntMap.toList t)+ in "{" <> keys <> "} - {" <> tries <> "}"++makeLenses ''EGraph+makeLenses ''EClass+makeLenses ''EClassData+makeLenses ''EGraphDB++-- * E-Graph basic supporting functions++-- | returns an empty e-graph+emptyGraph :: EGraph+emptyGraph = EGraph IntMap.empty Map.empty IntMap.empty emptyDB++-- | returns an empty e-graph DB+emptyDB :: EGraphDB+emptyDB = EDB Set.empty Set.empty Map.empty FingerTree.empty IntMap.empty IntMap.empty IntSet.empty 0++-- | Creates a new e-class from an e-class id, a new e-node,+-- and the info of this e-class +createEClass :: EClassId -> ENode -> EClassData -> Int -> EClass+createEClass cId enode' info h = EClass cId (Set.singleton $ encodeEnode enode') Set.empty h info+{-# INLINE createEClass #-}++-- | gets the canonical id of an e-class+canonical :: Monad m => EClassId -> EGraphST m EClassId+canonical eclassId =+ do m <- gets _canonicalMap+ let oneStep = m IntMap.! eclassId+ if oneStep == eclassId+ then pure eclassId+ else go m oneStep+ where+ go :: Monad m => IntMap EClassId -> EClassId -> EGraphST m EClassId+ go m ecId+ | m IntMap.! ecId == ecId = do modify' $ over canonicalMap (IntMap.insert eclassId ecId) -- creates a shortcut for next time+ pure ecId -- if the e-class id is mapped to itself, it's canonical+ | otherwise = go m (m IntMap.! ecId) -- otherwise, check the next id in the sequence+{-# INLINE canonical #-}++-- | canonize the e-node children+canonize :: Monad m => ENode -> EGraphST m ENode+canonize = mapM canonical -- applies canonical to the children+{-# INLINE canonize #-}++-- | gets an e-class with id `c`+getEClass :: Monad m => EClassId -> EGraphST m EClass+getEClass c = gets ((IntMap.! c) . _eClass)+{-# INLINE getEClass #-}++-- | Creates a singleton trie from an e-class id+trie :: EClassId -> IntMap IntTrie -> IntTrie+trie eid = IntTrie (Set.singleton eid)++-- | Check whether an e-class is a constant value+isConst :: Monad m => EClassId -> EGraphST m Bool+isConst eid = do ec <- gets ((IntMap.! eid) . _eClass)+ case (_consts . _info) ec of+ ConstVal _ -> pure True+ _ -> pure False+{-# INLINE isConst #-}
+ src/Algorithm/EqSat/Info.hs view
@@ -0,0 +1,168 @@+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.EqSat.Info+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability :+--+-- Functions related to info/data calculation in Equality Graph data structure+-- Heavily based on hegg (https://github.com/alt-romes/hegg by alt-romes)+--+-----------------------------------------------------------------------------++module Algorithm.EqSat.Info where++import Control.Lens ( over )+import Control.Monad --(forM, forM_, when, foldM, void)+import Control.Monad.State+import Data.AEq (AEq ((~==)))+import Data.IntMap (IntMap) -- , delete, empty, insert, toList)+import qualified Data.IntMap as IntMap+import Data.Map (Map)+import qualified Data.Map as Map+import Data.SRTree+import Data.SRTree.Eval (evalFun, evalOp, PVector)+import Data.HashSet (HashSet)+import qualified Data.HashSet as Set+import qualified Data.IntSet as IntSet+import Algorithm.EqSat.Egraph+import Data.AEq (AEq ((~==)))+import Algorithm.EqSat.Queries+import Data.Maybe+import qualified Data.Set as TrueSet+import Data.Sequence (Seq(..), (><))++import Debug.Trace++-- * Data related functions ++-- | join data from two e-classes+-- TODO: instead of folding, just do not apply rules+-- list of values instead of single value+joinData :: EClassData -> EClassData -> EClassData+joinData (EData c1 b1 cn1 fit1 p1 sz1) (EData c2 b2 cn2 fit2 p2 sz2) =+ EData (min c1 c2) b (combineConsts cn1 cn2) (minMaybe fit1 fit2) (bestParam p1 p2 fit1 fit2) (min sz1 sz2)+ where+ minMaybe Nothing x = x+ minMaybe x Nothing = x+ minMaybe x y = min x y++ bestParam Nothing x _ _ = x+ bestParam x Nothing _ _ = x+ bestParam x y (Just f1) (Just f2) = if f1 < f2 then x else y++ b = if c1 <= c2 then b1 else b2+ combineConsts (ConstVal x) (ConstVal y)+ | abs (x-y) < 1e-7 = ConstVal $ (x+y)/2+ | isNaN x || isInfinite x = ConstVal y + | isNaN y || isInfinite y = ConstVal x+ | isNaN x && isNaN y = ConstVal x+ | x ~== y = ConstVal $ (x+y)/2+ | abs (x / y) < 1 + 1e-6 || abs (y / x) < 1 + 1e-6 = ConstVal $ min x y+ | isInfinite x && isInfinite y = ConstVal x+ | isInfinite x && isNaN y = ConstVal y+ | isNaN x && isInfinite y = ConstVal x+ | otherwise = error $ "Combining different values: " <> show x <> " " <> show y <> " " <> show (x/y)+ combineConsts (ParamIx ix) (ParamIx iy) = ParamIx (min ix iy)+ combineConsts NotConst x = x+ combineConsts x NotConst = x+ combineConsts x y = error (show x <> " " <> show y)++-- | Calculate e-node data (constant values and cost)+makeAnalysis :: Monad m => CostFun -> ENode -> EGraphST m EClassData+makeAnalysis costFun enode =+ do consts <- calculateConsts enode+ enode' <- canonize enode+ cost <- calculateCost costFun enode'+ sz <- sum <$> mapM (\ecId -> gets (_size . _info . (IntMap.! ecId) . _eClass)) (childrenOf enode')+ pure $ EData cost enode' consts Nothing Nothing (sz+1)++getChildrenMinHeight :: Monad m => ENode -> EGraphST m Int+getChildrenMinHeight enode = do+ let children = childrenOf enode+ minimum' [] = 0+ minimum' xs = minimum xs+ minimum' <$> mapM (\ec -> gets (_height . (IntMap.! ec) . _eClass)) children++-- | update the heights of each e-class+-- won't work if there's no root+calculateHeights :: Monad m => EGraphST m ()+calculateHeights =+ do queue <- findRootClasses+ classes <- gets (Prelude.map fst . IntMap.toList . _eClass)+ let nClasses = length classes+ forM_ classes (setHeight nClasses) -- set all heights to max possible height (number of e-classes)+ forM_ queue (setHeight 0) -- set root e-classes height to zero+ go queue (TrueSet.fromList queue) 1 -- next height is 1+ where+ setHeight x eId' =+ do eId <- canonical eId'+ ec <- getEClass eId+ let ec' = over height (const x) ec+ modify' $ over eClass (IntMap.insert eId ec')++ setMinHeight x eId' = -- set height to the minimum between current and x+ do eId <- canonical eId'+ h <- _height <$> getEClass eId+ setHeight (min h x) eId++ getChildrenEC :: Monad m => EClassId -> EGraphST m [EClassId]+ getChildrenEC ec' = do ec <- canonical ec'+ gets (concatMap childrenOf' . _eNodes . (IntMap.! ec) . _eClass)++ childrenOf' (_, -1, -1, _) = []+ childrenOf' (_, e1, -1, _) = [e1]+ childrenOf' (_, e1, e2, _) = [e1, e2]++ go [] _ _ = pure ()+ go qs tabu h =+ do childrenOf <- (TrueSet.\\ tabu) . TrueSet.fromList . concat <$> forM qs getChildrenEC -- rerieve all unvisited children+ let childrenL = TrueSet.toList childrenOf+ forM_ childrenL (setMinHeight h) -- set the height of the children as the minimum between current and h+ go childrenL (TrueSet.union tabu childrenOf) (h+1) -- move one breadth search style++-- | calculates the cost of a node+calculateCost :: Monad m => CostFun -> SRTree EClassId -> EGraphST m Cost+calculateCost f t =+ do let cs = childrenOf t+ costs <- traverse (fmap (_cost . _info) . getEClass) cs+ pure . f $ replaceChildren costs t++-- | check whether an e-node evaluates to a const+calculateConsts :: Monad m => SRTree EClassId -> EGraphST m Consts+calculateConsts t =+ do let cs = childrenOf t+ eg <- get+ consts <- traverse (fmap (_consts . _info) . getEClass) cs+ case combineConsts $ replaceChildren consts t of+ ConstVal x | isNaN x -> pure (ConstVal x)+ a -> pure a++combineConsts :: SRTree Consts -> Consts+combineConsts (Const x) = ConstVal x+combineConsts (Param ix) = ParamIx ix+combineConsts (Var _) = NotConst+combineConsts (Uni f t) = case t of+ ConstVal x -> ConstVal $ evalFun f x+ _ -> t+combineConsts (Bin op l r) = evalOp' l r+ where+ evalOp' (ParamIx ix) (ParamIx iy) = ParamIx (min ix iy)+ evalOp' (ConstVal x) (ConstVal y) = ConstVal $ evalOp op x y+ evalOp' _ _ = NotConst++insertFitness :: Monad m => EClassId -> Double -> PVector -> EGraphST m ()+insertFitness eId fit params = do+ ec <- gets ((IntMap.! eId) . _eClass)+ let oldFit = _fitness . _info $ ec+ newInfo = (_info ec){_fitness = Just fit, _theta = Just params}+ newEc = ec{_info = newInfo}+ sz = _size newInfo+ modify' $ over eClass (IntMap.insert eId newEc)+ if (isNothing oldFit)+ then modify' $ over (eDB . unevaluated) (IntSet.delete eId)+ . over (eDB . fitRangeDB) (insertRange eId fit)+ . over (eDB . sizeFitDB) (IntMap.adjust (insertRange eId fit) sz . IntMap.insertWith (><) sz Empty)+ else modify' $ over (eDB . fitRangeDB) (insertRange eId fit . removeRange eId (fromJust oldFit))
+ src/Algorithm/EqSat/Queries.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE BangPatterns #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.EqSat.Queries+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability :+--+-- Query functions for e-graphs+-- Heavily based on hegg (https://github.com/alt-romes/hegg by alt-romes)+--+-----------------------------------------------------------------------------++module Algorithm.EqSat.Queries where++import Algorithm.EqSat.Egraph+import qualified Data.IntMap as IntMap+import qualified Data.Map as Map+import qualified Data.HashSet as Set+import Control.Monad.State ( gets, modify' )+import Control.Monad ( filterM )+import Control.Lens ( over )+import Data.Maybe+import Data.Sequence ( Seq(..) )++import Debug.Trace++-- this is too slow for now, it needs a db of its own+-- basically a db for each query we need+getEClassesThat :: Monad m => (EClass -> Bool) -> EGraphST m [EClassId]+getEClassesThat p = do+ gets (map fst . filter (\(ecId, ec) -> p ec) . IntMap.toList . _eClass)+ --go ecs+ where+ go :: Monad m => [EClassId] -> EGraphST m [EClassId]+ go [] = pure []+ go (ecId:ecs) = do ec <- gets (p . (IntMap.! ecId) . _eClass)+ ecs' <- go ecs+ if ec+ then pure (ecId:ecs')+ else pure ecs'++updateFitness :: Monad m => Double -> EClassId -> EGraphST m ()+updateFitness f ecId = do+ ec <- gets ((IntMap.! ecId) . _eClass)+ let info = _info ec+ modify' $ over eClass (IntMap.insert ecId ec{_info=info{_fitness = Just f}})++-- | returns all the root e-classes (e-class without parents)+findRootClasses :: Monad m => EGraphST m [EClassId]+findRootClasses = gets (Prelude.map fst . Prelude.filter isParent . IntMap.toList . _eClass)+ where+ isParent (k, v) = Prelude.null (_parents v) || (k `Set.member` (Set.map fst (_parents v)))++-- | returns the e-class id with the best fitness that+-- is true to a predicate+getTopECLassThat :: Monad m => Int -> (EClass -> Bool) -> EGraphST m [EClassId]+getTopECLassThat n p = do+ gets (_fitRangeDB . _eDB)+ >>= go n []+ where+ go :: Monad m => Int -> [EClassId] -> RangeTree Double -> EGraphST m [EClassId]+ go 0 bests rt = pure bests+ go m bests rt = case rt of+ Empty -> pure bests+ t :|> y -> do let x = snd y+ ecId <- canonical x+ ec <- gets ((IntMap.! ecId) . _eClass)+ if (isInfinite . fromJust . _fitness . _info $ ec)+ then pure bests+ else if p ec+ then go (m-1) (x:bests) t+ else go m bests t+getTopECLassWithSize :: Monad m => Int -> Int -> EGraphST m [EClassId]+getTopECLassWithSize sz n = do+ gets ((IntMap.!? sz) . _sizeFitDB . _eDB)+ >>= go n []+ where+ go :: Monad m => Int -> [EClassId] -> Maybe (RangeTree Double) -> EGraphST m [EClassId]+ go _ bests Nothing = pure []+ go 0 bests (Just rt) = pure bests+ go m bests (Just rt) = case rt of+ Empty -> pure bests+ t :|> y -> do let x = snd y+ ecId <- canonical x+ ec <- gets ((IntMap.! ecId) . _eClass)+ if (isInfinite . fromJust . _fitness . _info $ ec)+ then pure bests+ else go (m-1) (x:bests) (Just t)
+ src/Algorithm/EqSat/Simplify.hs view
@@ -0,0 +1,214 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE LambdaCase #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.EqSat.Simplify+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability :+--+-- Module containing the algebraic rules and simplification function.+--+-----------------------------------------------------------------------------+module Algorithm.EqSat.Simplify ( Rule(..), simplifyEqSatDefault, applyMergeOnlyDftl, rewrites, rewriteBasic, rewritesFun ) where++import Algorithm.EqSat (eqSat, applySingleMergeOnlyEqSat)+import Algorithm.EqSat.Egraph+import Algorithm.EqSat.DB+ ( ClassOrVar,+ Pattern (Fixed, VarPat),+ Rule (..),+ getInt,+ )+import Control.Monad.State.Strict (evalState)+import Data.IntMap (IntMap)+import qualified Data.IntMap as IM+import Data.Map (Map)+import qualified Data.Map as Map+import Data.SRTree++type ConstrFun = Pattern -> Map ClassOrVar ClassOrVar -> EGraph -> Bool ++constrainOnVal :: (Consts -> Bool) -> Pattern -> Map ClassOrVar ClassOrVar -> EGraph -> Bool +constrainOnVal f (VarPat c) subst eg =+ let cid = getInt $ subst Map.! Right (fromEnum c)+ in f (_consts . _info $ _eClass eg IM.! cid)+constrainOnVal _ _ _ _ = False ++-- TODO: aux functions to avoid repeated pattern in constraint creation +--+-- check if a matched pattern contains constant +isConstPt :: ConstrFun+isConstPt = constrainOnVal $ + \case+ ConstVal _ -> True + _ -> False++-- check if the matched pattern is a positive constant +isConstPos :: ConstrFun+isConstPos = constrainOnVal $+ \case+ ConstVal x -> x > 0 + _ -> False++isNotParam :: ConstrFun+isNotParam = constrainOnVal $+ \case+ ParamIx _ -> False+ _ -> True++-- check if the matched pattern is nonzero+isNotZero :: ConstrFun+isNotZero = constrainOnVal $+ \case+ ConstVal x -> abs x < 1e-9+ _ -> True++-- check if the matched pattern is even +isEven :: ConstrFun+isEven = constrainOnVal $+ \case+ ConstVal x -> ceiling x == floor x && even (round x) + _ -> True++-- check if the matched pattern is integer+isInteger :: ConstrFun+isInteger = constrainOnVal $+ \case+ ConstVal x -> ceiling x == floor x+ _ -> True++-- check if the matched pattern is positive+isPositive :: ConstrFun+isPositive = constrainOnVal $+ \case+ ConstVal x -> x > 0+ _ -> True++-- check if the matched pattern is valid+isValid :: ConstrFun+isValid = constrainOnVal $+ \case+ ConstVal x -> not (isNaN x || isInfinite x)+ _ -> True++-- basic algebraic rules +rewriteBasic :: [Rule]+rewriteBasic =+ [+ "x" * "x" :=> "x" ** 2+ , "x" * "y" :=> "y" * "x"+ , "x" + "y" :=> "y" + "x"+ , ("x" ** "y") * "x" :=> "x" ** ("y" + 1) :| isConstPt "y"+ , ("x" ** "y") * ("x" ** "z") :=> "x" ** ("y" + "z") -- :| isPositive "x"+ , ("x" + "y") + "z" :=> "x" + ("y" + "z")+ --, ("x" + "y") - "z" :=> "x" + ("y" - "z") -- TODO: check that I don't need that+ , ("x" * "y") * "z" :=> "x" * ("y" * "z")+ , ("x" * "y") + ("x" * "z") :=> "x" * ("y" + "z")+ , "x" - ("y" + "z") :=> ("x" - "y") - "z" -- TODO: check that I don't this+ , "x" - ("y" - "z") :=> ("x" - "y") + "z" -- TODO+ , ("x" * "y") / "z" :=> ("x" / "z") * "y" :| isNotZero "z" -- TODO: inv(x) <=> x^-1 , x/y <=> x*y^-1+ , "x" * ("y" / "z") :=> ("x" / "z") * "y" :| isNotZero "z" -- ^+ , "x" / ("y" * "z") :=> ("x" / "z") / "y" :| isNotZero "z" -- ^ TODO: 0 ^-1 check+ , ("w" * "x") + ("z" * "x") :=> ("w" + "z") * "x" -- :| isConstPt "w" :| isConstPt "z"+ , ("w" * "x") - ("z" * "x") :=> ("w" - "z") * "x" -- TODO: handle sub :| isConstPt "w" :| isConstPt "z"+ , ("w" * "x") / ("z" * "y") :=> ("w" / "z") * ("x" / "y") -- TODO handle with power :| isConstPt "w" :| isConstPt "z" :| isNotZero "z"+ -- TODO: a + b*y :=> b * (a/b + y) :| isNotZero b+ , (("x" * "y") + ("z" * "w")) :=> "x" * ("y" + ("z" / "x") * "w") :| isConstPt "x" :| isConstPt "z" :| isNotZero "x"+ , (("x" * "y") - ("z" * "w")) :=> "x" * ("y" - ("z" / "x") * "w") :| isConstPt "x" :| isConstPt "z" :| isNotZero "x"+ , (("x" * "y") * ("z" * "w")) :=> ("x" * "z") * ("y" * "w") :| isConstPt "x" :| isConstPt "z"+ -- , "x" + "y" :=> "y" * ("x" * "y" ** (-1) + 1) :| isNotZero "y" -- GABRIEL + -- , "x" + "y" * "z" :=> "y" * ("x" * "y" ** (-1) + "z") :| isNotZero "y" -- GABRIEL + ]++-- rules for nonlinear functions +rewritesFun :: [Rule]+rewritesFun =+ [+ log (sqrt "x") :=> 0.5 * log "x" :| isNotParam "x"+ , log (exp "x") :==: exp (log "x")+ , log (exp "x") :=> "x"+ -- , exp (log "x") :=> "x" -- :| isPositive "x" ??? exp(log(x)), x, log(exp(0))+ , "x" ** (1/2) :==: sqrt "x" -- <==>+ , "x" ** (1/3) :==: Fixed (Uni Cbrt "x")+ , log ("x" * "y") :=> log "x" + log "y" :| isConstPos "x" :| isConstPos "y"+ -- , log ("x" / "y") :=> log "x" - log "y" :| isConstPos "x" :| isConstPos "y"+ , log ("x" ** "y") :=> "y" * log "x"+ --, sqrt ("x" ** "y") :=> "x" ** ("y" / 2) :| isEven "y"+ -- , sqrt ("y" * "x") :=> sqrt "y" * sqrt "x" --+ --, sqrt ("y" / "x") :=> sqrt "y" / sqrt "x"+ , abs ("x" * "y") :=> abs "x" * abs "y" -- :| isConstPt "x"+ , abs ("x" ** "y") :=> abs "x" ** "y"+ , abs ("x" - "y") :=> abs ("y" - "x")+ --, sqrt ("z" * ("x" - "y")) :=> sqrt (negate "z") * sqrt ("y" - "x")+ --, sqrt ("z" * ("x" + "y")) :=> sqrt "z" * sqrt ("x" + "y")+ , recip (recip "x") :=> "x" :| isNotZero "x"+ , ("x" * "y") ** "z" :==: ("x" ** "z") * ("y" ** "z") -- :| bothSameSign "x" "y"+ , ("x" * "y") ** "z" :==: ("x" ** "z") * ("y" ** "z") -- :| isInteger "z"+ --, recip "x" :==: "x" ** (-1) -- GABRIEL + --, "x" / "y" :==: "x" * "y" ** (-1) -- GABRIEL + , abs "x" ** "y" :=> "x" ** "y" :| isEven "y"+ ]++-- Rules that reduces redundant parameters+constReduction :: [Rule]+constReduction =+ [+ 0 + "x" :=> "x"+ -- , "x" - 0 :=> "x"+ , 1 * "x" :=> "x"+ , 0 * "x" :=> 0 :| isValid "x" -- :| isNotParam "x"+ -- , 0 / "x" :=> 0 :| isNotZero "x"+ --, "x" - "x" :=> 0 :| isNotParam "x"+ --, "x" / "x" :=> 1 :| isNotZero "x" :| isNotParam "x"+ , "x" ** 1 :=> "x"+ , 0 ** "x" :=> 0 :| isPositive "x"+ , 1 ** "x" :=> 1+ -- , "x" * (1 / "x") :=> 1 :| isNotParam "x" :| isNotZero "x"+ , 0 - "x" :=> negate "x"+ , "x" + negate "y" :==: "x" - "y"+ -- , negate ("x" * "y") :=> (negate "x") * "y" :| isConstPt "x"+ , "x" ** "y" * "x" :=> "x" ** ("y" + 1) :| isPositive "x"+ , "x" ** "y" * "x" ** "z" :==: "x" ** ("y" + "z") :| isPositive "x"+ , ("x" ** "y") ** "z" :==: "x" ** ("y" * "z") :| isPositive "x"+ , ("x" * "y") ** "z" :==: "x" ** "z" * "y" ** "z" :| isPositive "x" :| isPositive "y"++ , "x" ** "y" * "x" :=> "x" ** ("y" + 1) :| isInteger "y" :| isNotZero "x"+ , "x" ** "y" * "x" ** "z" :==: "x" ** ("y" + "z") :| isInteger "y" :| isInteger "z" :| isNotZero "x"+ , ("x" ** "y") ** "z" :==: "x" ** ("y" * "z") :| isInteger "y" :| isInteger "z" :| isNotZero "x"+ , ("x" * "y") ** "z" :==: "x" ** "z" * "y" ** "z" :| isInteger "z" :| isNotZero "x" :| isNotZero "y"++ ]++-- | default cost function for simplification+-- TODO:+-- num_params:+-- length:+-- terminal < nonterminal:+-- symbol comparison (constants, parameters, variables x0, x10, x2)+-- op priorities (+, -, *, inv_div, pow, abs, exp, log, log10, sqrt)+-- univariates+myCost :: SRTree Int -> Int+myCost (Var _) = 1+myCost (Const _) = 1+myCost (Param _) = 1+myCost (Bin op l r) = 2 + l + r+myCost (Uni _ t) = 3 + t++-- all rewrite rules+rewrites :: [Rule]+rewrites = rewriteBasic <> constReduction <> rewritesFun++-- | simplify using the default parameters +simplifyEqSatDefault :: Fix SRTree -> Fix SRTree+simplifyEqSatDefault t = eqSat t rewrites myCost 30 `evalState` emptyGraph++-- | simplifies with custom parameters+simplifyEqSat :: [Rule] -> CostFun -> Int -> Fix SRTree -> Fix SRTree+simplifyEqSat rwrts costFun it t = eqSat t rwrts costFun it `evalState` emptyGraph++-- | apply a single step of merge-only using default rules+applyMergeOnlyDftl :: Monad m => CostFun -> EGraphST m ()+applyMergeOnlyDftl costFun = applySingleMergeOnlyEqSat costFun rewrites
+ src/Algorithm/Massiv/Utils.hs view
@@ -0,0 +1,278 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleContexts #-}+module Algorithm.Massiv.Utils where++import Data.Massiv.Array hiding ( forM_, unzip, map, init, zipWith, zip, tail, replicate, take )+import qualified Data.Massiv.Array as A+import qualified Data.Massiv.Array.Unsafe as UMA+import qualified Data.Massiv.Array.Mutable as MMA+import Control.Monad+import Data.Vector.Storable ((//))+import System.IO.Unsafe++-- taken from https://hackage.haskell.org/package/cubicspline-0.1.2+import Control.Arrow+import Data.List(unfoldr)++import Data.SRTree.Eval++type MMassArray m = MMA.MArray (PrimState m) S Ix2 Double++getRows :: SRMatrix -> Array B Ix1 PVector+getRows = computeAs B . outerSlices+{-# INLINE getRows #-}+getCols :: SRMatrix -> Array B Ix1 PVector+getCols = computeAs B . A.map (computeAs S) . innerSlices+{-# INLINE getCols #-}++appendRow :: MonadThrow m => SRMatrix -> PVector -> m SRMatrix+appendRow xs v = computeAs S <$> (stackOuterSlicesM . toList . computeAs B $ snoc (outerSlices xs) v)+{-# INLINE appendRow #-}++appendCol :: MonadThrow m => SRMatrix -> PVector -> m SRMatrix+appendCol xs v = computeAs S <$> (stackInnerSlicesM . toList . computeAs B $ snoc (A.map (computeAs S) $ innerSlices xs) v)+{-# INLINE appendCol #-}++updateS :: Array S Ix1 Double -> [(Int, Double)] -> Array S Ix1 Double+updateS vec new = fromStorableVector compMode $ toStorableVector vec // new++linSpace :: Int -> (Double, Double) -> [Double]+linSpace num (lo, hi) = Prelude.take num $ iterate (\x -> x + step) lo+ where+ step = (hi - lo) / (fromIntegral num - 1)+{-# INLINE linSpace #-}++outer :: (MonadThrow m)+ => PVector+ -> PVector+ -> m SRMatrix+outer arr1 arr2+ | isEmpty arr1 || isEmpty arr2 = pure $ setComp comp empty+ | otherwise =+ pure $ makeArray comp (Sz2 m1 m2) $ \(i :. j) ->+ UMA.unsafeIndex arr1 i * UMA.unsafeIndex arr2 j+ where+ comp = getComp arr1 <> getComp arr2+ Sz1 m1 = size arr1+ Sz1 m2 = size arr2+{-# INLINE outer #-}++det :: SRMatrix -> Double +det mtx+ | m==0 || n==0 = 1+ | otherwise = (^2) $ Prelude.product [l ! (i :. i) | i <- [0 .. m-1]]+ where+ Sz (m :. n) = size mtx+ (l, _) = unsafePerformIO (lu mtx)+ +detChol :: SRMatrix -> Double+detChol mtx+ | m==0 || n==0 = 1+ | otherwise = (^2) $ Prelude.product [cho ! (i :. i) | i <- [0 .. m-1]]+ where+ Sz (m :. n) = size mtx+ cho = unsafePerformIO (cholesky mtx)+{-# INLINE det #-}++rangedLinearDotProd :: PrimMonad m => Int -> Int -> Int -> MMassArray m -> m Double+rangedLinearDotProd r1 r2 len arr = go 0 0+ where+ go !acc k+ | k < len = do x <- UMA.unsafeLinearRead arr (r1 + k)+ y <- UMA.unsafeLinearRead arr (r2 + k)+ go (acc + x*y) (k + 1)+ | otherwise = pure acc+{-# INLINE rangedLinearDotProd #-}++data NegDef = NegDef+ deriving Show++instance Exception NegDef++cholesky :: (PrimMonad m, MonadThrow m, MonadIO m)+ => SRMatrix+ -> m SRMatrix+cholesky arr+ | m /= n = throwM $ SizeMismatchException (size arr) (size arr)+ | isEmpty arr = pure $ setComp comp empty+ | otherwise = MMA.createArrayS_ (size arr) create+ where+ comp = getComp arr+ (Sz2 m n) = size arr+ create l = Prelude.mapM_ (update l) [i :. j | i <- [0..m-1], j <- [0..m-1]]++ update l ix@(i :. j)+ | i < j = UMA.unsafeWrite l ix 0+ | otherwise = do let cur = UMA.unsafeIndex arr ix+ rowI = i*m+ rowJ = j*m+ xjj <- UMA.unsafeLinearRead l (rowJ + j)+ tot <- rangedLinearDotProd rowI rowJ j l+ let delta = cur - tot+ if i == j+ then if delta <= 0+ then throwM NegDef -- SizeMismatchException (size arr) (size arr) -- look at a better exception+ else UMA.unsafeLinearWrite l (rowI + j) (sqrt delta)+ else UMA.unsafeLinearWrite l (rowI + j) (delta / xjj)+{-# INLINE cholesky #-}++invChol :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> m SRMatrix+invChol arr = do l <- cholesky arr -- lower diag+ mtx <- thawS l+ forM_ [0 .. m-1] $ \i -> do+ lII <- UMA.unsafeRead mtx (i :. i)+ UMA.unsafeWrite mtx (i :. i) (1 / lII)+ forM_ [0 .. i-1] $ \j -> do+ tot <- rangedLinearDotProd (i*m + j) (j*m + j) (i-j) mtx+ UMA.unsafeWrite mtx (j :. i) ((-tot)/lII)+ UMA.unsafeWrite mtx (i :. j) 0+ mm <- newMArray (Sz2 m m) 0+ forM_ [0 .. m-1] $ \i -> do+ dii <- rangedLinearDotProd (i*m + i) (i*m + i) (m - i) mtx+ UMA.unsafeWrite mm (i :. i) dii+ forM_ [i+1 .. m-1] $ \j -> do+ dij <- rangedLinearDotProd (i*m + j) (j*m + j) (m - j) mtx+ UMA.unsafeWrite mm (i :. j) dij+ UMA.unsafeWrite mm (j :. i) dij+ freezeS mm++ where+ Sz2 m _ = size arr+{-# INLINE invChol #-}++-- LU decomposition and solver taken from https://hackage.haskell.org/package/linear-1.23/docs/src/Linear.Matrix.html+lu :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> m (SRMatrix, SRMatrix)+lu mtx = do+ let (Sz2 m n) = size mtx+ u <- thawS $ computeAs S $ identityMatrix (Sz m)+ l <- thawS $ A.replicate compMode (Sz2 m n) 0++ let buildLVal !i !j = do+ let go !k !s+ | k == j = pure s+ | otherwise = do lik <- UMA.unsafeRead l (i :. k)+ ukj <- UMA.unsafeRead u (k :. j)+ go (k+1) ( s + (lik * ukj) )+ s' <- go 0 0+ UMA.unsafeWrite l (i :. j) ((mtx ! (i :. j)) - s')+ -- pure l+ buildL !i !j+ = when (i /= n) $ do buildLVal i j+ buildL (i+1) j+ buildUVal !i !j = do+ let go !k !s+ | k == j = pure s+ | otherwise = do ljk <- UMA.unsafeRead l (j :. k)+ uki <- UMA.unsafeRead u (k :. i)+ go (k+1) (s + ljk * uki)++ s' <- go 0 0+ ljj <- UMA.unsafeRead l (j :. j)+ UMA.unsafeWrite u (j :. i) (((mtx ! (j :. i)) - s') / (ljj))+ -- pure u++ buildU !i !j+ = when (i /= n) $ do buildUVal i j+ buildU (i+1) j+ buildLU !j+ = when (j /= n) $+ do buildL j j+ buildU j j+ buildLU (j+1)+ buildLU 0+ finalL <- freezeS l+ finalU <- freezeS u+ pure (finalL, finalU)++forwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector+forwardSub a b = do+ let (Sz m) = size b+ x <- thawS $ A.replicate compMode (Sz1 m) 0+ let coeff !i !j !s+ | j == i = pure s+ | otherwise = do let aij = a ! (i :. j)+ xj <- UMA.unsafeRead x j+ coeff i (j+1) (s + aij * xj)+ go !i = when (i/= m) $+ do let bi = b ! i+ aii = a ! (i :. i)+ c <- coeff i 0 0+ UMA.unsafeWrite x i ((bi - c)/aii)+ go (i+1)+ go 0+ freezeS x++backwardSub :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector+backwardSub a b = do+ let (Sz m) = size b+ x <- thawS $ A.replicate compMode (Sz1 m) 0+ let coeff !i !j !s+ | j == m = pure s+ | otherwise = do let aij = a ! (i :. j)+ xj <- UMA.unsafeRead x j+ coeff i (j+1) (s + aij * xj)+ go !i = when (i >= 0) $+ do let bi = b ! i+ aii = a ! (i :. i)+ c <- coeff i (i+1) 0+ UMA.unsafeWrite x i ((bi - c)/aii)+ go (i-1)+ go (m-1)+ freezeS x++luSolve :: (PrimMonad m, MonadThrow m, MonadIO m) => SRMatrix -> PVector -> m PVector+luSolve a b = do (l, u) <- lu a+ forwardSub l b >>= backwardSub u++type PolyCos = (Double, Double, Double)++-- | Given a list of (x,y) co-ordinates, produces a list of coefficients to cubic equations, with knots at each of the initially provided x co-ordinates. Natural cubic spline interpololation is used. See: <http://en.wikipedia.org/wiki/Spline_interpolation#Interpolation_using_natural_cubic_spline>.+cubicSplineCoefficients :: [(Double, Double)] -> [PolyCos]+cubicSplineCoefficients xs = Prelude.zip3 x y z'+ where+ x = map fst xs+ y = map snd xs+ xdiff = zipWith (-) (tail x) x+ xdiff' = fromList compMode xdiff :: Vector S Double+ dydx :: Vector S Double+ dydx = fromList compMode $ Prelude.zipWith3 (\y0 y1 xd -> (y0-y1)/xd) (tail y) y xdiff+ n = length x++ w :: [Double]+ w = 0 : nextW 1 w+ where+ nextW ix (wi : t)+ | ix == n-1 = []+ | otherwise = let m = (xdiff' ! (ix-1)) * (2 - wi) + 2 * (xdiff' ! ix)+ wn = (xdiff' ! ix) / m+ in wn : nextW (ix+1) t+ z :: [Double]+ z = 0 : nextZ 1 z+ where+ nextZ ix (zi : t)+ | ix == n-1 = [0]+ | otherwise = let m = (xdiff' ! (ix-1)) * (2 - (w !! (ix-1))) + 2 * (xdiff' ! ix)+ zn = (6*((dydx ! ix) - (dydx ! (ix-1))) - (xdiff' ! (ix-1)) * zi) / m+ in zn : nextZ (ix+1) t++ z' :: [Double]+ z' = Prelude.reverse $ 0 : [z !! i - w !! i * z !! (i+1) | i <- [n-2,n-3 .. 0]]++chunkBy :: Int -> [t] -> [[t]]+chunkBy n = unfoldr go+ where go [] = Nothing+ go x = Just $ splitAt n x++genSplineFun :: [(Double, Double)] -> Double -> Double+genSplineFun pts x = go xs $ zip coefs (tail coefs)+ where+ xs = map fst pts+ coefs = cubicSplineCoefficients pts+ evalAt (a1,b1,c1) (a2,b2,c2) y = let hi1 = a2 - a1+ in c1/(6*hi1)*(a2-y)^3 + c2/(6*hi1)*(y-a1)^3 + (b2/hi1 - c2*hi1/6)*(y-a1) + (b1/hi1 - c1*hi1/6)*(a2-y)++ go [x1,x2] [(c1,c2)] = evalAt c1 c2 x+ go (x1:x2:xs) ((c1,c2):cs)+ | x < x1 = evalAt c1 c2 x+ | x >= x1 && x <= x2 = evalAt c1 c2 x+ | otherwise = go (x2:xs) cs
+ src/Algorithm/SRTree/AD.hs view
@@ -0,0 +1,323 @@+{-# language FlexibleInstances, DeriveFunctor #-}+{-# language ScopedTypeVariables #-}+{-# language RankNTypes #-}+{-# language ViewPatterns #-}+{-# language FlexibleContexts #-}+{-# language BangPatterns #-}+-----------------------------------------------------------------------------+-- |+-- Module : Data.SRTree.AD +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : FlexibleInstances, DeriveFunctor, ScopedTypeVariables+--+-- Automatic Differentiation for Expression trees+--+-----------------------------------------------------------------------------++module Algorithm.SRTree.AD+ ( forwardMode+ , forwardModeUnique+ , reverseModeUnique+ , forwardModeUniqueJac+ ) where++import Control.Monad (forM_)+import Control.Monad.ST ( runST )+import Data.Bifunctor (bimap, first, second)+import qualified Data.DList as DL+import Data.Massiv.Array hiding (forM_, map, replicate, zipWith)+import qualified Data.Massiv.Array as M+import qualified Data.Massiv.Array.Unsafe as UMA+import Data.Massiv.Core.Operations (unsafeLiftArray)+import Data.SRTree.Derivative ( derivative )+import Data.SRTree.Eval+ ( SRVector, evalFun, evalOp, SRMatrix, PVector, replicateAs )+import Data.SRTree.Internal+import Data.SRTree.Print (showExpr)+import Data.SRTree.Recursion ( cataM, cata, accu )+import qualified Data.Vector as V+import Debug.Trace (trace, traceShow)+import GHC.IO (unsafePerformIO)++applyUni :: (Index ix, Source r e, Floating e, Floating b) => Function -> Either (Array r ix e) b -> Either (Array D ix e) b+applyUni f (Left t) =+ Left $ M.map (evalFun f) t+applyUni f (Right t) =+ Right $ evalFun f t+{-# INLINE applyUni #-}++applyDer :: (Index ix, Source r e, Floating e, Floating b) => Function -> Either (Array r ix e) b -> Either (Array D ix e) b+applyDer f (Left t) =+ Left $ M.map (derivative f) t+applyDer f (Right t) =+ Right $ derivative f t+{-# INLINE applyDer #-}++negate' :: (Index ix, Source r e, Num e, Num b) => Either (Array r ix e) b -> Either (Array D ix e) b+negate' (Left t) = Left $ M.map negate t+negate' (Right t) = Right $ negate t+{-# INLINE negate' #-}++applyBin :: (Index ix, Floating b) => Op -> Either (Array D ix b) b -> Either (Array D ix b) b -> Either (Array D ix b) b+applyBin op (Left ly) (Left ry) =+ Left $ case op of+ Add -> ly !+! ry+ Sub -> ly !-! ry+ Mul -> ly !*! ry+ Div -> ly !/! ry+ Power -> ly .** ry+ PowerAbs -> M.map abs (ly .** ry)+ AQ -> ly !/! (M.map sqrt (M.map (+1) (ry !*! ry)))++applyBin op (Left ly) (Right ry) =+ Left $ unsafeLiftArray (\ x -> evalOp op x ry) ly+applyBin op (Right ly) (Left ry) =+ Left $ unsafeLiftArray (\ x -> evalOp op ly x) ry+applyBin op (Right ly) (Right ry) =+ Right $ evalOp op ly ry+{-# INLINE applyBin #-}++-- | get the value of a certain index if it is an array (Left) +-- or returns the value itself if it is a scalar.+(!??) :: (Manifest r e, Index ix) => Either (Array r ix e) e -> ix -> e+(Left y) !?? ix = y ! ix+(Right y) !?? ix = y+{-# INLINE (!??) #-}++-- | Calculates the results of the error vector multiplied by the Jacobian of an expression using forward mode+-- provided a vector of variable values `xss`, a vector of parameter values `theta` and+-- a function that changes a Double value to the type of the variable values.+-- uses unsafe operations to use mutable array instead of a tape+forwardMode :: Array S Ix2 Double -> Array S Ix1 Double -> SRVector -> Fix SRTree -> (Array D Ix1 Double, Array S Ix1 Double)+forwardMode xss theta err tree = let (yhat, jacob) = runST $ cataM lToR alg tree+ in (fromEither yhat, computeAs S err ><! jacob)+ where + (Sz p) = M.size theta+ (Sz (m :. n)) = M.size xss+ cmp = getComp xss+ -- | if the tree does not use a variable + -- it will return a single scalar, fromEither fixes this+ fromEither (Left y) = y+ fromEither (Right y) = M.replicate cmp (Sz m) y++ -- if it is a variable, returns the value of that variable and an array of zeros (Jacobian)+ alg (Var ix) = do tape <- M.newMArray (Sz2 m p) 0 + >>= UMA.unsafeFreeze cmp+ pure (Left (xss <! ix), tape)++ -- if it is a constant, returns the value of the constant and array of zeros + alg (Const c) = do tape <- M.newMArray (Sz2 m p) 0+ >>= UMA.unsafeFreeze cmp+ pure (Right c, tape)++ -- if it is a parameter, returns the value of the parameter and the jacobian with a one in the corresponding column+ alg (Param ix) = do tape <- M.makeMArrayS (Sz2 m p) (\(i :. j) -> pure $ if j==ix then 1 else 0)+ >>= UMA.unsafeFreeze cmp+ pure (Right (theta ! ix), tape)++ -- 1. applies the derivative of f in the evaluated child + -- 2. replaces the value of the Jacobian at (i, j) with yi * J[i, j]+ alg (Uni f (t, tape')) = do let y = computeAs S . fromEither $ applyDer f t+ tape <- UMA.unsafeThaw tape'+ forM_ [0 .. m-1] $ \i -> do+ let yi = y ! i+ forM_ [0 .. p-1] $ \j -> do+ v <- UMA.unsafeRead tape (i :. j)+ UMA.unsafeWrite tape (i :. j) (yi * v)+ tapeF <- UMA.unsafeFreeze cmp tape+ pure (applyUni f t, tapeF)+ -- li, ri are the corresponding values of the evaluated left and right children + -- vl, vr are the corresponding value of the Jacobian at (i, j) + -- applies the corresponding derivative of each binary operator + alg (Bin op (l, tl') (r, tr')) = do+ tl <- UMA.unsafeThaw tl'+ tr <- UMA.unsafeThaw tr'+ let l' = case l of+ Left y -> Left $ computeAs S y+ Right v -> Right v+ r' = case r of+ Left y -> Left $ computeAs S y+ Right v -> Right v+ forM_ [0 .. m-1] $ \i -> do + let li = l' !?? i+ ri = r' !?? i+ forM_ [0 .. p-1] $ \j -> do + vl <- UMA.unsafeRead tl (i :. j)+ vr <- UMA.unsafeRead tr (i :. j)+ UMA.unsafeWrite tl (i :. j) $ case op of+ Add -> (vl+vr)+ Sub -> (vl-vr)+ Mul -> (vl * ri + vr * li)+ Div -> ((vl * ri - vr * li) / ri^2)+ Power -> (li ** (ri - 1) * (ri * vl + li * log li * vr))+ PowerAbs -> (abs li ** ri) * (vr * log (abs li) + ri * vl / li)+ AQ -> ((1 + ri*ri) * vl - li * ri * vr) / (1 + ri*ri) ** 1.5+ tlF <- UMA.unsafeFreeze cmp tl+ pure (applyBin op l r, tlF)+++ lToR (Var ix) = pure (Var ix)+ lToR (Param ix) = pure (Param ix)+ lToR (Const c) = pure (Const c)+ lToR (Uni f mt) = Uni f <$> mt+ lToR (Bin op ml mr) = Bin op <$> ml <*> mr++-- | The function `forwardModeUnique` calculates the numerical gradient of the tree and evaluates the tree at the same time. It assumes that each parameter has a unique occurrence in the expression. This should be significantly faster than `forwardMode`.+forwardModeUnique :: SRMatrix -> PVector -> SRVector -> Fix SRTree -> (SRVector, Array S Ix1 Double)+forwardModeUnique xss theta err = second (toGrad . DL.toList) . cata alg+ where+ (Sz n) = M.size theta+ one = replicateAs xss 1+ toGrad grad = M.fromList (getComp xss) [g !.! err | g <- grad]++ alg (Var ix) = (xss <! ix, DL.empty)+ alg (Param ix) = (replicateAs xss $ theta ! ix, DL.singleton one)+ alg (Const c) = (replicateAs xss c, DL.empty)+ alg (Uni f (v, gs)) = let v' = evalFun f v+ dv = derivative f v+ in (v', DL.map (*dv) gs)+ alg (Bin Add (v1, l) (v2, r)) = (v1+v2, DL.append l r)+ alg (Bin Sub (v1, l) (v2, r)) = (v1-v2, DL.append l (DL.map negate r))+ alg (Bin Mul (v1, l) (v2, r)) = (v1*v2, DL.append (DL.map (*v2) l) (DL.map (*v1) r))+ alg (Bin Div (v1, l) (v2, r)) = let dv = ((-v1)/(v2*v2)) + in (v1/v2, DL.append (DL.map (/v2) l) (DL.map (*dv) r))+ alg (Bin Power (v1, l) (v2, r)) = let dv1 = v1 ** (v2 - one)+ dv2 = v1 * log v1+ in (v1 ** v2, DL.map (*dv1) (DL.append (DL.map (*v2) l) (DL.map (*dv2) r)))+ alg (Bin PowerAbs (v1, l) (v2, r)) = let dv1 = abs v1 ** v2+ dv2 = DL.map (* (log (abs v1))) r+ dv3 = DL.map (*(v2 / v1)) l+ in (abs v1 ** v2, DL.map (*dv1) (DL.append dv2 dv3))+ alg (Bin AQ (v1, l) (v2, r)) = let dv1 = DL.map (*(1 + v2*v2)) l+ dv2 = DL.map (*(-v1*v2)) r+ in (v1/sqrt(1 + v2*v2), DL.map (/(1 + v2*v2)**1.5) $ DL.append dv1 dv2)++data TupleF a b = Single a | T a b | Branch a b b deriving Functor -- hi, I'm a tree+type Tuple a = Fix (TupleF a)++-- | Same as above, but using reverse mode, that is even faster.+reverseModeUnique :: SRMatrix+ -> PVector+ -> SRVector+ -> (SRVector -> SRVector)+ -> Fix SRTree+ -> (Array D Ix1 Double, Array S Ix1 Double)+reverseModeUnique xss theta ys f t = unsafePerformIO $+ do jacob <- M.newMArray (Sz p) 0+ let !_ = accu reverse (combine jacob) t ((Right 1), fwdMode)+ j <- freezeS jacob+ pure (v, j)+ where+ fwdMode = cata forward t+ v = fromEither $ getTop fwdMode+ err = f v - ys+ (Sz2 m _) = M.size xss+ p = countParams t+ fromEither (Left x) = x+ fromEither (Right x) = M.replicate (getComp xss) (Sz1 m) x++ oneTpl x = Fix $ Single x+ tuple x y = Fix $ T x y+ branch x y z = Fix $ Branch x y z++ getTop (Fix (Single x)) = x+ getTop (Fix (T x y)) = x+ getTop (Fix (Branch x y z)) = x++ unCons (Fix (T x y)) = y+ getBranches (Fix (Branch x y z)) = (y,z)++ -- forward just creates a new tree with the partial+ -- evaluation of the nodes+ forward (Var ix) = oneTpl (Left $ xss <! ix)+ forward (Param ix) = oneTpl (Right $ theta ! ix)+ forward (Const c) = oneTpl (Right c)+ forward (Uni g t) = let v = getTop t+ in tuple (applyUni g v) t+ forward (Bin op l r) = let vl = getTop l+ vr = getTop r+ in branch (applyBin op vl vr) l r++++ -- reverse walks from the root to the leaf calculating the+ -- partial derivative with respect to an arbitrary variable+ -- up to that point+ reverse (Var ix) (dx, _) = Var ix+ reverse (Param ix) (dx, _) = Param ix+ reverse (Const v) (dx, _) = Const v+ reverse (Uni f t) (dx, unCons -> v) =+ let g' = applyDer f (getTop v)+ in Uni f (t, ( applyBin Mul dx g', v ))+ reverse (Bin op l r) (dx, getBranches -> (vl, vr)) =+ let (dxl, dxr) = diff op dx (getTop vl) (getTop vr)+ in Bin op (l, (dxl, vl)) (r, (dxr, vr))++ -- dx is the current derivative so far+ -- fx is the evaluation of the left branch+ -- gx is the evaluation of the right branch+ --+ -- this should return a tuple, where the left element is+ -- dx * d op(f(x), g(x)) / d f(x) and+ -- the right branch dx * d op (f(x), g(x)) / d g(x)+ diff Add dx fx gy = (dx, dx)+ diff Sub dx fx gy = (dx, negate' dx)+ diff Mul dx fx gy = (applyBin Mul dx gy, applyBin Mul dx fx)+ diff Div dx fx gy = (applyBin Div dx gy, applyBin Mul dx (applyBin Div (negate' fx) (applyBin Mul gy gy)))+ diff Power dx fx gy = let dxl = applyBin Mul dx (applyBin Power fx (applyBin Sub gy (Right 1)))+ dv2 = applyBin Mul fx (applyUni Log fx)+ in (applyBin Mul dxl gy, applyBin Mul dxl dv2)+ diff PowerAbs dx fx gy = let dxl = applyBin Mul (applyBin Mul gy fx) (applyBin PowerAbs fx (applyBin Sub gy (Right 2)))+ dxr = applyBin Mul (applyUni LogAbs fx) (applyBin PowerAbs fx gy)+ in (applyBin Mul dxl dx, applyBin Mul dxr dx)+ diff AQ dx fx gy = let dxl = applyUni Recip (applyUni Sqrt (applyBin Add (applyUni Square gy) (Right 1)))+ dxy = applyBin Div (applyBin Mul fx gy) (applyUni Cube (applyUni Sqrt (applyBin Add (applyUni Square gy) (Right 1))))+ in (applyBin Mul dxl dx, applyBin Mul dxy dx)+++ -- once we reach a leaf with a parameter, we return a singleton+ -- with that derivative upwards until the root+ --combine :: (forall s . MArray (PrimState (ST s)) S Int Double) -> SRTree () -> (Either SRVector Double, a) -> ()+ combine j (Var ix) s = 0+ combine j (Const _) s = 0+ combine j (Param ix) s = unsafePerformIO $ do+ case fst s of+ Left v -> do v' <- dotM v err+ UMA.unsafeWrite j ix v'+ Right v -> UMA.unsafeWrite j ix $ M.foldrS (\x acc -> x*v + acc) 0 err+ UMA.unsafeRead j ix+ combine j (Uni f gs) s = gs+ combine j (Bin op l r) s = l+r+++-- | The function `forwardModeUnique` calculates the numerical gradient of the tree and evaluates the tree at the same time. It assumes that each parameter has a unique occurrence in the expression. This should be significantly faster than `forwardMode`.+forwardModeUniqueJac :: SRMatrix -> PVector -> Fix SRTree -> [PVector]+forwardModeUniqueJac xss theta = snd . second (map (M.computeAs M.S) . DL.toList) . cata alg+ where+ (Sz n) = M.size theta+ one = replicateAs xss 1++ alg (Var ix) = (xss <! ix, DL.empty)+ alg (Param ix) = (replicateAs xss $ theta ! ix, DL.singleton one)+ alg (Const c) = (replicateAs xss c, DL.empty)+ alg (Uni f (v, gs)) = let v' = evalFun f v+ dv = derivative f v+ in (v', DL.map (*dv) gs)+ alg (Bin Add (v1, l) (v2, r)) = (v1+v2, DL.append l r)+ alg (Bin Sub (v1, l) (v2, r)) = (v1-v2, DL.append l (DL.map negate r))+ alg (Bin Mul (v1, l) (v2, r)) = (v1*v2, DL.append (DL.map (*v2) l) (DL.map (*v1) r))+ alg (Bin Div (v1, l) (v2, r)) = let dv = ((-v1)/(v2*v2))+ in (v1/v2, DL.append (DL.map (/v2) l) (DL.map (*dv) r))+ alg (Bin Power (v1, l) (v2, r)) = let dv1 = v1 ** (v2 - one)+ dv2 = v1 * log v1+ in (v1 ** v2, DL.map (*dv1) (DL.append (DL.map (*v2) l) (DL.map (*dv2) r)))+ alg (Bin PowerAbs (v1, l) (v2, r)) = let dv1 = abs v1 ** v2+ dv2 = DL.map (* (log (abs v1))) r+ dv3 = DL.map (*(v2 / v1)) l+ in (abs v1 ** v2, DL.map (*dv1) (DL.append dv2 dv3))+ alg (Bin AQ (v1, l) (v2, r)) = let dv1 = DL.map (*(1 + v2*v2)) l+ dv2 = DL.map (*(-v1*v2)) r+ in (v1/sqrt(1 + v2*v2), DL.map (/(1 + v2*v2)**1.5) $ DL.append dv1 dv2)
+ src/Algorithm/SRTree/ConfidenceIntervals.hs view
@@ -0,0 +1,447 @@+{-# language ViewPatterns, ScopedTypeVariables, MultiWayIf, FlexibleContexts #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.SRTree.ConfidenceIntervals +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : ConstraintKinds+--+-- Functions to optimize the parameters of an expression.+--+-----------------------------------------------------------------------------+module Algorithm.SRTree.ConfidenceIntervals where++import qualified Data.Massiv.Array as A+import Data.Massiv.Array (Ix2(..), (*.), (!+!), (!*!))+import Data.Massiv.Array.Numeric ( identityMatrix )+import Statistics.Distribution ( ContDistr(quantile) )+import Statistics.Distribution.StudentT ( studentT )+import Statistics.Distribution.FDistribution ( fDistribution )+import qualified Data.Vector.Storable as VS+import Data.SRTree+import Data.SRTree.Eval+import Data.SRTree.Recursion ( cata )+import Algorithm.SRTree.Likelihoods+import Algorithm.SRTree.Opt+ ( minimizeNLLNonUnique, minimizeNLLWithFixedParam )+import Data.List ( sortOn, nubBy )+import Data.Maybe ( fromMaybe )+import Algorithm.SRTree.NonlinearOpt+import Algorithm.Massiv.Utils+import System.IO.Unsafe ( unsafePerformIO )+import Control.Monad.Catch ( catch )++import Debug.Trace ( trace, traceShow )++-- | profile likelihood algorithms: Bates (classical), ODE (faster), Constrained (fastest)+-- The Constrained approach returns only the endpoints.+data PType = Bates | ODE | Constrained deriving (Show, Read)++-- | Confidence Interval using Laplace approximation or profile likelihood.+data CIType = Laplace BasicStats | Profile BasicStats [ProfileT]++-- | Basic stats of the data: covariance of parameters, correlation, standard errors +data BasicStats = MkStats { _cov :: SRMatrix+ , _corr :: SRMatrix+ , _stdErr :: PVector+ } deriving (Eq, Show)++-- | a confience interval is composed of the point estimate (`est_`), lower bound (`_lower_`)+-- and upper bound (`upper_`)+data CI = CI { est_ :: Double+ , lower_ :: Double+ , upper_ :: Double+ } deriving (Eq, Show, Read)++-- | A profile likelihood is composed of a vector of tau values that traces the likelihood, +-- the matrix of thetas for each profile, the local optima, and two splines that converts +-- taus to theta and vice-versa. +data ProfileT = ProfileT { _taus :: PVector+ , _thetas :: SRMatrix+ , _opt :: Double+ , _tau2theta :: Double -> Double+ , _theta2tau :: Double -> Double+ }++-- shows the CI with n places +showCI :: Int -> CI -> String+showCI n (CI x l h) = show (rnd l) <> " <= " <> show (rnd x) <> " <= " <> show (rnd h)+ where+ rnd = (/10^n) . (fromIntegral . round) . (*10^n)+printCI :: Int -> CI -> IO ()+printCI n = putStrLn . showCI n++-- | Calculates the confidence interval of the parameters using +-- Laplace approximation or Profile likelihood+paramCI :: CIType -> Int -> PVector -> Double -> [CI]+paramCI (Laplace stats) nSamples theta alpha = zipWith3 CI (A.toList theta) lows highs+ where+ -- the Laplace approximation is theta +/- t(1-alpha/2) * standard error + (A.Sz k) = A.size theta+ t = quantile (studentT . fromIntegral $ nSamples - k) (1 - alpha / 2.0)+ stdErr = _stdErr stats+ lows = A.toList $ A.zipWith (-) theta $ A.map (*t) stdErr+ highs = A.toList $ A.zipWith (+) theta $ A.map (*t) stdErr++paramCI (Profile stats profiles) nSamples _ alpha = zipWith3 CI theta lows highs+ where+ -- for the profile likelihood we use the square root of the F-distribution with (1-alpha)+ k = length theta+ t = sqrt $ quantile (fDistribution k (fromIntegral $ nSamples - k)) (1 - alpha)+ stdErr = _stdErr stats+ lows = map (`_tau2theta` (-t)) profiles+ highs = map (`_tau2theta` t) profiles+ theta = map _opt profiles++-- | calculates the prediction confidence interval using Laplace approximation or profile likelihood. +--+predictionCI :: CIType -> Distribution -> (SRMatrix -> PVector) -> (SRMatrix -> [PVector]) -> (CI -> PVector -> Fix SRTree -> (Double -> Double, Double)) -> SRMatrix -> Fix SRTree -> PVector -> Double -> [CI] -> [CI]+predictionCI (Laplace stats) _ predFun jacFun _ xss tree theta alpha _ = zipWith3 CI yhat lows highs+ where+ yhat = A.toList $ predFun xss+ jac' :: A.Matrix A.S Double+ jac' = A.fromLists' compMode $ map A.toList $ jacFun xss+ jac :: [PVector]+ jac = A.toList $ A.outerSlices $ A.computeAs A.S $ A.transpose jac'+ n = length yhat+ (A.Sz k) = A.size theta+ t = quantile (studentT . fromIntegral $ n - k) (1 - alpha / 2.0)+ covs = A.toList $ A.outerSlices $ _cov stats+ lows = zipWith (-) yhat $ map (*t) resStdErr+ highs = zipWith (+) yhat $ map (*t) resStdErr++ getResStdError row = sqrt $ (A.!.!) row $ A.fromList compMode $ map (row A.!.!) covs+ resStdErr = map getResStdError jac++predictionCI (Profile _ _) dist predFun _ profFun xss tree theta alpha estPIs = zipWith3 f estPIs yhat $ take 10 xss'+ where+ yhat = A.toList $ predFun xss+ theta' = A.toStorableVector theta++ t = sqrt $ quantile (fDistribution k (fromIntegral $ n - k)) (1 - alpha)+ (A.Sz k) = A.size theta+ n = length yhat++ theta0 = calcTheta0 dist tree+ xss' = A.toList $ A.outerSlices xss++ f estPI yh xs =+ let t' = replaceParam0 tree $ evalVar xs theta0+ (spline, yh') = profFun estPI (A.fromStorableVector compMode (theta' VS.// [(0, yh)])) t'+ in CI yh' (spline (-t)) (spline t)++-- inverse function of the distributions +inverseDist :: Floating p => Distribution -> p -> p+inverseDist Gaussian y = y+inverseDist Bernoulli y = log (y/(1-y))+inverseDist Poisson y = log y++-- rewrite the tree by fixing theta 0 to optimal value +replaceParam0 :: Fix SRTree -> Fix SRTree -> Fix SRTree+replaceParam0 tree t0 = cata alg tree+ where+ alg (Var ix) = Fix $ Var ix+ alg (Param 0) = t0+ alg (Param ix) = Fix $ Param ix+ alg (Const c) = Fix $ Const c+ alg (Uni g t) = Fix $ Uni g t+ alg (Bin op l r) = Fix $ Bin op l r++evalVar :: PVector -> Fix SRTree -> Fix SRTree+evalVar xs = cata alg+ where+ alg (Var ix) = Fix $ Const (xs A.! ix)+ alg (Param ix) = Fix $ Param ix+ alg (Const c) = Fix $ Const c+ alg (Uni g t) = Fix $ Uni g t+ alg (Bin op l r) = Fix $ Bin op l r++calcTheta0 :: Distribution -> Fix SRTree -> Fix SRTree+calcTheta0 dist tree = case cata alg tree of+ Left g -> g $ inverseDist dist (Fix $ Param 0)+ Right _ -> error "No theta0?"+ where+ alg (Var ix) = Right $ Fix $ Var ix+ alg (Param 0) = Left id+ alg (Param ix) = Right $ Fix $ Param ix+ alg (Const c) = Right $ Fix $ Const c+ alg (Uni g t) = case t of+ Left f -> Left $ f . evalInverse g+ Right v -> Right $ evalFun g v+ alg (Bin op l r) = case l of+ Left f -> case r of+ Left _ -> error "This shouldn't happen!"+ Right v -> Left $ f . invright op v+ Right vl -> case r of+ Left g -> Left $ g . invleft op vl+ Right vr -> Right $ evalOp op vl vr++-- calculate the profile likelihood of every parameter +getAllProfiles :: PType -> Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> PVector -> [CI] -> Double -> [ProfileT]+getAllProfiles ptype dist mSErr xss ys tree theta stdErr estCIs alpha = reverse (getAll 0 [])+ where+ (A.Sz k) = A.size theta+ (A.Sz n) = A.size ys+ tau_max = sqrt $ quantile (fDistribution k (n - k)) (1 - 0.01)+ tau_max' = sqrt $ quantile (fDistribution k (n - k)) (1 - alpha)++ profFun ix = case ptype of+ Bates -> getProfile dist mSErr xss ys tree theta (stdErr A.! ix) tau_max ix+ ODE -> getProfileODE dist mSErr xss ys tree theta (stdErr A.! ix) (estCIs !! ix) tau_max ix+ Constrained -> getProfileCnstr dist mSErr xss ys tree theta (stdErr A.! ix) tau_max' ix++ getAll ix acc | ix == k = acc+ | otherwise = case profFun ix of+ Left t -> getAllProfiles ptype dist mSErr xss ys tree t stdErr estCIs alpha+ Right p -> getAll (ix + 1) (p : acc)++-- calculates the profile likelihood of a single parameter +getProfile :: Distribution+ -> Maybe Double+ -> SRMatrix+ -> PVector+ -> Fix SRTree+ -> PVector+ -> Double+ -> Double+ -> Int+ -> Either PVector ProfileT+getProfile dist mSErr xss ys tree theta stdErr_i tau_max ix+ | stdErr_i == 0.0 = pure $ ProfileT (A.fromList compMode [-tau_max, tau_max]) (A.fromLists' compMode [theta', theta']) (theta A.! ix) (const (theta A.! ix)) (const tau_max)+ | otherwise =+ do negDelta <- go kmax (-stdErr_i / 8) 0 1 mempty+ posDelta <- go kmax (stdErr_i / 8) 0 1 p0+ let (A.fromList compMode -> taus, A.fromLists' compMode. map A.toList -> thetas) = negDelta <> posDelta+ (tau2theta, theta2tau) = createSplines taus thetas stdErr_i tau_max ix+ pure $ ProfileT taus thetas optTh tau2theta theta2tau+ where+ theta' = A.toList theta+ p0 = ([0], [theta_opt])+ kmax = 300+ nll_opt = nll dist mSErr xss ys tree theta_opt+ theta_opt = fst $ minimizeNLLNonUnique dist mSErr 100 xss ys tree theta+ optTh = theta_opt A.! ix+ minimizer = minimizeNLLWithFixedParam dist mSErr 100 xss ys tree ix++ -- after k iterations, interpolates to the endpoint+ go 0 delta _ _ acc = Right acc+ go k delta t inv_slope acc@(taus, thetas)+ | isNaN inv_slope = Right acc -- stop since we cannot move forward on discontinuity+ | nll_cond < nll_opt = Left theta_t -- found a better optima+ | abs tau > tau_max = Right acc' -- we reached the endpoint+ | otherwise = go (k-1) delta (t + inv_slope) inv_slope' acc'+ where+ t_delta = (theta_opt A.! ix) + delta * (t + inv_slope)+ theta_delta = updateS theta_opt [(ix, t_delta)]+ theta_t = minimizer theta_delta+ zv = A.computeAs A.S (snd $ gradNLL dist mSErr xss ys tree theta_t) A.! ix+ zvs = snd $ gradNLL dist mSErr xss ys tree theta_t+ inv_slope' = min 4.0 . max 0.0625 . abs $ (tau / (stdErr_i * zv))+ nll_cond = nll dist mSErr xss ys tree theta_t+ acc' = if nll_cond == nll_opt || ( (not.null) taus && tau == head taus ) || isNaN tau+ then acc+ else (tau:taus, theta_t:thetas)+ tau = signum delta * sqrt (2*nll_cond - 2*nll_opt)++-- Based on https://insysbio.github.io/LikelihoodProfiler.jl/latest/+-- Borisov, Ivan, and Evgeny Metelkin. "Confidence intervals by constrained optimization—An algorithm and software package for practical identifiability analysis in systems biology." PLOS Computational Biology 16.12 (2020): e1008495.+getProfileCnstr :: Distribution+ -> Maybe Double+ -> SRMatrix+ -> PVector+ -> Fix SRTree+ -> PVector+ -> Double -> Double+ -> Int+ -> Either PVector ProfileT+getProfileCnstr dist mSErr xss ys tree theta stdErr_i tau_max ix+ | stdErr_i == 0.0 = pure $ ProfileT taus thetas theta_i (const theta_i) (const tau_max)+ | otherwise = pure $ ProfileT taus thetas theta_i tau2theta (const tau_max)+ where+ taus = A.fromList compMode [-tau_max, tau_max]+ theta' = A.toList theta+ thetas = A.fromLists' compMode [theta', theta']+ theta_i = theta A.! ix+ getPoint = getEndPoint dist mSErr xss ys tree theta tau_max ix+ leftPt = getPoint True+ rightPt = getPoint False+ tau2theta tau = if tau < 0 then leftPt else rightPt++getEndPoint :: Distribution -> Maybe Double -> A.Array A.S Ix2 Double -> A.Array A.S A.Ix1 Double -> Fix SRTree -> A.Array A.S A.Ix1 Double -> Double -> Int -> Bool -> Double+getEndPoint dist mSErr xss ys tree theta tau_max ix isLeft =+ case minimizeAugLag problem (A.toStorableVector theta_opt) of+ Right sol -> solutionParams sol VS.! ix+ Left e -> traceShow e $ theta_opt A.! ix+ where+ (A.Sz1 n) = A.size theta++ theta_opt = fst $ minimizeNLLNonUnique dist mSErr 100 xss ys tree theta+ nll_opt = nll dist mSErr xss ys tree theta_opt+ loss_crit = nll_opt + tau_max++ loss = subtract loss_crit . nll dist mSErr xss ys tree . A.fromStorableVector compMode+ obj = (if isLeft then id else negate) . (VS.! ix)++ stop = ObjectiveRelativeTolerance 1e-4 :| []+ localAlg = NELDERMEAD obj [] Nothing+ local = LocalProblem (fromIntegral n) stop localAlg+ constraint = InequalityConstraint (Scalar loss) 1e-6++ problem = AugLagProblem [] [] (AUGLAG_LOCAL local [constraint] [])+{-# INLINE getEndPoint #-}++-- Based on+-- Jian-Shen Chen & Robert I Jennrich (2002) Simple Accurate Approximation of Likelihood Profiles,+-- Journal of Computational and Graphical Statistics, 11:3, 714-732, DOI: 10.1198/106186002493+getProfileODE :: Distribution+ -> Maybe Double+ -> SRMatrix+ -> PVector+ -> Fix SRTree+ -> PVector+ -> Double+ -> CI+ -> Double+ -> Int+ -> Either PVector ProfileT+getProfileODE dist mSErr xss ys tree theta stdErr_i estCI tau_max ix+ | stdErr_i == 0.0 = pure dflt+ | otherwise = let (A.fromList compMode -> taus, A.fromLists' compMode . map A.toList -> thetas) = solLeft <> ([0], [theta_opt]) <> solRight+ (tau2theta, theta2tau) = createSplines taus thetas stdErr_i tau_max ix+ in pure $ ProfileT taus thetas optTh tau2theta theta2tau+ where+ dflt = ProfileT (A.fromList compMode [-tau_max, tau_max]) (A.fromLists' compMode [theta', theta']) (theta A.! ix) (const (theta A.! ix)) (const tau_max)+ minimizer = fst . minimizeNLLNonUnique dist mSErr 100 xss ys tree+ grader = snd . gradNLLNonUnique dist mSErr xss ys tree+ theta_opt = minimizer theta+ theta' = A.toList theta+ nll_opt = nll dist mSErr xss ys tree theta_opt+ optTh = theta_opt A.! ix+ p' = p+1+ (A.Sz1 p) = A.size theta+ sErr = fromMaybe 1 mSErr+ getHess = hessianNLL dist mSErr xss ys tree++ odeFun gamma _ u =+ let grad = grader u+ w = hessianNLL dist mSErr xss ys tree u+ m = A.makeArray compMode (A.Sz (p' :. p'))+ (\ (i :. j) -> if | i<p && j<p -> w A.! (i :. j)+ | i==ix -> 1+ | j==ix -> 1+ | otherwise -> 0+ )++ v = A.computeAs A.S $ A.snoc (A.map (*(-gamma)) grad) 1+ dotTheta = unsafePerformIO $ luSolve m v+ in A.fromStorableVector compMode $ VS.init $ A.toStorableVector dotTheta+ tsHi = linSpace 50 (optTh, upper_ estCI)+ tsLo = linSpace 50 (optTh, lower_ estCI)+ scanOn sig = foldMap (calcTau sig) . f . scanl (rk (odeFun sig)) (optTh, theta_opt)+ where f = if sig==1 then id else reverse+ solRight = scanOn 1 tsHi+ solLeft = scanOn (-1) tsLo+ calcTau s t = let nll_i = nll dist mSErr xss ys tree $ snd t+ z = signum ((snd t A.! ix) - optTh) * sqrt (2 * nll_i - 2 * nll_opt)+ in if z == 0 || isNaN z then ([], []) else ([z], [snd t])++rk :: (Double -> PVector -> PVector) -> (Double, PVector) -> Double -> (Double, PVector)+rk f (t, y) t' = (t', y !+! ((1.0/6.0) *. h' !*! (k1 !+! (2.0 *. k2) !+! (2.0 *. k3) !+! k4)))+ where+ h = t' - t+ h', k1, k2, k3, k4 :: PVector+ h' = A.replicate compMode (A.size y) h+ k1 = f t y+ k2 = f (t + 0.5*h) (A.computeAs A.S $ A.zipWith3 (g 0.5) y h' k1) -- (y !+! 0.5*.h' A.!*! k1)+ k3 = f (t + 0.5*h) (A.computeAs A.S $ A.zipWith3 (g 0.5) y h' k2) -- (y !+! 0.5*.h' A.!*! k2)+ k4 = f (t + 1.0*h) (A.computeAs A.S $ A.zipWith3 (g 1.0) y h' k3) -- (y !+! 1.0*.h'!*!k3)+ g a yi hi ki = yi + a * hi * ki+{-# INLINE rk #-}++-- tau0, tau1 theta0, thetaX = tau1 theta0 / tau0+getStatsFromModel :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> BasicStats+getStatsFromModel dist mSErr xss ys tree theta = MkStats cov corr stdErr+ where+ (A.Sz1 k) = A.size theta+ (A.Sz1 n) = A.size ys+ nParams = fromIntegral k+ ssr = sse xss ys tree theta+ ident = A.computeAs A.S $ identityMatrix nParams++ -- only for gaussian+ sErr = sqrt $ ssr / fromIntegral (n - k)++ hess = hessianNLL dist mSErr xss ys tree theta+ -- cov = catch (unsafePerformIO (invChol hess)) (\e -> trace "cov NegDef" $ pure ident)+ fexcept :: (A.PrimMonad m, A.MonadThrow m, A.MonadIO m) => A.SomeException -> m SRMatrix+ fexcept e = trace "cov NegDef" $ pure ident+ cov = unsafePerformIO $ catch (invChol hess) fexcept++ stdErr = A.makeArray compMode (A.Sz1 k) (\ix -> sqrt $ cov A.! (ix :. ix))+ stdErrSq = case outer stdErr stdErr of+ Left _ -> error "stdErr size mismatch?"+ Right v -> v++ corr = A.computeAs A.S $ A.zipWith (/) cov stdErrSq++-- Create splines for profile-t+createSplines :: PVector -> SRMatrix -> Double -> Double -> Int -> (Double -> Double, Double -> Double)+createSplines taus thetas se tau_max ix+ | n < 2 = (genSplineFun [(-tau_max, -se), (tau_max, se)], genSplineFun [(-se, 0), (se, 1)])+ | otherwise = (tau2theta, theta2tau)+ where+ (A.Sz n) = A.size taus+ cols = getCol ix thetas+ nubOnFirst = nubBy (\x y -> fst x == fst y)+ tau2theta = genSplineFun $ nubOnFirst $ sortOnFirst taus cols+ theta2tau = genSplineFun $ nubOnFirst $ sortOnFirst cols taus++getCol :: Int -> SRMatrix -> PVector+getCol ix mtx = getCols mtx A.! ix+{-# inline getCol #-}++sortOnFirst :: PVector -> PVector -> [(Double, Double)]+sortOnFirst xs ys = sortOn fst $ zip (A.toList xs) (A.toList ys)+{-# inline sortOnFirst #-}++splinesSketches :: Double -> PVector -> PVector -> (Double -> Double) -> (Double -> Double)+splinesSketches tauScale (A.toList -> tau) (A.toList -> theta) theta2tau+ | length tau < 2 = id+ | otherwise = genSplineFun gpq+ where+ gpq = sortOn fst [(x, acos y') | (x, y) <- zip tau theta+ , let y' = theta2tau y / tauScale+ , abs y' < 1 ]++approximateContour :: Int -> Int -> [ProfileT] -> Int -> Int -> Double -> [(Double, Double)]+approximateContour nParams nPoints profs ix1 ix2 alpha = go 0+ where+ -- get the info for ix1 and ix2+ (prof1, prof2) = (profs !! ix1, profs !! ix2)+ (tau2theta1, theta2tau1) = (_tau2theta prof1, _theta2tau prof1)+ (tau2theta2, theta2tau2) = (_tau2theta prof2, _theta2tau prof2)++ -- calculate the spline for A-D+ tauScale = sqrt (fromIntegral nParams * quantile (fDistribution nParams (nPoints - nParams)) (1 - alpha))+ splineG1 = splinesSketches tauScale (_taus prof1) (getCol ix2 (_thetas prof1)) theta2tau2+ splineG2 = splinesSketches tauScale (_taus prof2) (getCol ix1 (_thetas prof2)) theta2tau1+ angles = [ (0, splineG1 1), (splineG2 1, 0), (pi, splineG1 (-1)), (splineG2 (-1), pi) ]+ splineAD = genSplineFun points++ applyIfNeg (x, y) = if y < 0 then (-x, -y) else (x ,y)+ points = sortOn fst+ $ [applyIfNeg ((x+y)/2, x - y) | (x, y) <- angles]+ <> (\(x,y) -> [(x + 2*pi, y)]) (head points)++ -- generate the points of the curve+ go 100 = []+ go ix = (p, q) : go (ix+1)+ where+ ai = ix * 2 * pi / 99 - pi+ di = splineAD ai+ taup = cos (ai + di / 2) * tauScale+ tauq = cos (ai - di / 2) * tauScale+ p = tau2theta1 taup+ q = tau2theta2 tauq
+ src/Algorithm/SRTree/Likelihoods.hs view
@@ -0,0 +1,260 @@+{-# LANGUAGE ViewPatterns #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.SRTree.Likelihoods +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : ConstraintKinds+--+-- Functions to calculate different likelihood functions, their gradient, and Hessian matrices.+--+-----------------------------------------------------------------------------+module Algorithm.SRTree.Likelihoods+ ( Distribution (..)+ , PVector+ , SRMatrix+ , sse+ , mse+ , rmse+ , r2+ , nll+ , predict+ , gradNLL+ , gradNLLNonUnique+ , fisherNLL+ , getSErr+ , hessianNLL+ )+ where++import Algorithm.SRTree.AD ( forwardMode, reverseModeUnique ) -- ( reverseModeUnique )+import Data.Massiv.Array hiding (all, map, read, replicate, tail, take, zip)+import qualified Data.Massiv.Array as M+import Data.Maybe (fromMaybe)+import Data.SRTree (Fix (..), SRTree (..), floatConstsToParam, relabelParams)+import Data.SRTree.Derivative (deriveByParam)+import Data.SRTree.Eval (PVector, SRMatrix, SRVector, compMode, evalTree)++-- | Supported distributions for negative log-likelihood+data Distribution = Gaussian | Bernoulli | Poisson+ deriving (Show, Read, Enum, Bounded)++-- | Sum-of-square errors or Sum-of-square residues+sse :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double+sse xss ys tree theta = err+ where+ (Sz m) = M.size ys+ cmp = getComp xss+ yhat = evalTree xss theta tree+ err = M.sum $ (delay ys - yhat) ^ (2 :: Int)++-- | Total Sum-of-squares+sseTot :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double+sseTot xss ys tree theta = err+ where+ (Sz m) = M.size ys+ cmp = getComp xss+ ym = M.sum ys / fromIntegral m+ err = M.sum $ (M.map (subtract ym) ys) ^ (2 :: Int)+ +-- | Mean squared errors+mse :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double+mse xss ys tree theta = let (Sz m) = M.size ys in sse xss ys tree theta / fromIntegral m++-- | Root of the mean squared errors+rmse :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double+rmse xss ys tree = sqrt . mse xss ys tree++-- | Coefficient of determination+r2 :: SRMatrix -> PVector -> Fix SRTree -> PVector -> Double+r2 xss ys tree theta = 1 - sse xss ys tree theta / sseTot xss ys tree theta++-- | logistic function+logistic :: Floating a => a -> a+logistic x = 1 / (1 + exp (-x))+{-# inline logistic #-}++-- | get the standard error from a Maybe Double+-- if it is Nothing, estimate from the ssr, otherwise use the current value+-- For distributions other than Gaussian, it defaults to a constant 1+getSErr :: Num a => Distribution -> a -> Maybe a -> a+getSErr Gaussian est = fromMaybe est+getSErr _ _ = const 1+{-# inline getSErr #-}++-- negation of the sum of values in a vector+negSum :: PVector -> Double+negSum = negate . M.sum+{-# inline negSum #-}++-- | Negative log-likelihood+nll :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> Double++-- | Gaussian distribution+nll Gaussian msErr xss ys t theta = 0.5*(ssr/s2 + m*log (2*pi*s2))+ where+ (Sz m') = M.size ys + (Sz p') = M.size theta+ m = fromIntegral m' + p = fromIntegral p'+ ssr = sse xss ys t theta+ mse' = mse xss ys t theta+ est = sqrt (m - p) -- $ ssr / (m - p)+ sErr = getSErr Gaussian est msErr+ s2 = sErr ^ 2++-- | Bernoulli distribution of f(x; theta) is, given phi = 1 / (1 + exp (-f(x; theta))),+-- y log phi + (1-y) log (1 - phi), assuming y \in {0,1}+nll Bernoulli _ xss ys tree theta+ | notValid ys = error "For Bernoulli distribution the output must be either 0 or 1."+ | otherwise = negate . M.sum $ delay ys * yhat - log (1 + exp yhat)+ where+ (Sz m) = M.size ys+ yhat = evalTree xss theta tree+ notValid = M.any (\x -> x /= 0 && x /= 1)++nll Poisson _ xss ys tree theta + | notValid ys = error "For Poisson distribution the output must be non-negative."+ -- | M.any isNaN yhat = error $ "NaN predictions " <> show theta+ | otherwise = negate . M.sum $ ys' * yhat - ys' * log ys' - exp yhat+ where+ ys' = delay ys+ yhat = evalTree xss theta tree+ notValid = M.any (<0)++nll' :: Distribution -> Double -> SRVector -> SRVector -> Double+nll' Gaussian sErr yhat ys = 0.5*(ssr/s2 + m*log (2*pi*s2))+ where + (Sz m') = M.size ys + m = fromIntegral m' + ssr = M.sum $ (ys - yhat)^2+ s2 = sErr ^ 2+nll' Bernoulli _ yhat ys = negate . M.sum $ ys * yhat - log (1 + exp yhat)+nll' Poisson _ yhat ys = negate . M.sum $ ys * yhat - ys * log ys - exp yhat+{-# INLINE nll' #-}++-- | Prediction for different distributions+predict :: Distribution -> Fix SRTree -> PVector -> SRMatrix -> SRVector+predict Gaussian tree theta xss = evalTree xss theta tree+predict Bernoulli tree theta xss = logistic $ evalTree xss theta tree+predict Poisson tree theta xss = exp $ evalTree xss theta tree++-- | Gradient of the negative log-likelihood+gradNLL :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (Double, SRVector)+gradNLL Gaussian msErr xss ys tree theta =+ (nll' Gaussian sErr yhat ys', delay grad ./ (sErr * sErr))+ where+ (Sz m) = M.size ys+ (Sz p) = M.size theta+ ys' = delay ys+ (yhat, grad) = reverseModeUnique xss theta ys' id tree+ -- err = yhat - delay ys+ ssr = sse xss ys tree theta+ est = sqrt $ fromIntegral (m - p) -- $ ssr / fromIntegral (m - p)+ sErr = getSErr Gaussian est msErr++gradNLL Bernoulli _ xss (delay -> ys) tree theta+ | M.any (\x -> x /= 0 && x /= 1) ys = error "For Bernoulli distribution the output must be either 0 or 1."+ | otherwise = (nll' Bernoulli 1.0 yhat ys, delay grad)+ where+ (yhat, grad) = reverseModeUnique xss theta ys logistic tree+ grad' = M.map nanTo0 grad+ --err = logistic yhat - ys+ nanTo0 x = if isNaN x then 0 else x++gradNLL Poisson _ xss (delay -> ys) tree theta+ | M.any (<0) ys = error "For Poisson distribution the output must be non-negative."+ -- | M.any isNaN grad = error $ "NaN gradient " <> show grad+ | otherwise = (nll' Poisson 1.0 yhat ys, delay grad)+ where+ (yhat, grad) = reverseModeUnique xss theta ys exp tree+ --err = exp yhat - ys++-- | Gradient of the negative log-likelihood+gradNLLNonUnique :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (Double, SRVector)+gradNLLNonUnique Gaussian msErr xss ys tree theta =+ (nll' Gaussian sErr yhat ys', delay grad ./ (sErr * sErr))+ where+ (Sz m) = M.size ys+ (Sz p) = M.size theta+ ys' = delay ys+ (yhat, grad) = forwardMode xss theta err tree+ err = predict Gaussian tree theta xss - ys'+ ssr = sse xss ys tree theta+ est = sqrt $ fromIntegral (m - p) -- $ ssr / fromIntegral (m - p)+ sErr = getSErr Gaussian est msErr++gradNLLNonUnique Bernoulli _ xss (delay -> ys) tree theta+ | M.any (\x -> x /= 0 && x /= 1) ys = error "For Bernoulli distribution the output must be either 0 or 1."+ | otherwise = (nll' Bernoulli 1.0 yhat ys, delay grad)+ where+ (yhat, grad) = forwardMode xss theta err tree+ grad' = M.map nanTo0 grad+ err = predict Bernoulli tree theta xss - delay ys+ nanTo0 x = if isNaN x then 0 else x++gradNLLNonUnique Poisson _ xss (delay -> ys) tree theta+ | M.any (<0) ys = error "For Poisson distribution the output must be non-negative."+ -- | M.any isNaN grad = error $ "NaN gradient " <> show grad+ | otherwise = (nll' Poisson 1.0 yhat ys, delay grad)+ where+ (yhat, grad) = forwardMode xss theta err tree+ err = predict Poisson tree theta xss - delay ys++-- | Fisher information of negative log-likelihood+fisherNLL :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> SRVector+fisherNLL dist msErr xss ys tree theta = makeArray cmp (Sz p) build+ where+ build ix = let dtdix = deriveByParam ix t'+ d2tdix2 = deriveByParam ix dtdix + f' = eval dtdix + f'' = eval d2tdix2 + in (/sErr^2) . M.sum $ phi' * f'^2 - res * f''+ cmp = getComp xss + (Sz m) = M.size ys+ (Sz p) = M.size theta+ t' = fst $ floatConstsToParam tree+ eval = evalTree xss theta+ ssr = sse xss ys tree theta+ sErr = getSErr dist est msErr+ est = sqrt $ fromIntegral (m-p) -- $ ssr / fromIntegral (m - p)+ yhat = eval t'+ res = delay ys - phi++ (phi, phi') = case dist of+ Gaussian -> (yhat, M.replicate compMode (Sz m) 1)+ Bernoulli -> (logistic yhat, phi*(M.replicate compMode (Sz m) 1 - phi))+ Poisson -> (exp yhat, phi)++-- | Hessian of negative log-likelihood+--+-- Note, though the Fisher is just the diagonal of the return of this function+-- it is better to keep them as different functions for efficiency+hessianNLL :: Distribution -> Maybe Double -> SRMatrix -> PVector -> Fix SRTree -> PVector -> SRMatrix+hessianNLL dist msErr xss ys tree theta = makeArray cmp (Sz (p :. p)) build + where+ build (ix :. iy) = let dtdix = deriveByParam ix t' + dtdiy = deriveByParam iy t' + d2tdixy = deriveByParam iy dtdix+ fx = eval dtdix + fy = eval dtdiy + fxy = eval d2tdixy + in (/sErr^2) . M.sum $ phi' * fx * fy - res * fxy+ cmp = getComp xss+ (Sz m) = M.size ys+ (Sz p) = M.size theta+ t' = tree -- relabelParams tree -- $ floatConstsToParam tree+ eval = evalTree xss theta+ ssr = sse xss ys tree theta+ sErr = getSErr dist est msErr+ est = sqrt $ fromIntegral (m - p) -- $ ssr / fromIntegral (m - p)+ yhat = eval t'+ res = delay ys - phi++ (phi, phi') = case dist of+ Gaussian -> (yhat, M.replicate cmp (Sz m) 1)+ Bernoulli -> (logistic yhat, phi*(M.replicate cmp (Sz m) 1 - phi))+ Poisson -> (exp yhat, phi)+
+ src/Algorithm/SRTree/ModelSelection.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.SRTree.ModelSelection +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : ConstraintKinds+--+-- Helper functions for model selection criteria+--+-----------------------------------------------------------------------------++module Algorithm.SRTree.ModelSelection where++import Algorithm.Massiv.Utils ( det )+import Algorithm.SRTree.Likelihoods+ ( PVector, SRMatrix, fisherNLL, hessianNLL, nll, Distribution )+import Data.Massiv.Array (Ix2 (..), Sz (..), (!-!))+import qualified Data.Massiv.Array as A+import Data.SRTree+import Data.SRTree.Eval (evalTree)+import Data.SRTree.Recursion (cata)+import qualified Data.Vector.Storable as VS+++-- | Bayesian information criterion+bic :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+bic dist mSErr xss ys theta tree = (p + 1) * log n + 2 * nll dist mSErr xss ys tree theta+ where+ (A.Sz (fromIntegral -> p)) = A.size theta+ (A.Sz (fromIntegral -> n)) = A.size ys+{-# INLINE bic #-}++-- | Akaike information criterion+aic :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+aic dist mSErr xss ys theta tree = 2 * (p + 1) + 2 * nll dist mSErr xss ys tree theta+ where+ (A.Sz (fromIntegral -> p)) = A.size theta+ (A.Sz (fromIntegral -> n)) = A.size ys+{-# INLINE aic #-}++-- | Evidence +evidence :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+evidence dist mSErr xss ys theta tree = (1 - b) * nll dist mSErr xss ys tree theta - p / 2 * log b+ where+ (A.Sz (fromIntegral -> p)) = A.size theta+ (A.Sz (fromIntegral -> n)) = A.size ys+ b = 1 / sqrt n+{-# INLINE evidence #-}++-- | MDL as described in +-- Bartlett, Deaglan J., Harry Desmond, and Pedro G. Ferreira. "Exhaustive symbolic regression." IEEE Transactions on Evolutionary Computation (2023).+mdl :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+mdl dist mSErr xss ys theta tree = nll' dist mSErr xss ys theta' tree+ + logFunctional tree+ + logParameters dist mSErr xss ys theta tree+ where+ fisher = fisherNLL dist mSErr xss ys tree theta+ theta' = A.computeAs A.S $ A.zipWith (\t f -> if isSignificant t f then t else 0.0) theta fisher+ isSignificant v f = abs (v / sqrt(12 / f) ) >= 1+{-# INLINE mdl #-}++-- | MDL Lattice as described in+-- Bartlett, Deaglan, Harry Desmond, and Pedro Ferreira. "Priors for symbolic regression." Proceedings of the Companion Conference on Genetic and Evolutionary Computation. 2023.+mdlLatt :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+mdlLatt dist mSErr xss ys theta tree = nll' dist mSErr xss ys theta' tree+ + logFunctional tree+ + logParametersLatt dist mSErr xss ys theta tree+ where+ fisher = fisherNLL dist mSErr xss ys tree theta+ theta' = A.computeAs A.S $ A.zipWith (\t f -> if isSignificant t f then t else 0.0) theta fisher+ isSignificant v f = abs (v / sqrt(12 / f) ) >= 1+{-# INLINE mdlLatt #-}++-- | same as `mdl` but weighting the functional structure by frequency calculated using a wiki information of+-- physics and engineering functions+mdlFreq :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+mdlFreq dist mSErr xss ys theta tree = nll dist mSErr xss ys tree theta+ + logFunctionalFreq tree+ + logParameters dist mSErr xss ys theta tree+{-# INLINE mdlFreq #-}++-- log of the functional complexity+logFunctional :: Fix SRTree -> Double+logFunctional tree = countNodes tree * log (countUniqueTokens tree') + + foldr (\c acc -> log (abs c) + acc) 0 consts + + log(2) * numberOfConsts+ where+ tree' = fst $ floatConstsToParam tree+ consts = getIntConsts tree+ numberOfConsts = fromIntegral $ length consts+ signs = sum [1 | a <- getIntConsts tree, a < 0] -- TODO: will we use that?+{-# INLINE logFunctional #-}++-- same as above but weighted by frequency +logFunctionalFreq :: Fix SRTree -> Double+logFunctionalFreq tree = treeToNat tree' + + foldr (\c acc -> log (abs c) + acc) 0 consts + + countVarNodes tree * log (numberOfVars tree)+ where+ tree' = fst $ floatConstsToParam tree+ consts = getIntConsts tree+{-# INLINE logFunctionalFreq #-}++-- log of the parameters complexity+logParameters :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+logParameters dist mSErr xss ys theta tree = -(p / 2) * log 3 + 0.5 * logFisher + logTheta+ where+ -- p = fromIntegral $ VS.length theta+ fisher = fisherNLL dist mSErr xss ys tree theta++ (logTheta, logFisher, p) = foldr addIfSignificant (0, 0, 0)+ $ zip (A.toList theta) (A.toList fisher)++ addIfSignificant (v, f) (acc_v, acc_f, acc_p)+ | isSignificant v f = (acc_v + log (abs v), acc_f + log f, acc_p + 1)+ | otherwise = (acc_v, acc_f, acc_p)++ isSignificant v f = abs (v / sqrt(12 / f) ) >= 1++-- same as above but for the Lattice +logParametersLatt :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+logParametersLatt dist mSErr xss ys theta tree = 0.5 * p * (1 - log 3) + 0.5 * log detFisher+ where+ fisher = fisherNLL dist mSErr xss ys tree theta+ detFisher = det $ hessianNLL dist mSErr xss ys tree theta++ (logTheta, logFisher, p) = foldr addIfSignificant (0, 0, 0)+ $ zip (A.toList theta) (A.toList fisher)++ addIfSignificant (v, f) (acc_v, acc_f, acc_p)+ | isSignificant v f = (acc_v + log (abs v), acc_f + log f, acc_p + 1)+ | otherwise = (acc_v, acc_f, acc_p)++ isSignificant v f = abs (v / sqrt(12 / f) ) >= 1++-- flipped version of nll+nll' :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Double+nll' dist mSErr xss ys theta tree = nll dist mSErr xss ys tree theta+{-# INLINE nll' #-}++treeToNat :: Fix SRTree -> Double+treeToNat = cata $+ \case+ Uni f t -> funToNat f + t+ Bin op l r -> opToNat op + l + r+ _ -> 0.6610799229372109+ where++ opToNat :: Op -> Double+ opToNat Add = 2.500842464597881+ opToNat Sub = 2.500842464597881+ opToNat Mul = 1.720356134912558+ opToNat Div = 2.60436883851265+ opToNat Power = 2.527957363394847+ opToNat PowerAbs = 2.527957363394847+ opToNat AQ = 2.60436883851265++ funToNat :: Function -> Double+ funToNat Sqrt = 4.780867285331753+ funToNat Log = 4.765599813200964+ funToNat Exp = 4.788589331425663+ funToNat Abs = 6.352564869783006+ funToNat Sin = 5.9848400896576885+ funToNat Cos = 5.474014465891698+ funToNat Sinh = 8.038963823353235+ funToNat Cosh = 8.262107374667444+ funToNat Tanh = 7.85664226655928+ funToNat Tan = 8.262107374667444+ funToNat _ = 8.262107374667444+ --funToNat Factorial = 7.702491586732021+{-# INLINE treeToNat #-}
+ src/Algorithm/SRTree/NonlinearOpt.hs view
@@ -0,0 +1,974 @@+{-# OPTIONS_GHC -Wall #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeApplications #-}++{- |+Module : Numeric.NLOPT+Copyright : (c) Matthew Peddie 2017+License : BSD3+Maintainer : Matthew Peddie <mpeddie@gmail.com>+Stability : provisional+Portability : GHC++This module provides a high-level, @hmatrix@-compatible interface to+the <http://ab-initio.mit.edu/wiki/index.php/NLopt NLOPT> library by+Steven G. Johnson.++NOTE: This is an adaptation from https://hackage.haskell.org/package/hmatrix-nlopt-0.2.0.0+that removes the dependency to hmatrix and support any Vector Storage.++= Documentation++Most non-numerical details are documented, but for specific+information on what the optimization methods do, how constraints are+handled, etc., you should consult:++ * The <http://ab-initio.mit.edu/wiki/index.php/NLopt_Introduction NLOPT introduction>++ * The <http://ab-initio.mit.edu/wiki/index.php/NLopt_Reference NLOPT reference manual>++ * The <http://ab-initio.mit.edu/wiki/index.php/NLopt_Algorithms NLOPT algorithm manual>++= Example program++The following interactive session example uses the Nelder-Mead simplex+algorithm, a derivative-free local optimizer, to minimize a trivial+function with a minimum of 22.0 at @(0, 0)@.++>>> import Numeric.LinearAlgebra ( dot, fromList )+>>> let objf x = x `dot` x + 22 -- define objective+>>> let stop = ObjectiveRelativeTolerance 1e-6 :| [] -- define stopping criterion+>>> let algorithm = NELDERMEAD objf [] Nothing -- specify algorithm+>>> let problem = LocalProblem 2 stop algorithm -- specify problem+>>> let x0 = fromList [5, 10] -- specify initial guess+>>> minimizeLocal problem x0+Right (Solution {solutionCost = 22.0, solutionParams = [0.0,0.0], solutionResult = FTOL_REACHED})++-}++module Algorithm.SRTree.NonlinearOpt (+ -- * Specifying the objective function+ Objective+ , ObjectiveD+ , Preconditioner+ -- * Specifying the constraints+ -- ** Bound constraints+ , Bounds(..)+ -- ** Nonlinear constraints+ --+ -- $nonlinearconstraints++ -- *** Constraint functions+ , ScalarConstraint+ , ScalarConstraintD+ , VectorConstraint+ , VectorConstraintD+ -- *** Constraint types+ , Constraint(..)+ , EqualityConstraint(..)+ , InequalityConstraint(..)+ -- *** Collections of constraints+ , EqualityConstraints+ , EqualityConstraintsD+ , InequalityConstraints+ , InequalityConstraintsD+ -- * Stopping conditions+ --+ -- $nonempty+ , StoppingCondition(..)+ , NonEmpty(..)+ -- * Additional configuration+ , RandomSeed(..)+ , Population(..)+ , VectorStorage(..)+ , InitialStep(..)+ -- * Minimization problems+ -- ** Local minimization+ , LocalAlgorithm(..)+ , LocalProblem(..)+ , minimizeLocal+ -- ** Global minimization+ , GlobalAlgorithm(..)+ , GlobalProblem(..)+ , minimizeGlobal+ -- ** Minimization by augmented Lagrangian+ , AugLagAlgorithm(..)+ , AugLagProblem(..)+ , minimizeAugLag+ -- ** Results+ , Solution(..)+ , N.Result(..)+ ) where++import qualified Numeric.Optimization.NLOPT.Bindings as N++import Data.List.NonEmpty (NonEmpty(..))++import qualified Data.Vector.Storable as V+import Data.Vector.Storable ( Vector )++import Control.Exception ( Exception )+import qualified Control.Exception as Ex+import Data.Typeable ( Typeable )+import Data.Foldable ( traverse_ )++import System.IO.Unsafe ( unsafePerformIO )++-- each element i contains a row vec +type Matrix a = [Vector a]++flatten :: V.Storable a => Matrix a -> Vector a +flatten = V.concat+{-# INLINE flatten #-}++{- Function wrapping for the immutable HMatrix interface -}+wrapScalarFunction :: (Vector Double -> Double) -> N.ScalarFunction ()+wrapScalarFunction f params _ _ = return $ f params++wrapScalarFunctionD :: (Vector Double -> (Double, Vector Double))+ -> N.ScalarFunction ()+wrapScalarFunctionD f params grad _ = do+ case grad of+ Nothing -> return ()+ Just g -> V.copy g usergrad+ return result+ where+ (result, usergrad) = f params++wrapVectorFunction :: (Vector Double -> Word -> Vector Double)+ -> Word -> N.VectorFunction ()+wrapVectorFunction f n params vout _ _ = V.copy vout $ f params n++wrapVectorFunctionD :: (Vector Double -> Word -> (Vector Double, Matrix Double))+ -> Word -> N.VectorFunction ()+wrapVectorFunctionD f n params vout jac _ = do+ V.copy vout result+ case jac of+ Nothing -> return ()+ Just j -> V.copy j (flatten userjac)+ where+ (result, userjac) = f params n++wrapPreconditionerFunction :: (Vector Double -> Vector Double -> Vector Double)+ -> N.PreconditionerFunction ()+wrapPreconditionerFunction f params v vpre _ = V.copy vpre (f params v)++{- Objective functions -}+-- | An objective function that calculates the objective value at the+-- given parameter vector.+type Objective+ = Vector Double -- ^ Parameter vector+ -> Double -- ^ Objective function value++-- | An objective function that calculates both the objective value+-- and the gradient of the objective with respect to the input+-- parameter vector, at the given parameter vector.+type ObjectiveD+ = Vector Double -- ^ Parameter vector+ -> (Double, Vector Double) -- ^ (Objective function value, gradient)++-- | A preconditioner function, which computes @vpre = H(x) v@, where+-- @H@ is the Hessian matrix: the positive semi-definite second+-- derivative at the given parameter vector @x@, or an approximation+-- thereof.+type Preconditioner+ = Vector Double -- ^ Parameter vector @x@+ -> Vector Double -- ^ Vector @v@ to precondition at @x@+ -> Vector Double -- ^ Preconditioned vector @vpre@++data ObjectiveFunction f+ = MinimumObjective f+ | PreconditionedMinimumObjective Preconditioner f++applyObjective :: N.Opt -> ObjectiveFunction Objective -> IO N.Result+applyObjective opt (MinimumObjective f) =+ N.set_min_objective opt (wrapScalarFunction f) ()+applyObjective opt (PreconditionedMinimumObjective p f) =+ N.set_precond_min_objective opt (wrapScalarFunction f)+ (wrapPreconditionerFunction p) ()++applyObjectiveD :: N.Opt -> ObjectiveFunction ObjectiveD -> IO N.Result+applyObjectiveD opt (MinimumObjective f) =+ N.set_min_objective opt (wrapScalarFunctionD f) ()+applyObjectiveD opt (PreconditionedMinimumObjective p f) =+ N.set_precond_min_objective opt (wrapScalarFunctionD f)+ (wrapPreconditionerFunction p) ()++{- Constraint functions -}+-- | A constraint function which returns @c(x)@ given the parameter+-- vector @x@. The constraint will enforce that @c(x) == 0@ (equality+-- constraint) or @c(x) <= 0@ (inequality constraint).+type ScalarConstraint+ = Vector Double -- ^ Parameter vector @x@+ -> Double -- ^ Constraint violation (deviation from 0)++-- | A constraint function which returns @c(x)@ given the parameter+-- vector @x@ along with the gradient of @c(x)@ with respect to @x@ at+-- that point. The constraint will enforce that @c(x) == 0@ (equality+-- constraint) or @c(x) <= 0@ (inequality constraint).+type ScalarConstraintD+ = Vector Double -- ^ Parameter vector+ -> (Double, Vector Double) -- ^ (Constraint violation, constraint gradient)++-- | A constraint function which returns a vector @c(x)@ given the+-- parameter vector @x@. The constraint will enforce that @c(x) == 0@+-- (equality constraint) or @c(x) <= 0@ (inequality constraint).+type VectorConstraint+ = Vector Double -- ^ Parameter vector+ -> Word -- ^ Constraint Vectorize+ -> Vector Double -- ^ Constraint violation vector++-- | A constraint function which returns @c(x)@ given the parameter+-- vector @x@ along with the Jacobian (first derivative) matrix of+-- @c(x)@ with respect to @x@ at that point. The constraint will+-- enforce that @c(x) == 0@ (equality constraint) or @c(x) <= 0@+-- (inequality constraint).+type VectorConstraintD+ = Vector Double -- ^ Parameter vector+ -> Word -- ^ Constraint Vectorize+ -> (Vector Double, Matrix Double) -- ^ (Constraint violation vector,+ -- constraint Jacobian)++-- $nonlinearconstraints+--+-- Note that most NLOPT algorithms do not support nonlinear+-- constraints natively; if you need to enforce nonlinear constraints,+-- you may want to use the 'AugLagAlgorithm' family of solvers, which+-- can add nonlinear constraints to some algorithm that does not+-- support them by a principled modification of the objective+-- function.+--+-- == Example program+--+-- The following interactive session example enforces a scalar+-- constraint on the problem given in the beginning of the module: the+-- parameters must always sum to 1. The minimizer finds a constrained+-- minimum of 22.5 at @(0.5, 0.5)@.+--+-- >>> import Numeric.LinearAlgebra ( dot, fromList, toList )+-- >>> let objf x = x `dot` x + 22+-- >>> let stop = ObjectiveRelativeTolerance 1e-9 :| []+-- >>> -- define constraint function:+-- >>> let constraintf x = sum (toList x) - 1.0+-- >>> -- define constraint object to pass to the algorithm:+-- >>> let constraint = EqualityConstraint (Scalar constraintf) 1e-6+-- >>> let algorithm = COBYLA objf [] [] [constraint] Nothing+-- >>> let problem = LocalProblem 2 stop algorithm+-- >>> let x0 = fromList [5, 10]+-- >>> minimizeLocal problem x0+-- Right (Solution {solutionCost = 22.500000000013028, solutionParams = [0.5000025521533521,0.49999744784664796], solutionResult = FTOL_REACHED})+++data Constraint s v+ -- | A scalar constraint.+ = Scalar s+ -- | A vector constraint.+ | Vector Word v+ -- | A scalar constraint with an attached preconditioning function.+ | Preconditioned Preconditioner s++-- | An equality constraint, comprised of both the constraint function+-- (or functions, if a preconditioner is used) along with the desired+-- tolerance.+data EqualityConstraint s v = EqualityConstraint+ { eqConstraintFunctions :: Constraint s v+ , eqConstraintTolerance :: Double+ }++-- | An inequality constraint, comprised of both the constraint+-- function (or functions, if a preconditioner is used) along with the+-- desired tolerance.+data InequalityConstraint s v = InequalityConstraint+ { ineqConstraintFunctions :: Constraint s v+ , ineqConstraintTolerance :: Double+ }++-- | A collection of equality constraints that do not supply+-- constraint derivatives.+type EqualityConstraints =+ [EqualityConstraint ScalarConstraint VectorConstraint]++-- | A collection of inequality constraints that do not supply+-- constraint derivatives.+type InequalityConstraints =+ [InequalityConstraint ScalarConstraint VectorConstraint]++-- | A collection of equality constraints that supply constraint+-- derivatives.+type EqualityConstraintsD = [EqualityConstraint ScalarConstraintD VectorConstraintD]++-- | A collection of inequality constraints that supply constraint+-- derivatives.+type InequalityConstraintsD = [InequalityConstraint ScalarConstraintD VectorConstraintD]++class ApplyConstraint constraint where+ applyConstraint :: N.Opt -> constraint -> IO N.Result++instance ApplyConstraint (EqualityConstraint ScalarConstraint VectorConstraint) where+ applyConstraint opt (EqualityConstraint ty tol) = case ty of+ Scalar s ->+ N.add_equality_constraint opt (wrapScalarFunction s) () tol+ Vector n v ->+ N.add_equality_mconstraint opt n (wrapVectorFunction v n) () tol+ Preconditioned p s ->+ N.add_precond_equality_constraint opt (wrapScalarFunction s)+ (wrapPreconditionerFunction p) () tol++instance ApplyConstraint (InequalityConstraint ScalarConstraint VectorConstraint) where+ applyConstraint opt (InequalityConstraint ty tol) = case ty of+ Scalar s ->+ N.add_inequality_constraint opt (wrapScalarFunction s) () tol+ Vector n v ->+ N.add_inequality_mconstraint opt n (wrapVectorFunction v n) () tol+ Preconditioned p s ->+ N.add_precond_inequality_constraint opt (wrapScalarFunction s)+ (wrapPreconditionerFunction p) () tol++instance ApplyConstraint (EqualityConstraint ScalarConstraintD VectorConstraintD) where+ applyConstraint opt (EqualityConstraint ty tol) = case ty of+ Scalar s ->+ N.add_equality_constraint opt (wrapScalarFunctionD s) () tol+ Vector n v ->+ N.add_equality_mconstraint opt n (wrapVectorFunctionD v n) () tol+ Preconditioned p s ->+ N.add_precond_equality_constraint opt (wrapScalarFunctionD s)+ (wrapPreconditionerFunction p) () tol++instance ApplyConstraint (InequalityConstraint ScalarConstraintD VectorConstraintD) where+ applyConstraint opt (InequalityConstraint ty tol) = case ty of+ Scalar s ->+ N.add_inequality_constraint opt (wrapScalarFunctionD s) () tol+ Vector n v ->+ N.add_inequality_mconstraint opt n (wrapVectorFunctionD v n) () tol+ Preconditioned p s ->+ N.add_precond_inequality_constraint opt (wrapScalarFunctionD s)+ (wrapPreconditionerFunction p) () tol++{- Bounds -}++-- | Bound constraints are specified by vectors of the same dimension+-- as the parameter space.+--+-- == Example program+--+-- The following interactive session example enforces lower bounds on+-- the example from the beginning of the module. This prevents the+-- optimizer from locating the true minimum at @(0, 0)@; a slightly+-- higher constrained minimum at @(1, 1)@ is found. Note that the+-- optimizer returns 'N.XTOL_REACHED' rather than 'N.FTOL_REACHED',+-- because the bound constraint is active at the final minimum.+--+-- >>> import Numeric.LinearAlgebra ( dot, fromList )+-- >>> let objf x = x `dot` x + 22 -- define objective+-- >>> let stop = ObjectiveRelativeTolerance 1e-6 :| [] -- define stopping criterion+-- >>> let lowerbound = LowerBounds $ fromList [1, 1] -- specify bounds+-- >>> let algorithm = NELDERMEAD objf [lowerbound] Nothing -- specify algorithm+-- >>> let problem = LocalProblem 2 stop algorithm -- specify problem+-- >>> let x0 = fromList [5, 10] -- specify initial guess+-- >>> minimizeLocal problem x0+-- Right (Solution {solutionCost = 24.0, solutionParams = [1.0,1.0], solutionResult = XTOL_REACHED})+data Bounds+ -- | Lower bound vector @v@ means we want @x >= v@.+ = LowerBounds (Vector Double)+ -- | Upper bound vector @u@ means we want @x <= u@.+ | UpperBounds (Vector Double)+ deriving (Eq, Show, Read)++applyBounds :: N.Opt -> Bounds -> IO N.Result+applyBounds opt (LowerBounds lbvec) = N.set_lower_bounds opt lbvec+applyBounds opt (UpperBounds ubvec) = N.set_upper_bounds opt ubvec++{- Stopping conditions -}++-- | A 'StoppingCondition' tells NLOPT when to stop working on a+-- minimization problem. When multiple 'StoppingCondition's are+-- provided, the problem will stop when any one condition is met.+data StoppingCondition+ -- | Stop minimizing when an objective value @J@ less than or equal+ -- to the provided value is found.+ = MinimumValue Double+ -- | Stop minimizing when an optimization step changes the objective+ -- value @J@ by less than the provided tolerance multiplied by @|J|@.+ | ObjectiveRelativeTolerance Double+ -- | Stop minimizing when an optimization step changes the objective+ -- value by less than the provided tolerance.+ | ObjectiveAbsoluteTolerance Double+ -- | Stop when an optimization step changes /every element/ of the+ -- parameter vector @x@ by less than @x@ scaled by the provided+ -- tolerance.+ | ParameterRelativeTolerance Double+ -- | Stop when an optimization step changes /every element/ of the+ -- parameter vector @x@ by less than the corresponding element in+ -- the provided vector of tolerances values.+ | ParameterAbsoluteTolerance (Vector Double)+ -- | Stop when the number of evaluations of the objective function+ -- exceeds the provided count.+ | MaximumEvaluations Word+ -- | Stop when the optimization time exceeds the provided time (in+ -- seconds). This is not a precise limit.+ | MaximumTime Double+ deriving (Eq, Show, Read)++-- $nonempty+--+-- The 'NonEmpty' data type from 'Data.List.NonEmpty' is re-exported+-- here, because it is used to ensure that you always specify at least+-- one stopping condition.++applyStoppingCondition :: N.Opt -> StoppingCondition -> IO N.Result+applyStoppingCondition opt (MinimumValue x) = N.set_stopval opt x+applyStoppingCondition opt (ObjectiveRelativeTolerance x) = N.set_ftol_rel opt x+applyStoppingCondition opt (ObjectiveAbsoluteTolerance x) = N.set_ftol_abs opt x+applyStoppingCondition opt (ParameterRelativeTolerance x) = N.set_xtol_rel opt x+applyStoppingCondition opt (ParameterAbsoluteTolerance v) = N.set_xtol_abs opt v+applyStoppingCondition opt (MaximumEvaluations n) = N.set_maxeval opt n+applyStoppingCondition opt (MaximumTime deltat) = N.set_maxtime opt deltat++{- Random seed control -}++-- | This specifies how to initialize the random number generator for+-- stochastic algorithms.+data RandomSeed+ -- | Seed the RNG with the provided value.+ = SeedValue Word+ -- | Seed the RNG using the system clock.+ | SeedFromTime+ -- | Don't perform any explicit initialization of the RNG.+ | Don'tSeed+ deriving (Eq, Show, Read)++applyRandomSeed :: RandomSeed -> IO ()+applyRandomSeed Don'tSeed = return ()+applyRandomSeed (SeedValue n) = N.srand n+applyRandomSeed SeedFromTime = N.srand_time++{- Random stuff -}++-- | This specifies the population size for algorithms that use a pool+-- of solutions.+newtype Population = Population Word deriving (Eq, Show, Read)++applyPopulation :: N.Opt -> Population -> IO N.Result+applyPopulation opt (Population n) = N.set_population opt n++-- | This specifies the memory size to be used by algorithms like+-- 'LBFGS' which store approximate Hessian or Jacobian matrices.+newtype VectorStorage = VectorStorage Word deriving (Eq, Show, Read)++applyVectorStorage :: N.Opt -> VectorStorage -> IO N.Result+applyVectorStorage opt (VectorStorage n) = N.set_vector_storage opt n++-- | This vector with the same dimension as the parameter vector @x@+-- specifies the initial step for the optimizer to take. (This+-- applies to local gradient-free algorithms, which cannot use+-- gradients to estimate how big a step to take.)+newtype InitialStep = InitialStep (Vector Double) deriving (Eq, Show, Read)++applyInitialStep :: N.Opt -> InitialStep -> IO N.Result+applyInitialStep opt (InitialStep v) = N.set_initial_step opt v++{- Algorithms -}++data GlobalProblem = GlobalProblem+ { lowerBounds :: Vector Double -- ^ Lower bounds for @x@+ , upperBounds :: Vector Double -- ^ Upper bounds for @x@+ , gstop :: NonEmpty StoppingCondition -- ^ At least one stopping+ -- condition+ , galgorithm :: GlobalAlgorithm -- ^ Algorithm specification+ }++-- | These are the global minimization algorithms provided by NLOPT. Please see+-- <http://ab-initio.mit.edu/wiki/index.php/NLopt_Algorithms the NLOPT algorithm manual>+-- for more details on how the methods work and how they relate to one another.+--+-- Optional parameters are wrapped in a 'Maybe'; for example, if you+-- see 'Maybe' 'Population', you can simply specify 'Nothing' to use+-- the default behavior.+data GlobalAlgorithm+ -- | DIviding RECTangles+ = DIRECT Objective+ -- | DIviding RECTangles, locally-biased variant+ | DIRECT_L Objective+ -- | DIviding RECTangles, "slightly randomized"+ | DIRECT_L_RAND Objective RandomSeed+ -- | DIviding RECTangles, unscaled version+ | DIRECT_NOSCAL Objective+ -- | DIviding RECTangles, locally-biased and unscaled+ | DIRECT_L_NOSCAL Objective+ -- | DIviding RECTangles, locally-biased, unscaled and "slightly+ -- randomized"+ | DIRECT_L_RAND_NOSCAL Objective RandomSeed+ -- | DIviding RECTangles, original FORTRAN implementation+ | ORIG_DIRECT Objective InequalityConstraints+ -- | DIviding RECTangles, locally-biased, original FORTRAN+ -- implementation+ | ORIG_DIRECT_L Objective InequalityConstraints+ -- | Stochastic Global Optimization.+ -- __This algorithm is only available if you have linked with @libnlopt_cxx@.__+ | STOGO ObjectiveD+ -- | Stochastic Global Optimization, randomized variant.+ -- __This algorithm is only available if you have linked with @libnlopt_cxx@.__+ | STOGO_RAND ObjectiveD RandomSeed+ -- | Controlled Random Search with Local Mutation+ | CRS2_LM Objective RandomSeed (Maybe Population)+ -- | Improved Stochastic Ranking Evolution Strategy+ | ISRES Objective InequalityConstraints EqualityConstraints RandomSeed (Maybe Population)+ -- | Evolutionary Algorithm+ | ESCH Objective+ -- | Original Multi-Level Single-Linkage+ | MLSL Objective LocalProblem (Maybe Population)+ -- | Multi-Level Single-Linkage with Sobol Low-Discrepancy+ -- Sequence for starting points+ | MLSL_LDS Objective LocalProblem (Maybe Population)++algorithmEnumOfGlobal :: GlobalAlgorithm -> N.Algorithm+algorithmEnumOfGlobal (DIRECT _) = N.GN_DIRECT+algorithmEnumOfGlobal (DIRECT_L _) = N.GN_DIRECT_L+algorithmEnumOfGlobal (DIRECT_L_RAND _ _) = N.GN_DIRECT_L_RAND+algorithmEnumOfGlobal (DIRECT_NOSCAL _) = N.GN_DIRECT_NOSCAL+algorithmEnumOfGlobal (DIRECT_L_NOSCAL _) = N.GN_DIRECT_L_NOSCAL+algorithmEnumOfGlobal (DIRECT_L_RAND_NOSCAL _ _) = N.GN_DIRECT_L_RAND_NOSCAL+algorithmEnumOfGlobal (ORIG_DIRECT _ _) = N.GN_ORIG_DIRECT+algorithmEnumOfGlobal (ORIG_DIRECT_L _ _) = N.GN_ORIG_DIRECT_L+algorithmEnumOfGlobal (STOGO _) = N.GD_STOGO+algorithmEnumOfGlobal (STOGO_RAND _ _) = N.GD_STOGO_RAND+algorithmEnumOfGlobal (CRS2_LM _ _ _) = N.GN_CRS2_LM+algorithmEnumOfGlobal (ISRES _ _ _ _ _) = N.GN_ISRES+algorithmEnumOfGlobal (ESCH _) = N.GN_ESCH+algorithmEnumOfGlobal (MLSL _ _ _) = N.G_MLSL+algorithmEnumOfGlobal (MLSL_LDS _ _ _) = N.G_MLSL_LDS++applyGlobalObjective :: N.Opt -> GlobalAlgorithm -> IO ()+applyGlobalObjective opt alg = go alg+ where+ obj = tryTo . applyObjective opt . MinimumObjective+ objD = tryTo . applyObjectiveD opt . MinimumObjective++ go (DIRECT o) = obj o+ go (DIRECT_L o) = obj o+ go (DIRECT_NOSCAL o) = obj o+ go (DIRECT_L_NOSCAL o) = obj o+ go (ESCH o) = obj o+ go (STOGO o) = objD o+ go (DIRECT_L_RAND o _) = obj o+ go (DIRECT_L_RAND_NOSCAL o _) = obj o+ go (ORIG_DIRECT o _) = obj o+ go (ORIG_DIRECT_L o _) = obj o+ go (STOGO_RAND o _) = objD o+ go (CRS2_LM o _ _) = obj o+ go (ISRES o _ _ _ _) = obj o+ go (MLSL o _ _) = obj o+ go (MLSL_LDS o _ _) = obj o++applyGlobalAlgorithm :: N.Opt -> GlobalAlgorithm -> IO ()+applyGlobalAlgorithm opt alg = do+ applyGlobalObjective opt alg+ go alg+ where+ seed = applyRandomSeed+ pop = maybe (return ()) (tryTo . applyPopulation opt)+ ic = traverse_ (tryTo . applyConstraint opt)+ ec = traverse_ (tryTo . applyConstraint opt)++ local lp = setupLocalProblem lp >>= N.set_local_optimizer opt++ go (DIRECT_L_RAND _ s) = seed s+ go (DIRECT_L_RAND_NOSCAL _ s) = seed s+ go (ORIG_DIRECT _ ineq) = ic ineq+ go (ORIG_DIRECT_L _ ineq) = ic ineq+ go (STOGO_RAND _ s) = seed s+ go (CRS2_LM _ s p) = seed s *> pop p+ go (ISRES _ ineq eq s p) = ic ineq *> ec eq *> seed s *> pop p+ go (MLSL _ lp p) = local lp *> pop p+ go (MLSL_LDS _ lp p) = local lp *> pop p+ go _ = return ()++tryTo :: IO N.Result -> IO ()+tryTo act = do+ result <- act+ if (N.isSuccess result)+ then return ()+ else Ex.throw $ NloptException result++data NloptException = NloptException N.Result deriving (Show, Typeable)+instance Exception NloptException++-- | Solve the specified global optimization problem.+--+-- = Example program+--+-- The following interactive session example uses the 'ISRES'+-- algorithm, a stochastic, derivative-free global optimizer, to+-- minimize a trivial function with a minimum of 22.0 at @(0, 0)@.+-- The search is conducted within a box from -10 to 10 in each+-- dimension.+--+-- >>> import Numeric.LinearAlgebra ( dot, fromList )+-- >>> let objf x = x `dot` x + 22 -- define objective+-- >>> let stop = ObjectiveRelativeTolerance 1e-12 :| [] -- define stopping criterion+-- >>> let algorithm = ISRES objf [] [] (SeedValue 22) Nothing -- specify algorithm+-- >>> let lowerbounds = fromList [-10, -10] -- specify bounds+-- >>> let upperbounds = fromList [10, 10] -- specify bounds+-- >>> let problem = GlobalProblem lowerbounds upperbounds stop algorithm+-- >>> let x0 = fromList [5, 8] -- specify initial guess+-- >>> minimizeGlobal problem x0+-- Right (Solution {solutionCost = 22.000000000002807, solutionParams = [-1.660591102367038e-6,2.2407062393213684e-7], solutionResult = FTOL_REACHED})+minimizeGlobal :: GlobalProblem -- ^ Problem specification+ -> Vector Double -- ^ Initial parameter guess+ -> Either N.Result Solution -- ^ Optimization results+minimizeGlobal prob x0 =+ unsafePerformIO $ (Right <$> minimizeGlobal' prob x0) `Ex.catch` handler+ where+ handler :: NloptException -> IO (Either N.Result a)+ handler (NloptException retcode) = return $ Left retcode++applyGlobalProblem :: N.Opt -> GlobalProblem -> IO ()+applyGlobalProblem opt (GlobalProblem lb ub stop alg) = do+ tryTo $ applyBounds opt (LowerBounds lb)+ tryTo $ applyBounds opt (UpperBounds ub)+ traverse_ (tryTo . applyStoppingCondition opt) stop+ applyGlobalAlgorithm opt alg++newOpt :: N.Algorithm -> Word -> IO N.Opt+newOpt alg sz = do+ opt' <- N.create alg sz+ case opt' of+ Nothing -> Ex.throw $ NloptException N.FAILURE+ Just opt -> return opt++setupGlobalProblem :: GlobalProblem -> IO N.Opt+setupGlobalProblem gp@(GlobalProblem _ _ _ alg) = do+ opt <- newOpt (algorithmEnumOfGlobal alg) (problemSize gp)+ applyGlobalProblem opt gp+ return opt++solveProblem :: N.Opt -> Vector Double -> IO Solution+solveProblem opt x0 = do+ (N.Output outret outcost outx) <- N.optimize opt x0+ if (N.isSuccess outret)+ then return $ Solution outcost outx outret+ else Ex.throw $ NloptException outret++minimizeGlobal' :: GlobalProblem -> Vector Double -> IO Solution+minimizeGlobal' gp x0 = do+ opt <- setupGlobalProblem gp+ solveProblem opt x0++data LocalProblem = LocalProblem+ { lsize :: Word -- ^ The dimension of the+ -- parameter vector.+ , lstop :: NonEmpty StoppingCondition -- ^ At least one stopping+ -- condition+ , lalgorithm :: LocalAlgorithm -- ^ Algorithm specification+ }++-- | These are the local minimization algorithms provided by NLOPT. Please see+-- <http://ab-initio.mit.edu/wiki/index.php/NLopt_Algorithms the NLOPT algorithm manual>+-- for more details on how the methods work and how they relate to one+-- another. Note that some local methods require you provide+-- derivatives (gradients or Jacobians) for your objective function+-- and constraint functions.+--+-- Optional parameters are wrapped in a 'Maybe'; for example, if you+-- see 'Maybe' 'VectorStorage', you can simply specify 'Nothing' to+-- use the default behavior.+data LocalAlgorithm+ -- | Limited-memory BFGS+ = LBFGS_NOCEDAL ObjectiveD (Maybe VectorStorage)+ -- | Limited-memory BFGS+ | LBFGS ObjectiveD (Maybe VectorStorage)+ -- | Shifted limited-memory variable-metric, rank-2+ | VAR2 ObjectiveD (Maybe VectorStorage)+ -- | Shifted limited-memory variable-metric, rank-1+ | VAR1 ObjectiveD (Maybe VectorStorage)+ -- | Truncated Newton's method+ | TNEWTON ObjectiveD (Maybe VectorStorage)+ -- | Truncated Newton's method with automatic restarting+ | TNEWTON_RESTART ObjectiveD (Maybe VectorStorage)+ -- | Preconditioned truncated Newton's method+ | TNEWTON_PRECOND ObjectiveD (Maybe VectorStorage)+ -- | Preconditioned truncated Newton's method with automatic+ -- restarting+ | TNEWTON_PRECOND_RESTART ObjectiveD (Maybe VectorStorage)+ -- | Method of moving averages+ | MMA ObjectiveD InequalityConstraintsD+ -- | Sequential Least-Squares Quadratic Programming+ | SLSQP ObjectiveD [Bounds] InequalityConstraintsD EqualityConstraintsD+ -- | Conservative Convex Separable Approximation+ | CCSAQ ObjectiveD Preconditioner+ -- | PRincipal AXIS gradient-free local optimization+ | PRAXIS Objective [Bounds] (Maybe InitialStep)+ -- | Constrained Optimization BY Linear Approximations+ | COBYLA Objective [Bounds] InequalityConstraints EqualityConstraints+ (Maybe InitialStep)+ -- | Powell's NEWUOA algorithm+ | NEWUOA Objective (Maybe InitialStep)+ -- | Powell's NEWUOA algorithm with bounds by SGJ+ | NEWUOA_BOUND Objective [Bounds] (Maybe InitialStep)+ -- | Nelder-Mead Simplex gradient-free method+ | NELDERMEAD Objective [Bounds] (Maybe InitialStep)+ -- | NLOPT implementation of Rowan's Subplex algorithm+ | SBPLX Objective [Bounds] (Maybe InitialStep)+ -- | Bounded Optimization BY Quadratic Approximations+ | BOBYQA Objective [Bounds] (Maybe InitialStep)++algorithmEnumOfLocal :: LocalAlgorithm -> N.Algorithm+algorithmEnumOfLocal (LBFGS_NOCEDAL _ _) = N.LD_LBFGS_NOCEDAL+algorithmEnumOfLocal (LBFGS _ _) = N.LD_LBFGS+algorithmEnumOfLocal (VAR2 _ _) = N.LD_VAR2+algorithmEnumOfLocal (VAR1 _ _) = N.LD_VAR1+algorithmEnumOfLocal (TNEWTON _ _) = N.LD_TNEWTON+algorithmEnumOfLocal (TNEWTON_RESTART _ _) = N.LD_TNEWTON_RESTART+algorithmEnumOfLocal (TNEWTON_PRECOND _ _) = N.LD_TNEWTON_PRECOND+algorithmEnumOfLocal (TNEWTON_PRECOND_RESTART _ _) = N.LD_TNEWTON_PRECOND_RESTART+algorithmEnumOfLocal (MMA _ _) = N.LD_MMA+algorithmEnumOfLocal (SLSQP _ _ _ _) = N.LD_SLSQP+algorithmEnumOfLocal (CCSAQ _ _) = N.LD_CCSAQ+algorithmEnumOfLocal (PRAXIS _ _ _) = N.LN_PRAXIS+algorithmEnumOfLocal (COBYLA _ _ _ _ _) = N.LN_COBYLA+algorithmEnumOfLocal (NEWUOA _ _) = N.LN_NEWUOA+algorithmEnumOfLocal (NEWUOA_BOUND _ _ _) = N.LN_NEWUOA+algorithmEnumOfLocal (NELDERMEAD _ _ _) = N.LN_NELDERMEAD+algorithmEnumOfLocal (SBPLX _ _ _) = N.LN_SBPLX+algorithmEnumOfLocal (BOBYQA _ _ _) = N.LN_BOBYQA++applyLocalObjective :: N.Opt -> LocalAlgorithm -> IO ()+applyLocalObjective opt alg = go alg+ where+ obj = tryTo . applyObjective opt . MinimumObjective+ objD = tryTo . applyObjectiveD opt . MinimumObjective+ precond p = tryTo . applyObjectiveD opt . PreconditionedMinimumObjective p++ go (LBFGS_NOCEDAL o _) = objD o+ go (LBFGS o _) = objD o+ go (VAR2 o _) = objD o+ go (VAR1 o _) = objD o+ go (TNEWTON o _) = objD o+ go (TNEWTON_RESTART o _) = objD o+ go (TNEWTON_PRECOND o _) = objD o+ go (TNEWTON_PRECOND_RESTART o _) = objD o+ go (MMA o _) = objD o+ go (SLSQP o _ _ _) = objD o+ go (CCSAQ o prec) = precond prec o+ go (PRAXIS o _ _) = obj o+ go (COBYLA o _ _ _ _) = obj o+ go (NEWUOA o _) = obj o+ go (NEWUOA_BOUND o _ _) = obj o+ go (NELDERMEAD o _ _) = obj o+ go (SBPLX o _ _) = obj o+ go (BOBYQA o _ _) = obj o++applyLocalAlgorithm :: N.Opt -> LocalAlgorithm -> IO ()+applyLocalAlgorithm opt alg = do+ applyLocalObjective opt alg+ go alg+ where+ ic = traverse_ (tryTo . applyConstraint opt)+ icd = traverse_ (tryTo . applyConstraint opt)+ ec = traverse_ (tryTo . applyConstraint opt)+ ecd = traverse_ (tryTo . applyConstraint opt)+ store = maybe (return ()) (tryTo . applyVectorStorage opt)+ bound = traverse_ (tryTo . applyBounds opt)+ step0 = maybe (return ()) (tryTo . applyInitialStep opt)++ go (LBFGS_NOCEDAL _ vs) = store vs+ go (LBFGS _ vs) = store vs+ go (VAR2 _ vs) = store vs+ go (VAR1 _ vs) = store vs+ go (TNEWTON _ vs) = store vs+ go (TNEWTON_RESTART _ vs) = store vs+ go (TNEWTON_PRECOND _ vs) = store vs+ go (TNEWTON_PRECOND_RESTART _ vs) = store vs+ go (MMA _ ineqd) = icd ineqd+ go (SLSQP _ b ineqd eqd) =+ bound b *> icd ineqd *> ecd eqd+ go (CCSAQ _ _ ) = return ()+ go (PRAXIS _ b s) = bound b *> step0 s+ go (COBYLA _ b ineq eq s) =+ bound b *> ic ineq *> ec eq *> step0 s+ go (NEWUOA _ s) = step0 s+ go (NEWUOA_BOUND _ b s) = bound b *> step0 s+ go (NELDERMEAD _ b s) = bound b *> step0 s+ go (SBPLX _ b s) = bound b *> step0 s+ go (BOBYQA _ b s) = bound b *> step0 s++applyLocalProblem :: N.Opt -> LocalProblem -> IO ()+applyLocalProblem opt (LocalProblem _ stop alg) = do+ traverse_ (tryTo . applyStoppingCondition opt) stop+ applyLocalAlgorithm opt alg++setupLocalProblem :: LocalProblem -> IO N.Opt+setupLocalProblem lp@(LocalProblem sz _ alg) = do+ opt <- newOpt (algorithmEnumOfLocal alg) sz+ applyLocalProblem opt lp+ return opt++minimizeLocal' :: LocalProblem -> Vector Double -> IO Solution+minimizeLocal' lp x0 = do+ opt <- setupLocalProblem lp+ solveProblem opt x0++-- |+-- == Example program+--+-- The following interactive session example enforces the same scalar+-- constraint as the nonlinear constraint example, but this time it+-- uses the SLSQP solver to find the minimum.+--+-- >>> import Numeric.LinearAlgebra ( dot, fromList, toList, scale )+-- >>> let objf x = (x `dot` x + 22, 2 `scale` x)+-- >>> let stop = ObjectiveRelativeTolerance 1e-9 :| []+-- >>> let constraintf x = (sum (toList x) - 1.0, fromList [1, 1])+-- >>> let constraint = EqualityConstraint (Scalar constraintf) 1e-6+-- >>> let algorithm = SLSQP objf [] [] [constraint]+-- >>> let problem = LocalProblem 2 stop algorithm+-- >>> let x0 = fromList [5, 10]+-- >>> minimizeLocal problem x0+-- Right (Solution {solutionCost = 22.5, solutionParams = [0.4999999999999998,0.5000000000000002], solutionResult = FTOL_REACHED})+minimizeLocal :: LocalProblem -> Vector Double -> Either N.Result Solution+minimizeLocal prob x0 =+ unsafePerformIO $ (Right <$> minimizeLocal' prob x0) `Ex.catch` handler+ where+ handler :: NloptException -> IO (Either N.Result a)+ handler (NloptException retcode) = return $ Left retcode++class ProblemSize c where+ problemSize :: c -> Word++instance ProblemSize LocalProblem where+ problemSize = lsize++instance ProblemSize GlobalProblem where+ problemSize = fromIntegral . V.length . lowerBounds++instance ProblemSize AugLagProblem where+ problemSize (AugLagProblem _ _ alg) = case alg of+ AUGLAG_LOCAL lp _ _ -> problemSize lp+ AUGLAG_EQ_LOCAL lp -> problemSize lp+ AUGLAG_GLOBAL gp _ _ -> problemSize gp+ AUGLAG_EQ_GLOBAL gp -> problemSize gp+++-- | __IMPORTANT NOTE__+--+-- For augmented lagrangian problems, you, the user, are responsible+-- for providing the appropriate type of constraint. If the+-- subsidiary problem requires an `ObjectiveD`, then you should+-- provide constraint functions with derivatives. If the subsidiary+-- problem requires an `Objective`, you should provide constraint+-- functions without derivatives. If you don't do this, you may get a+-- runtime error.+data AugLagProblem = AugLagProblem+ { alEquality :: EqualityConstraints -- ^ Possibly empty set of+ -- equality constraints+ , alEqualityD :: EqualityConstraintsD -- ^ Possibly empty set of+ -- equality constraints with+ -- derivatives+ , alalgorithm :: AugLagAlgorithm -- ^ Algorithm specification.+ }++-- | The Augmented Lagrangian solvers allow you to enforce nonlinear+-- constraints while using local or global algorithms that don't+-- natively support them. The subsidiary problem is used to do the+-- minimization, but the @AUGLAG@ methods modify the objective to+-- enforce the constraints. Please see+-- <http://ab-initio.mit.edu/wiki/index.php/NLopt_Algorithms the NLOPT algorithm manual>+-- for more details on how the methods work and how they relate to one another.+--+-- See the documentation for 'AugLagProblem' for an important note+-- about the constraint functions.+data AugLagAlgorithm+ -- | AUGmented LAGrangian with a local subsidiary method+ = AUGLAG_LOCAL LocalProblem InequalityConstraints InequalityConstraintsD+ -- | AUGmented LAGrangian with a local subsidiary method and with+ -- penalty functions only for equality constraints+ | AUGLAG_EQ_LOCAL LocalProblem+ -- | AUGmented LAGrangian with a global subsidiary method+ | AUGLAG_GLOBAL GlobalProblem InequalityConstraints InequalityConstraintsD+ -- | AUGmented LAGrangian with a global subsidiary method and with+ -- penalty functions only for equality constraints.+ | AUGLAG_EQ_GLOBAL GlobalProblem++algorithmEnumOfAugLag :: AugLagAlgorithm -> N.Algorithm+algorithmEnumOfAugLag (AUGLAG_LOCAL _ _ _) = N.AUGLAG+algorithmEnumOfAugLag (AUGLAG_EQ_LOCAL _) = N.AUGLAG_EQ+algorithmEnumOfAugLag (AUGLAG_GLOBAL _ _ _) = N.AUGLAG+algorithmEnumOfAugLag (AUGLAG_EQ_GLOBAL _) = N.AUGLAG_EQ++-- | This structure is returned in the event of a successful+-- optimization.+data Solution = Solution+ { solutionCost :: Double -- ^ The objective function value+ -- at the minimum+ , solutionParams :: Vector Double -- ^ The parameter vector which+ -- minimizes the objective+ , solutionResult :: N.Result -- ^ Why the optimizer stopped+ } deriving (Eq, Show, Read)++applyAugLagAlgorithm :: N.Opt -> AugLagAlgorithm -> IO ()+applyAugLagAlgorithm opt alg = go alg+ where+ ic = traverse_ (tryTo . applyConstraint opt)+ icd = traverse_ (tryTo . applyConstraint opt)+ -- AUGLAG won't work at all if you don't pass it the same+ -- objective as the subproblem -- here we pull out the subproblem+ -- objectives from the algorithm spec and set the same objective+ -- function so the user can't mess it up.+ local lp = tryTo $ do+ localopt <- setupLocalProblem lp+ applyLocalObjective opt (lalgorithm lp)+ N.set_local_optimizer opt localopt+ global gp = do+ tryTo $ setupGlobalProblem gp >>= N.set_local_optimizer opt+ applyGlobalObjective opt (galgorithm gp)++ go (AUGLAG_LOCAL lp ineq ineqd) = local lp *> ic ineq *> icd ineqd+ go (AUGLAG_EQ_LOCAL lp) = local lp+ go (AUGLAG_GLOBAL gp ineq ineqd) = global gp *> ic ineq *> icd ineqd+ go (AUGLAG_EQ_GLOBAL gp) = global gp++applyAugLagProblem :: N.Opt -> AugLagProblem -> IO ()+applyAugLagProblem opt (AugLagProblem eq eqd alg) = do+ traverse_ (tryTo . applyConstraint opt) eq+ traverse_ (tryTo . applyConstraint opt) eqd+ applyAugLagAlgorithm opt alg++minimizeAugLag' :: AugLagProblem -> Vector Double -> IO Solution+minimizeAugLag' ap@(AugLagProblem _ _ alg) x0 = do+ opt <- newOpt (algorithmEnumOfAugLag alg) (problemSize ap)+ applyAugLagProblem opt ap+ solveProblem opt x0++-- |+-- == Example program+--+-- The following interactive session example enforces the same scalar+-- constraint as the nonlinear constraint example, but this time it+-- uses the augmented Lagrangian method to enforce the constraint and+-- the 'SBPLX' algorithm, which does not support nonlinear constraints+-- itself, to perform the minimization. As before, the parameters+-- must always sum to 1, and the minimizer finds the same constrained+-- minimum of 22.5 at @(0.5, 0.5)@.+--+-- >>> import Numeric.LinearAlgebra ( dot, fromList, toList )+-- >>> let objf x = x `dot` x + 22+-- >>> let stop = ObjectiveRelativeTolerance 1e-9 :| []+-- >>> let algorithm = SBPLX objf [] Nothing+-- >>> let subproblem = LocalProblem 2 stop algorithm+-- >>> let x0 = fromList [5, 10]+-- >>> minimizeLocal subproblem x0+-- Right (Solution {solutionCost = 22.0, solutionParams = [0.0,0.0], solutionResult = FTOL_REACHED})+-- >>> -- define constraint function:+-- >>> let constraintf x = sum (toList x) - 1.0+-- >>> -- define constraint object to pass to the algorithm:+-- >>> let constraint = EqualityConstraint (Scalar constraintf) 1e-6+-- >>> let problem = AugLagProblem [constraint] [] (AUGLAG_EQ_LOCAL subproblem)+-- >>> minimizeAugLag problem x0+-- Right (Solution {solutionCost = 22.500000015505844, solutionParams = [0.5000880506776678,0.4999119493223323], solutionResult = FTOL_REACHED})++minimizeAugLag :: AugLagProblem -> Vector Double -> Either N.Result Solution+minimizeAugLag prob x0 =+ unsafePerformIO $ (Right <$> minimizeAugLag' prob x0) `Ex.catch` handler+ where+ handler :: NloptException -> IO (Either N.Result a)+ handler (NloptException retcode) = return $ Left retcode
+ src/Algorithm/SRTree/Opt.hs view
@@ -0,0 +1,109 @@+-----------------------------------------------------------------------------+-- |+-- Module : Algorithm.SRTree.Opt +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : ConstraintKinds+--+-- Functions to optimize the parameters of an expression.+--+-----------------------------------------------------------------------------+module Algorithm.SRTree.Opt+ where++import Algorithm.SRTree.Likelihoods+import Algorithm.SRTree.NonlinearOpt+import Data.Bifunctor (bimap, second)+import Data.Massiv.Array+import Data.SRTree (Fix (..), SRTree (..), floatConstsToParam, relabelParams)+import Data.SRTree.Eval (evalTree, compMode)+import qualified Data.Vector.Storable as VS++-- | minimizes the negative log-likelihood of the expression+minimizeNLL :: Distribution -> Maybe Double -> Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)+minimizeNLL dist msErr niter xss ys tree t0+ | niter == 0 = (t0, f)+ | n == 0 = (t0, f)+ | otherwise = (fromStorableVector compMode t_opt, f)+ where+ tree' = relabelParams tree -- $ fst $ floatConstsToParam tree+ t0' = toStorableVector t0+ (Sz n) = size t0+ (Sz m) = size ys+ funAndGrad = second (toStorableVector . computeAs S) . gradNLL dist msErr xss ys tree' . fromStorableVector compMode+ (f, _) = gradNLL dist msErr xss ys tree t0 -- if there's no parameter or no iterations++ algorithm = LBFGS funAndGrad Nothing+ stop = ObjectiveRelativeTolerance 1e-10 :| [MaximumEvaluations (fromIntegral niter)]+ problem = LocalProblem (fromIntegral n) stop algorithm+ t_opt = case minimizeLocal problem t0' of+ Right sol -> solutionParams sol+ Left e -> t0'++-- | minimizes the likelihood assuming repeating parameters in the expression +minimizeNLLNonUnique :: Distribution -> Maybe Double -> Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)+minimizeNLLNonUnique dist msErr niter xss ys tree t0+ | niter == 0 = (t0, f)+ | n == 0 = (t0, f)+ | otherwise = (fromStorableVector compMode t_opt, f)+ where+ t0' = toStorableVector t0+ (Sz n) = size t0+ (Sz m) = size ys+ funAndGrad = second (toStorableVector . computeAs S) . gradNLLNonUnique dist msErr xss ys tree . fromStorableVector compMode+ (f, _) = gradNLLNonUnique dist msErr xss ys tree t0 -- if there's no parameter or no iterations++ algorithm = LBFGS funAndGrad Nothing+ stop = ObjectiveRelativeTolerance 1e-5 :| [MaximumEvaluations (fromIntegral niter)]+ problem = LocalProblem (fromIntegral n) stop algorithm+ t_opt = case minimizeLocal problem t0' of+ Right sol -> solutionParams sol+ Left e -> t0'++-- | minimizes the function while keeping the parameter ix fixed (used to calculate the profile)+minimizeNLLWithFixedParam :: Distribution -> Maybe Double -> Int -> SRMatrix -> PVector -> Fix SRTree -> Int -> PVector -> PVector+minimizeNLLWithFixedParam dist msErr niter xss ys tree ix t0+ | niter == 0 = t0+ | n == 0 = t0+ | n > m = t0+ | otherwise = fromStorableVector compMode t_opt+ where+ t0' = toStorableVector t0+ (Sz n) = size t0+ (Sz m) = size ys+ setTo0 = (VS.// [(ix, 0.0)])+ funAndGrad = second (setTo0 . toStorableVector . computeAs S). gradNLLNonUnique dist msErr xss ys tree . fromStorableVector compMode+ (f, _) = gradNLLNonUnique dist msErr xss ys tree t0 -- if there's no parameter or no iterations++ algorithm = LBFGS funAndGrad Nothing+ stop = ObjectiveRelativeTolerance 1e-5 :| [MaximumEvaluations (fromIntegral niter)]+ problem = LocalProblem (fromIntegral n) stop algorithm+ t_opt = case minimizeLocal problem t0' of+ Right sol -> solutionParams sol+ Left e -> t0'++-- | minimizes using Gaussian likelihood +minimizeGaussian :: Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)+minimizeGaussian = minimizeNLL Gaussian Nothing++-- | minimizes using Binomial likelihood +minimizeBinomial :: Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)+minimizeBinomial = minimizeNLL Bernoulli Nothing++-- | minimizes using Poisson likelihood +minimizePoisson :: Int -> SRMatrix -> PVector -> Fix SRTree -> PVector -> (PVector, Double)+minimizePoisson = minimizeNLL Poisson Nothing++-- estimates the standard error if not provided +estimateSErr :: Distribution -> Maybe Double -> SRMatrix -> PVector -> PVector -> Fix SRTree -> Int -> Maybe Double+estimateSErr Gaussian Nothing xss ys theta0 t nIter = Just err+ where+ theta = fst $ minimizeNLL Gaussian (Just 1) nIter xss ys t theta0+ (Sz m) = size ys+ (Sz p) = size theta+ ssr = sse xss ys t theta+ err = sqrt $ ssr / fromIntegral (m - p)+estimateSErr _ (Just s) _ _ _ _ _ = Just s+estimateSErr _ _ _ _ _ _ _ = Nothing
src/Data/SRTree.hs view
@@ -1,7 +1,7 @@ ----------------------------------------------------------------------------- -- | -- Module : Data.SRTree --- Copyright : (c) Fabricio Olivetti 2021 - 2021+-- Copyright : (c) Fabricio Olivetti 2021 - 2024 -- License : BSD3 -- Maintainer : fabricio.olivetti@gmail.com -- Stability : experimental@@ -16,25 +16,22 @@ , Op(..) , param , var+ , constv , arity , getChildren+ , childrenOf+ , replaceChildren+ , getOperator , countNodes , countVarNodes , countConsts , countParams , countOccurrences- , deriveBy- , deriveByVar- , deriveByParam- , derivative- , forwardMode- , gradParamsFwd- , gradParamsRev- , evalFun- , evalOp- , inverseFunc- , evalTree+ , countUniqueTokens+ , numberOfVars+ , getIntConsts , relabelParams+ , relabelVars , constsToParam , floatConstsToParam , paramsToConst@@ -48,25 +45,22 @@ , Op(..) , param , var+ , constv , arity , getChildren+ , childrenOf+ , replaceChildren+ , getOperator , countNodes , countVarNodes , countConsts , countParams , countOccurrences- , deriveBy- , deriveByVar- , deriveByParam- , derivative- , forwardMode- , gradParamsFwd- , gradParamsRev- , evalFun- , evalOp- , inverseFunc- , evalTree+ , countUniqueTokens+ , numberOfVars+ , getIntConsts , relabelParams+ , relabelVars , constsToParam , floatConstsToParam , paramsToConst
+ src/Data/SRTree/Datasets.hs view
@@ -0,0 +1,214 @@+{-# language ImportQualifiedPost #-}+{-# language ViewPatterns #-}+{-# language OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module : Data.SRTree.Datasets+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : FlexibleInstances, DeriveFunctor, ScopedTypeVariables, ConstraintKinds+--+-- Utility library to handle regression datasets+-- this module exports only the `loadDataset` function.+--+-----------------------------------------------------------------------------+module Data.SRTree.Datasets ( loadDataset )+ where++import Codec.Compression.GZip (decompress)+import Data.ByteString.Char8 qualified as B+import Data.ByteString.Lazy qualified as BS+import Data.List (delete, find, intercalate)+import Data.Massiv.Array+ ( Array,+ Comp (Seq, Par),+ Ix2 ((:.)),+ S (..),+ Sz (Sz1),+ (<!),+ )+import Data.Massiv.Array qualified as M+import Data.Maybe (fromJust)+import Data.SRTree.Eval (PVector, SRMatrix, compMode)+import Data.Vector qualified as V+import System.FilePath (takeExtension)+import Text.Read (readMaybe)++-- | Loads a list of list of bytestrings to a matrix of double+loadMtx :: [[B.ByteString]] -> Array S Ix2 Double+loadMtx = M.fromLists' compMode . map (map (read . B.unpack))+{-# INLINE loadMtx #-}++-- | Returns true if the extension is .gz+isGZip :: FilePath -> Bool+isGZip = (== ".gz") . takeExtension+{-# INLINE isGZip #-}++-- | Detects the separator automatically by +-- checking whether the use of each separator generates+-- the same amount of SRMatrix in every row and at least two SRMatrix.+--+-- >>> detectSep ["x1,x2,x3,x4"] +-- ','+detectSep :: [B.ByteString] -> Char+detectSep xss = go seps+ where+ seps = [' ','\t','|',':',';',',']+ xss' = map B.strip xss++ -- consistency check whether all rows have the same+ -- number of columns when spliting by this sep + allSameLen [] = True+ allSameLen (y:ys) = y /= 1 && all (==y) ys++ go [] = error $ "CSV parsing error: unsupported separator. Supporter separators are "+ <> intercalate "," (map show seps)+ go (c:cs) = if allSameLen $ map (length . B.split c) xss'+ then c+ else go cs+{-# INLINE detectSep #-}++-- | reads a file and returns a list of list of `ByteString`+-- corresponding to each element of the matrix.+-- The first row can be a header. +readFileToLines :: FilePath -> IO [[B.ByteString]]+readFileToLines filename = do+ content <- removeBEmpty . toLines . toChar8 . unzip <$> BS.readFile filename+ let sep = getSep content+ pure . removeEmpty . map (B.split sep) $ content+ where+ getSep = detectSep . take 100 -- use only first 100 rows to detect separator+ removeBEmpty = filter (not . B.null)+ removeEmpty = filter (not . null)+ toLines = B.split '\n'+ unzip = if isGZip filename then decompress else id+ toChar8 = B.pack . map (toEnum . fromEnum) . BS.unpack+{-# INLINE readFileToLines #-}++-- | Splits the parameters from the filename+-- the expected format of the filename is *filename.ext:p1:p2:p3:p4*+-- where p1 and p2 is the starting and end rows for the training data,+-- by default p1 = 0 and p2 = number of rows - 1+-- p3 is the target PVector, it can be a string corresponding to the header+-- or an index.+-- p4 is a comma separated list of SRMatrix (either index or name) to be used as +-- input variables. These will be renamed internally as x0, x1, ... in the order+-- of this list.+splitFileNameParams :: FilePath -> (FilePath, [B.ByteString])+splitFileNameParams (B.pack -> filename) = (B.unpack fname, take 4 params)+ where+ (fname : params') = B.split ':' filename+ -- fill up the empty parameters with an empty string+ params = params' <> replicate (4 - min 4 (length params')) B.empty+{-# inline splitFileNameParams #-}++-- | Tries to parse a string into an int+parseVal :: String -> Either String Int+parseVal xs = case readMaybe xs of+ Nothing -> Left xs+ Just x -> Right x+{-# inline parseVal #-}++-- | Given a map between PVector name and indeces,+-- the target PVector and the variables SRMatrix,+-- returns the indices of the variables SRMatrix and the target+getColumns :: [(B.ByteString, Int)] -> B.ByteString -> B.ByteString -> ([Int], Int)+getColumns headerMap target columns = (ixs, iy)+ where+ n_cols = length headerMap+ getIx c = case parseVal c of+ -- if the PVector is a name, retrive the index+ Left name -> case find ((== B.pack name) . fst) headerMap of+ Nothing -> error $ "PVector name " <> name <> " does not exist."+ Just v -> snd v+ -- if it is an int, check if it is within range+ Right v -> if v >= 0 && v < n_cols+ then v+ else error $ "PVector index " <> show v <> " out of range."+ -- if the input variables SRMatrix are ommitted, use+ -- every PVector except for iy+ ixs = if B.null columns+ then delete iy [0 .. n_cols - 1]+ else map (getIx . B.unpack) $ B.split ',' columns+ -- if the target PVector is ommitted, use the last one+ iy = if B.null target+ then n_cols - 1+ else getIx $ B.unpack target+{-# inline getColumns #-}++-- | Given the start and end rows, it returns the +-- hmatrix extractors for the training and validation data+getRows :: B.ByteString -> B.ByteString -> Int -> (Int, Int)+getRows (B.unpack -> start) (B.unpack -> end) nRows+ | st_ix >= end_ix = error $ "Invalid range: " <> show start <> ":" <> show end <> "."+ | st_ix == 0 && end_ix == nRows-1 = (0, nRows - 1)+ | otherwise = (st_ix, end_ix)+ where+ st_ix = if null start+ then 0+ else case readMaybe start of+ Nothing -> error $ "Invalid starting row " <> start <> "."+ Just x -> if x < 0 || x >= nRows+ then error $ "Invalid starting row " <> show x <> "."+ else x+ end_ix = if null end+ then nRows - 1+ else case readMaybe end of+ Nothing -> error $ "Invalid end row " <> end <> "."+ Just x -> if x < 0 || x >= nRows+ then error $ "Invalid end row " <> show x <> "."+ else x+{-# inline getRows #-}++-- | `loadDataset` loads a dataset with a filename in the format:+-- filename.ext:start_row:end_row:target:features+-- it returns the X_train, y_train, X_test, y_test, varnames, target name +-- where varnames are a comma separated list of the name of the vars +-- and target name is the name of the target+--+-- where+--+-- **start_row:end_row** is the range of the training rows (default 0:nrows-1).+-- every other row not included in this range will be used as validation+-- **target** is either the name of the PVector (if the datafile has headers) or the index+-- of the target variable+-- **features** is a comma separated list of SRMatrix names or indices to be used as+-- input variables of the regression model.+loadDataset :: FilePath -> Bool -> IO ((SRMatrix, PVector, SRMatrix, PVector), String, String)+loadDataset filename hasHeader = do + csv <- readFileToLines fname+ pure $ processData csv params hasHeader+ where+ (fname, params) = splitFileNameParams filename++-- support function that does everything for loadDataset+processData :: [[B.ByteString]] -> [B.ByteString] -> Bool -> ((SRMatrix, PVector, SRMatrix, PVector), String, String)+processData csv params hasHeader = ((x_train, y_train, x_val, y_val) , varnames, targetname)+ where+ ncols = length $ head csv+ nrows = length csv - fromEnum hasHeader+ (header, content) = if hasHeader+ then (zip (map B.strip $ head csv) [0..], tail csv)+ else (map (\i -> (B.pack ('x' : show i), i)) [0 .. ncols-1], csv)+ varnames = intercalate "," [B.unpack v | c <- ixs+ , let v = fst . fromJust $ find ((==c).snd) header+ ]+ targetname = if hasHeader then (B.unpack . fst . fromJust . find ((==iy).snd) $ header) else "y"+ -- get rows and SRMatrix indices+ (st, end) = getRows (params !! 0) (params !! 1) nrows+ (ixs, iy) = getColumns header (params !! 2) (params !! 3)++ -- load data and split sets+ datum = loadMtx content+ p = length ixs++ x = M.computeAs S $ M.throwEither $ M.stackInnerSlicesM $ map (datum <!) ixs+ y = datum <! iy+ x_train = M.computeAs S $ M.extractFromTo' (st :. 0) (end+1 :. p) x+ y_train = M.computeAs S $ M.extractFromTo' st (end+1) y + x_val = M.computeAs S $ M.throwEither $ M.deleteRowsM st (Sz1 $ end - st + 1) x+ y_val = M.computeAs S $ M.throwEither $ M.deleteColumnsM st (Sz1 $ end - st + 1) y+{-# inline processData #-}+
+ src/Data/SRTree/Derivative.hs view
@@ -0,0 +1,125 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module : Data.SRTree.Derivative +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : FlexibleInstances, DeriveFunctor, ScopedTypeVariables+--+-- Symbolic derivative of SRTree expressions+--+-----------------------------------------------------------------------------+module Data.SRTree.Derivative+ ( derivative+ , doubleDerivative+ , deriveByVar+ , deriveByParam+ )+ where++import Data.SRTree.Internal+import Data.SRTree.Recursion (Fix (..), mutu)+import Data.Attoparsec.ByteString.Char8 (double)++-- | Creates the symbolic partial derivative of a tree by variable `dx` (if `p` is `False`)+-- or parameter `dx` (if `p` is `True`).+-- This uses mutual recursion where the first recursion (alg1) holds the derivative w.r.t. +-- the current node and the second (alg2) holds the original tree.+--+-- >>> showExpr . deriveBy False 0 $ 2 * "x0" * "x1"+-- "(2.0 * x1)"+-- >>> showExpr . deriveBy True 1 $ 2 * "x0" * "t0" - sqrt ("t1" * "x0")+-- "(-1.0 * ((1.0 / (2.0 * Sqrt((t1 * x0)))) * x0))"+deriveBy :: Bool -> Int -> Fix SRTree -> Fix SRTree+deriveBy p dx = fst (mutu alg1 alg2)+ where+ alg1 (Var ix) = if not p && ix == dx then 1 else 0+ alg1 (Param ix) = if p && ix == dx then 1 else 0+ alg1 (Const _) = 0+ alg1 (Uni f t) = derivative f (snd t) * fst t+ alg1 (Bin Add l r) = fst l + fst r+ alg1 (Bin Sub l r) = fst l - fst r+ alg1 (Bin Mul l r) = fst l * snd r + snd l * fst r+ alg1 (Bin Div l r) = (fst l * snd r - snd l * fst r) / snd r ** 2+ alg1 (Bin Power l r) = snd l ** (snd r - 1) * (snd r * fst l + snd l * log (snd l) * fst r)+ alg1 (Bin PowerAbs l r) = (abs (snd l) ** (snd r)) * (fst r * log (abs (snd l)) + snd r * fst l / snd l)+ alg1 (Bin AQ l r) = ((1 + snd r * snd r) * fst l - snd l * snd r * fst r) / (1 + snd r * snd r) ** 1.5++ alg2 (Var ix) = var ix+ alg2 (Param ix) = param ix+ alg2 (Const c) = Fix (Const c)+ alg2 (Uni f t) = Fix (Uni f $ snd t)+ alg2 (Bin f l r) = Fix (Bin f (snd l) (snd r))++-- | Derivative of each supported function+-- For a function h(f) it returns the derivative dh/df+--+-- >>> derivative Log 2.0+-- 0.5+derivative :: Floating a => Function -> a -> a+derivative Id = const 1+derivative Abs = \x -> x / abs x+derivative Sin = cos+derivative Cos = negate.sin+derivative Tan = recip . (**2.0) . cos+derivative Sinh = cosh+derivative Cosh = sinh+derivative Tanh = (1-) . (**2.0) . tanh+derivative ASin = recip . sqrt . (1-) . (^2)+derivative ACos = negate . recip . sqrt . (1-) . (^2)+derivative ATan = recip . (1+) . (^2)+derivative ASinh = recip . sqrt . (1+) . (^2)+derivative ACosh = \x -> 1 / (sqrt (x-1) * sqrt (x+1))+derivative ATanh = recip . (1-) . (^2)+derivative Sqrt = recip . (2*) . sqrt+derivative SqrtAbs = \x -> x / (2.0 * abs x ** (3.0/2.0))+derivative Cbrt = recip . (3*) . (**(1/3)) . (^2)+derivative Square = (2*)+derivative Exp = exp+derivative Log = recip+derivative LogAbs = recip+derivative Recip = negate . recip . (^2)+derivative Cube = (3*) . (^2)+{-# INLINE derivative #-}++-- | Second-order derivative of supported functions+--+-- >>> doubleDerivative Log 2.0+-- -0.25+doubleDerivative :: Floating a => Function -> a -> a+doubleDerivative Id = const 0+doubleDerivative Abs = const 0+doubleDerivative Sin = negate.sin+doubleDerivative Cos = negate.cos+doubleDerivative Tan = \x -> 2 * sin x / (cos x) ^ 3+doubleDerivative Sinh = sinh+doubleDerivative Cosh = cosh+doubleDerivative Tanh = \x -> -2 * tanh x * (1 / cosh x)^2+doubleDerivative ASin = \x -> x / (1 - x^2)**(3/2)+doubleDerivative ACos = \x -> x / (1 - x^2)**(3/2)+doubleDerivative ATan = \x -> (-2*x) / (x^2 + 1)^2+doubleDerivative ASinh = \x -> x / (x^2 + 1)**(3/2) -- check+doubleDerivative ACosh = \x -> 1 / (sqrt (x-1) * sqrt (x+1)) -- check+doubleDerivative ATanh = recip . (1-) . (^2) -- check+doubleDerivative Sqrt = \x -> -1 / (4 * sqrt x^3)+doubleDerivative SqrtAbs = \x -> (-x)*x/(4 * abs x ** (3.5))+doubleDerivative Cbrt = \x -> -2 / (9 * x * (x^2)**(1/3))+doubleDerivative Square = const 2+doubleDerivative Exp = exp+doubleDerivative Log = negate . recip . (^2)+doubleDerivative LogAbs = negate . recip . (^2)+doubleDerivative Recip = (*2) . recip . (^3)+doubleDerivative Cube = (6*)+{-# INLINE doubleDerivative #-}++-- | Symbolic derivative by a variable+deriveByVar :: Int -> Fix SRTree -> Fix SRTree+deriveByVar = deriveBy False+{-# INLINE deriveByVar #-}++-- | Symbolic derivative by a parameter+deriveByParam :: Int -> Fix SRTree -> Fix SRTree+deriveByParam = deriveBy True+{-# INLINE deriveByParam #-}
+ src/Data/SRTree/Eval.hs view
@@ -0,0 +1,210 @@+{-# LANGUAGE LambdaCase #-}+-----------------------------------------------------------------------------+-- |+-- Module : Data.SRTree.Eval +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : FlexibleInstances, DeriveFunctor, ScopedTypeVariables+--+-- Evaluation of SRTree expressions+--+-----------------------------------------------------------------------------+{-# LANGUAGE FlexibleInstances #-}+module Data.SRTree.Eval+ ( evalTree+ , evalOp+ , evalFun+ , cbrt+ , inverseFunc+ , invertibles+ , evalInverse+ , invright+ , invleft+ , replicateAs+ , SRVector, PVector, SRMatrix+ , compMode+ )+ where++import Data.Massiv.Array+import qualified Data.Massiv.Array as M+import Data.SRTree.Internal+import Data.SRTree.Recursion (Fix (..), cata)++-- | Vector of target values +type SRVector = M.Array D Ix1 Double+-- | Vector of parameter values. Needs to be strict to be readily accesible.+type PVector = M.Array S Ix1 Double+-- | Matrix of features values +type SRMatrix = M.Array S Ix2 Double++compMode :: M.Comp+compMode = M.Par'++-- Improve quality of life with Num and Floating instances for our matrices +instance Index ix => Num (M.Array D ix Double) where+ (+) = (!+!)+ (-) = (!-!)+ (*) = (!*!)+ abs = absA+ signum = signumA + fromInteger = fromInteger+ negate = negateA++instance Index ix => Floating (M.Array D ix Double) where+ pi = pi + exp = expA + log = logA + sqrt = sqrtA + sin = sinA + cos = cosA+ tan = tanA + asin = asinA + acos = acosA + atan = atanA + sinh = sinhA + cosh = coshA+ tanh = tanhA + asinh = asinhA + acosh = acoshA + atanh = atanhA + (**) = (.**)+instance Index ix => Fractional (M.Array D ix Double) where+ fromRational = fromRational+ (/) = (!/!)+ recip = recipA++-- returns a vector with the same number of rows as xss and containing a single repeated value.+replicateAs :: SRMatrix -> Double -> SRVector+replicateAs xss c = let (Sz (m :. _)) = M.size xss in M.replicate (getComp xss) (Sz m) c++-- | Evaluates the tree given a vector of variable values, a vector of parameter values and a function that takes a Double and change to whatever type the variables have. This is useful when working with datasets of many values per variables.+evalTree :: SRMatrix -> PVector -> Fix SRTree -> SRVector+evalTree xss params = cata $ + \case + Var ix -> xss <! ix+ Param ix -> replicateAs xss $ params ! ix+ Const c -> replicateAs xss c+ Uni g t -> evalFun g t+ Bin op l r -> evalOp op l r+{-# INLINE evalTree #-}++-- evaluates an operator +evalOp :: Floating a => Op -> a -> a -> a+evalOp Add = (+)+evalOp Sub = (-)+evalOp Mul = (*)+evalOp Div = (/)+evalOp Power = (**)+evalOp PowerAbs = \l r -> abs l ** r+evalOp AQ = \l r -> l / sqrt(1 + r*r)+{-# INLINE evalOp #-}++-- evaluates a function +evalFun :: Floating a => Function -> a -> a+evalFun Id = id+evalFun Abs = abs+evalFun Sin = sin+evalFun Cos = cos+evalFun Tan = tan+evalFun Sinh = sinh+evalFun Cosh = cosh+evalFun Tanh = tanh+evalFun ASin = asin+evalFun ACos = acos+evalFun ATan = atan+evalFun ASinh = asinh+evalFun ACosh = acosh+evalFun ATanh = atanh+evalFun Sqrt = sqrt+evalFun SqrtAbs = sqrt . abs+evalFun Cbrt = cbrt+evalFun Square = (^2)+evalFun Log = log+evalFun LogAbs = log . abs+evalFun Exp = exp+evalFun Recip = recip+evalFun Cube = (^3)+{-# INLINE evalFun #-}++-- Cubic root+cbrt :: Floating a => a -> a+cbrt x = signum x * abs x ** (1/3)+{-# INLINE cbrt #-}++-- | Returns the inverse of a function. This is a partial function.+inverseFunc :: Function -> Function+inverseFunc Id = Id+inverseFunc Sin = ASin+inverseFunc Cos = ACos+inverseFunc Tan = ATan+inverseFunc Sinh = ASinh+inverseFunc Cosh = ACosh+inverseFunc Tanh = ATanh+inverseFunc ASin = Sin+inverseFunc ACos = Cos+inverseFunc ATan = Tan+inverseFunc ASinh = Sinh+inverseFunc ACosh = Cosh+inverseFunc ATanh = Tanh+inverseFunc Sqrt = Square+inverseFunc Square = Sqrt+-- inverseFunc Cbrt = (^3)+inverseFunc Log = Exp+inverseFunc Exp = Log+inverseFunc Recip = Recip+-- inverseFunc Abs = Abs -- we assume abs(x) = sqrt(x^2) so y = sqrt(x^2) => x^2 = y^2 => x = sqrt(y^2) = x = abs(y)+inverseFunc x = error $ show x ++ " has no support for inverse function"+{-# INLINE inverseFunc #-}++-- | evals the inverse of a function+evalInverse :: Floating a => Function -> a -> a+evalInverse Id = id+evalInverse Sin = asin+evalInverse Cos = acos+evalInverse Tan = atan+evalInverse Sinh = asinh+evalInverse Cosh = acosh+evalInverse Tanh = atanh+evalInverse ASin = sin+evalInverse ACos = cos+evalInverse ATan = tan+evalInverse ASinh = sinh+evalInverse ACosh = cosh+evalInverse ATanh = tanh+evalInverse Sqrt = (^2)+evalInverse SqrtAbs = (^2)+evalInverse Square = sqrt+evalInverse Cbrt = (^3)+evalInverse Log = exp+evalInverse LogAbs = exp+evalInverse Exp = log+evalInverse Abs = abs -- we assume abs(x) = sqrt(x^2) so y = sqrt(x^2) => x^2 = y^2 => x = sqrt(y^2) = x = abs(y)+evalInverse Recip = recip+evalInverse Cube = cbrt++-- | evals the right inverse of an operator +invright :: Floating a => Op -> a -> (a -> a)+invright Add v = subtract v+invright Sub v = (+v)+invright Mul v = (/v)+invright Div v = (*v)+invright Power v = (**(1/v))+invright PowerAbs v = (**(1/v))+invright AQ v = (* sqrt (1 + v*v))++-- | evals the left inverse of an operator +invleft :: Floating a => Op -> a -> (a -> a)+invleft Add v = subtract v+invleft Sub v = (+v) . negate -- y = v - r => r = v - y+invleft Mul v = (/v)+invleft Div v = (v/) -- y = v / r => r = v/y+invleft Power v = logBase v -- (/(log v)) . log -- y = v ^ r log y = r log v r = log y / log v+invleft PowerAbs v = logBase v . abs+invleft AQ v = (v/)++-- | List of invertible functions+invertibles :: [Function]+invertibles = [Id, Sin, Cos, Tan, Tanh, ASin, ACos, ATan, ATanh, Sqrt, Square, Log, Exp, Recip]
src/Data/SRTree/Internal.hs view
@@ -1,11 +1,12 @@ {-# language FlexibleInstances, DeriveFunctor #-} {-# language ScopedTypeVariables #-} {-# language RankNTypes #-}-{-# language ViewPatterns #-}+{-# language OverloadedStrings #-}+{-# language LambdaCase #-} ----------------------------------------------------------------------------- -- | -- Module : Data.SRTree.Internal --- Copyright : (c) Fabricio Olivetti 2021 - 2021+-- Copyright : (c) Fabricio Olivetti 2021 - 2024 -- License : BSD3 -- Maintainer : fabricio.olivetti@gmail.com -- Stability : experimental@@ -21,25 +22,22 @@ , Op(..) , param , var+ , constv , arity , getChildren+ , childrenOf+ , replaceChildren+ , getOperator , countNodes , countVarNodes , countConsts , countParams , countOccurrences- , deriveBy- , deriveByVar- , deriveByParam- , derivative- , forwardMode- , gradParamsFwd- , gradParamsRev- , evalFun- , evalOp- , inverseFunc- , evalTree+ , countUniqueTokens+ , numberOfVars+ , getIntConsts , relabelParams+ , relabelVars , constsToParam , floatConstsToParam , paramsToConst@@ -47,15 +45,11 @@ ) where -import Data.SRTree.Recursion ( Fix (..), cata, mutu, accu, cataM )--import qualified Data.Vector as V-import Data.Vector ((!))-import Control.Monad.State-import qualified Data.DList as DL-import Data.Bifunctor (second)--import Debug.Trace (trace)+import Control.Monad.State (MonadState (get), State, evalState, modify)+import Data.SRTree.Recursion (Fix (..), cata, cataM)+import qualified Data.Set as S+import Data.String (IsString (..))+import Text.Read (readMaybe) -- | Tree structure to be used with Symbolic Regression algorithms. -- This structure is a fixed point of a n-ary tree. @@ -63,12 +57,14 @@ Var Int -- ^ index of the variables | Param Int -- ^ index of the parameter | Const Double -- ^ constant value, can be converted to a parameter+ -- | IConst Int -- TODO: integer constant+ -- | RConst Ratio -- TODO: rational constant | Uni Function val -- ^ univariate function | Bin Op val val -- ^ binary operator deriving (Show, Eq, Ord, Functor) -- | Supported operators-data Op = Add | Sub | Mul | Div | Power+data Op = Add | Sub | Mul | Div | Power | PowerAbs | AQ deriving (Show, Read, Eq, Ord, Enum) -- | Supported functions@@ -88,10 +84,14 @@ | ACosh | ATanh | Sqrt+ | SqrtAbs | Cbrt | Square | Log+ | LogAbs | Exp+ | Recip+ | Cube deriving (Show, Read, Eq, Ord, Enum) -- | create a tree with a single node representing a variable@@ -102,6 +102,26 @@ param :: Int -> Fix SRTree param ix = Fix (Param ix) +-- | create a tree with a single node representing a constant value+constv :: Double -> Fix SRTree+constv x = Fix (Const x)++-- | the instance of `IsString` allows us to+-- create a tree using a more practical notation:+--+-- >>> :t "x0" + "t0" * sin("x1" * "t1")+-- Fix SRTree+--+instance IsString (Fix SRTree) where + fromString [] = error "empty string for SRTree"+ fromString ('x':ix) = case readMaybe ix of + Just iy -> Fix (Var iy)+ Nothing -> error "wrong format for variable. It should be xi where i is an index. Ex.: \"x0\", \"x1\"."+ fromString ('t':ix) = case readMaybe ix of + Just iy -> Fix (Param iy)+ Nothing -> error "wrong format for parameter. It should be ti where i is an index. Ex.: \"t0\", \"t1\"."+ fromString _ = error "A string can represent a variable or a parameter following the format xi or ti, respectivelly, where i is the index. Ex.: \"x0\", \"t0\"."+ instance Num (Fix SRTree) where Fix (Const 0) + r = r l + Fix (Const 0) = l@@ -142,6 +162,9 @@ l / r = Fix $ Bin Div l r {-# INLINE (/) #-} + recip = Fix . Uni Recip+ {-# INLINE recip #-}+ fromRational = Fix . Const . fromRational {-# INLINE fromRational #-} @@ -188,6 +211,29 @@ logBase l r = log l / log r {-# INLINE logBase #-} +instance Foldable SRTree where + foldMap f =+ \case+ Bin op l r -> f l <> f r+ Uni g t -> f t + _ -> mempty ++instance Traversable SRTree where + traverse f = + \case + Bin op l r -> Bin op <$> f l <*> f r + Uni g t -> Uni g <$> f t + Var ix -> pure (Var ix) + Param ix -> pure (Param ix) + Const x -> pure (Const x) + sequence =+ \case+ Bin op l r -> Bin op <$> l <*> r + Uni g t -> Uni g <$> t + Var ix -> pure (Var ix) + Param ix -> pure (Param ix) + Const x -> pure (Const x) + -- | Arity of the current node arity :: Fix SRTree -> Int arity = cata alg@@ -200,6 +246,10 @@ {-# INLINE arity #-} -- | Get the children of a node. Returns an empty list in case of a leaf node.+--+-- >>> map showExpr . getChildren $ "x0" + 2 +-- ["x0", 2]+-- getChildren :: Fix SRTree -> [Fix SRTree] getChildren (Fix (Var {})) = [] getChildren (Fix (Param {})) = []@@ -208,19 +258,53 @@ getChildren (Fix (Bin _ l r)) = [l, r] {-# INLINE getChildren #-} +-- | Get the children of an unfixed node +-- +childrenOf :: SRTree a -> [a] +childrenOf = + \case + Uni _ t -> [t] + Bin _ l r -> [l, r] + _ -> []++-- | replaces the children with elements from a list +replaceChildren :: [a] -> SRTree b -> SRTree a+replaceChildren [l, r] (Bin op _ _) = Bin op l r+replaceChildren [t] (Uni f _) = Uni f t+replaceChildren _ (Var ix) = Var ix+replaceChildren _ (Param ix) = Param ix+replaceChildren _ (Const x) = Const x+replaceChildren xs n = error "ERROR: trying to replace children with not enough elements."+{-# INLINE replaceChildren #-}++-- | returns a node containing the operator and () as children+getOperator :: SRTree a -> SRTree ()+getOperator (Bin op _ _) = Bin op () ()+getOperator (Uni f _) = Uni f ()+getOperator (Var ix) = Var ix+getOperator (Param ix) = Param ix+getOperator (Const x) = Const x+{-# INLINE getOperator #-}+ -- | Count the number of nodes in a tree.-countNodes :: Fix SRTree -> Int+--+-- >>> countNodes $ "x0" + 2+-- 3+countNodes :: Num a => Fix SRTree -> a countNodes = cata alg where- alg Var {} = 1- alg Param {} = 1- alg Const {} = 1- alg (Uni _ t) = 1 + t+ alg Var {} = 1+ alg Param {} = 1+ alg Const {} = 1+ alg (Uni _ t) = 1 + t alg (Bin _ l r) = 1 + l + r {-# INLINE countNodes #-} -- | Count the number of `Var` nodes-countVarNodes :: Fix SRTree -> Int+--+-- >>> countVarNodes $ "x0" + 2 * ("x0" - sin "x1")+-- 3+countVarNodes :: Num a => Fix SRTree -> a countVarNodes = cata alg where alg Var {} = 1@@ -231,7 +315,10 @@ {-# INLINE countVarNodes #-} -- | Count the number of `Param` nodes-countParams :: Fix SRTree -> Int+--+-- >>> countParams $ "x0" + "t0" * sin ("t1" + "x1") - "t0"+-- 3+countParams :: Num a => Fix SRTree -> a countParams = cata alg where alg Var {} = 0@@ -242,7 +329,10 @@ {-# INLINE countParams #-} -- | Count the number of const nodes-countConsts :: Fix SRTree -> Int+--+-- >>> countConsts $ "x0"* 2 + 3 * sin "x0"+-- 2+countConsts :: Num a => Fix SRTree -> a countConsts = cata alg where alg Var {} = 0@@ -253,7 +343,10 @@ {-# INLINE countConsts #-} -- | Count the occurrences of variable indexed as `ix`-countOccurrences :: Int -> Fix SRTree -> Int+--+-- >>> countOccurrences 0 $ "x0"* 2 + 3 * sin "x0" + "x1"+-- 2+countOccurrences :: Num a => Int -> Fix SRTree -> a countOccurrences ix = cata alg where alg (Var iy) = if ix == iy then 1 else 0@@ -263,302 +356,142 @@ alg (Bin _ l r) = l + r {-# INLINE countOccurrences #-} --- | Evaluates the tree given a vector of variable values, a vector of parameter values and a function that takes a Double and change to whatever type the variables have. This is useful when working with datasets of many values per variables.-evalTree :: (Num a, Floating a) => V.Vector a -> V.Vector Double -> (Double -> a) -> Fix SRTree -> a-evalTree xss params f = cata alg- where- alg (Var ix) = xss ! ix- alg (Param ix) = f $ params ! ix- alg (Const c) = f c- alg (Uni g t) = evalFun g t- alg (Bin op l r) = evalOp op l r-{-# INLINE evalTree #-}--evalOp :: Floating a => Op -> a -> a -> a-evalOp Add = (+)-evalOp Sub = (-)-evalOp Mul = (*)-evalOp Div = (/)-evalOp Power = (**)-{-# INLINE evalOp #-}--evalFun :: Floating a => Function -> a -> a-evalFun Id = id-evalFun Abs = abs-evalFun Sin = sin-evalFun Cos = cos-evalFun Tan = tan-evalFun Sinh = sinh-evalFun Cosh = cosh-evalFun Tanh = tanh-evalFun ASin = asin-evalFun ACos = acos-evalFun ATan = atan-evalFun ASinh = asinh-evalFun ACosh = acosh-evalFun ATanh = atanh-evalFun Sqrt = sqrt-evalFun Cbrt = cbrt-evalFun Square = (^2)-evalFun Log = log-evalFun Exp = exp-{-# INLINE evalFun #-}---- | Cubic root-cbrt :: Floating val => val -> val-cbrt x = signum x * abs x ** (1/3)-{-# INLINE cbrt #-}---- | Returns the inverse of a function. This is a partial function.-inverseFunc :: Function -> Function-inverseFunc Id = Id-inverseFunc Sin = ASin-inverseFunc Cos = ACos-inverseFunc Tan = ATan-inverseFunc Tanh = ATanh-inverseFunc ASin = Sin-inverseFunc ACos = Cos-inverseFunc ATan = Tan-inverseFunc ATanh = Tanh-inverseFunc Sqrt = Square-inverseFunc Square = Sqrt-inverseFunc Log = Exp-inverseFunc Exp = Log-inverseFunc x = error $ show x ++ " has no support for inverse function"-{-# INLINE inverseFunc #-}---- | Creates the symbolic partial derivative of a tree by variable `dx` (if `p` is `False`)--- or parameter `dx` (if `p` is `True`).-deriveBy :: Bool -> Int -> Fix SRTree -> Fix SRTree-deriveBy p dx = fst (mutu alg1 alg2)+-- | counts the number of unique tokens +--+-- >>> countUniqueTokens $ "x0" + ("x1" * "x0" - sin ("x0" ** 2))+-- 8+countUniqueTokens :: Num a => Fix SRTree -> a+countUniqueTokens = len . cata alg where- alg1 (Var ix) = if not p && ix == dx then 1 else 0- alg1 (Param ix) = if p && ix == dx then 1 else 0- alg1 (Const _) = 0- alg1 (Uni f t) = derivative f (snd t) * fst t- alg1 (Bin Add l r) = fst l + fst r- alg1 (Bin Sub l r) = fst l - fst r- alg1 (Bin Mul l r) = fst l * snd r + snd l * fst r- alg1 (Bin Div l r) = (fst l * snd r - snd l * fst r) / snd r ** 2- alg1 (Bin Power l r) = snd l ** (snd r - 1) * (snd r * fst l + snd l * log (snd l) * fst r)-- alg2 (Var ix) = var ix- alg2 (Param ix) = param ix- alg2 (Const c) = Fix (Const c)- alg2 (Uni f t) = Fix (Uni f $ snd t)- alg2 (Bin f l r) = Fix (Bin f (snd l) (snd r))--newtype Tape a = Tape { untape :: [a] } deriving (Show, Functor)--instance Num a => Num (Tape a) where- (Tape x) + (Tape y) = Tape $ zipWith (+) x y- (Tape x) - (Tape y) = Tape $ zipWith (-) x y- (Tape x) * (Tape y) = Tape $ zipWith (*) x y- abs (Tape x) = Tape (map abs x)- signum (Tape x) = Tape (map signum x)- fromInteger x = Tape [fromInteger x]- negate (Tape x) = Tape $ map (*(-1)) x-instance Floating a => Floating (Tape a) where- pi = Tape [pi]- exp (Tape x) = Tape (map exp x)- log (Tape x) = Tape (map log x)- sqrt (Tape x) = Tape (map sqrt x)- sin (Tape x) = Tape (map sin x)- cos (Tape x) = Tape (map cos x)- tan (Tape x) = Tape (map tan x)- asin (Tape x) = Tape (map asin x)- acos (Tape x) = Tape (map acos x)- atan (Tape x) = Tape (map atan x)- sinh (Tape x) = Tape (map sinh x)- cosh (Tape x) = Tape (map cosh x)- tanh (Tape x) = Tape (map tanh x)- asinh (Tape x) = Tape (map asinh x)- acosh (Tape x) = Tape (map acosh x)- atanh (Tape x) = Tape (map atanh x)- (Tape x) ** (Tape y) = Tape $ zipWith (**) x y-instance Fractional a => Fractional (Tape a) where- fromRational x = Tape [fromRational x]- (Tape x) / (Tape y) = Tape $ zipWith (/) x y- recip (Tape x) = Tape $ map recip x+ len (a, b, c, d, e) = fromIntegral $ length a + length b + length c + length d + length e+ alg (Var ix) = (mempty, mempty, S.singleton ix, mempty, mempty)+ alg (Param _) = (mempty, mempty, mempty, S.singleton 1, mempty)+ alg (Const _) = (mempty, mempty, mempty, mempty, S.singleton 1)+ alg (Uni f t) = (mempty, S.singleton f, mempty, mempty, mempty) <> t+ alg (Bin op l r) = (S.singleton op, mempty, mempty, mempty, mempty) <> l <> r+{-# INLINE countUniqueTokens #-} --- | Calculates the numerical derivative of a tree using forward mode--- provided a vector of variable values `xss`, a vector of parameter values `theta` and--- a function that changes a Double value to the type of the variable values.-forwardMode :: (Show a, Num a, Floating a) => V.Vector a -> V.Vector Double -> (Double -> a) -> Fix SRTree -> [a]-forwardMode xss theta f = untape . fst (mutu alg1 alg2)+-- | return the number of unique variables +-- +-- >>> numberOfVars $ "x0" + 2 * ("x0" - sin "x1")+-- 2+numberOfVars :: Num a => Fix SRTree -> a+numberOfVars = fromIntegral . S.size . cata alg where- n = V.length theta- repMat v = Tape $ replicate n v- zeroes = repMat $ f 0- twos = repMat $ f 2- tapeXs = [repMat $ xss ! ix | ix <- [0 .. V.length xss - 1]]- tapeTheta = [repMat $ f (theta ! ix) | ix <- [0 .. n - 1]]- paramVec = [ Tape [if ix==iy then f 1 else f 0 | iy <- [0 .. n-1]] | ix <- [0 .. n-1] ]-- alg1 (Var ix) = zeroes- alg1 (Param ix) = paramVec !! ix- alg1 (Const _) = zeroes- alg1 (Uni f t) = derivative f (snd t) * fst t- alg1 (Bin Add l r) = fst l + fst r- alg1 (Bin Sub l r) = fst l - fst r- alg1 (Bin Mul l r) = (fst l * snd r) + (snd l * fst r)- alg1 (Bin Div l r) = ((fst l * snd r) - (snd l * fst r)) / snd r ** twos- alg1 (Bin Power l r) = snd l ** (snd r - 1) * ((snd r * fst l) + (snd l * log (snd l) * fst r))-- alg2 (Var ix) = tapeXs !! ix- alg2 (Param ix) = tapeTheta !! ix- alg2 (Const c) = repMat $ f c- alg2 (Uni g t) = fmap (evalFun g) (snd t)- alg2 (Bin op l r) = evalOp op (snd l) (snd r)+ alg (Uni f t) = t+ alg (Bin op l r) = l <> r+ alg (Var ix) = S.singleton ix+ alg _ = mempty+{-# INLINE numberOfVars #-} --- | The function `gradParams` calculates the numerical gradient of the tree and evaluates the tree at the same time. It assumes that each parameter has a unique occurrence in the expression. This should be significantly faster than `forwardMode`.-gradParamsFwd :: (Show a, Num a, Floating a) => V.Vector a -> V.Vector Double -> (Double -> a) -> Fix SRTree -> (a, [a])-gradParamsFwd xss theta f = second DL.toList . cata alg+-- | returns the integer constants. We assume an integer constant +-- as those values in which `floor x == ceiling x`.+--+-- >>> getIntConsts $ "x0" + 2 * "x1" ** 3 - 3.14+-- [2.0,3.0]+getIntConsts :: Fix SRTree -> [Double]+getIntConsts = cata alg where- n = V.length theta-- alg (Var ix) = (xss ! ix, DL.empty)- alg (Param ix) = (f $ theta ! ix, DL.singleton 1)- alg (Const c) = (f c, DL.empty)- alg (Uni f (v, gs)) = let v' = evalFun f v- dv = derivative f v- in (v', DL.map (*dv) gs)- alg (Bin Add (v1, l) (v2, r)) = (v1+v2, DL.append l r)- alg (Bin Sub (v1, l) (v2, r)) = (v1-v2, DL.append l (DL.map negate r))- alg (Bin Mul (v1, l) (v2, r)) = (v1*v2, DL.append (DL.map (*v2) l) (DL.map (*v1) r))- alg (Bin Div (v1, l) (v2, r)) = let dv = (-v1/v2^2) - in (v1/v2, DL.append (DL.map (/v2) l) (DL.map (*dv) r))- alg (Bin Power (v1, l) (v2, r)) = let dv1 = v1 ** (v2 - 1)- dv2 = v1 * log v1- in (v1 ** v2, DL.map (*dv1) (DL.append (DL.map (*v2) l) (DL.map (*dv2) r)))--data TupleF a b = S a | T a b | B a b b deriving Functor -- hi, I'm a tree-type Tuple a = Fix (TupleF a)+ alg (Uni f t) = t+ alg (Bin op l r) = l <> r+ alg (Var ix) = []+ alg (Param _) = []+ alg (Const x) = [x | floor x == ceiling x]+{-# INLINE getIntConsts #-} -gradParamsRev :: forall a . (Show a, Num a, Floating a) => V.Vector a -> V.Vector Double -> (Double -> a) -> Fix SRTree -> (a, [a])-gradParamsRev xss theta f t = (getTop fwdMode, DL.toList g)+-- | Relabel the parameters indices incrementaly starting from 0+--+-- >>> showExpr . relabelParams $ "x0" + "t0" * sin ("t1" + "x1") - "t0" +-- "x0" + "t0" * sin ("t1" + "x1") - "t2" +relabelParams :: Fix SRTree -> Fix SRTree+relabelParams t = cataM leftToRight alg t `evalState` 0 where- fwdMode = cata forward t- g = accu reverse combine t (1, fwdMode)-- oneTpl x = Fix $ S x- tuple x y = Fix $ T x y- branch x y z = Fix $ B x y z- getTop (Fix (S x)) = x- getTop (Fix (T x y)) = x- getTop (Fix (B x y z)) = x- unCons (Fix (T x y)) = y- getBranches (Fix (B x y z)) = (y,z)-- forward (Var ix) = oneTpl (xss ! ix)- forward (Param ix) = oneTpl (f $ theta ! ix)- forward (Const c) = oneTpl (f c)- forward (Uni f t) = let v = getTop t- in tuple (evalFun f v) t- forward (Bin op l r) = let vl = getTop l- vr = getTop r- in branch (evalOp op vl vr) l r-- reverse (Var ix) (dx, _) = Var ix- reverse (Param ix) (dx, _) = Param ix- reverse (Const v) (dx, _) = Const v- reverse (Uni f t) (dx, unCons -> v) = Uni f (t, (dx * (derivative f $ getTop v), v))- reverse (Bin op l r) (dx, getBranches -> (vl, vr)) = let (dxl, dxr) = diff op dx (getTop vl) (getTop vr)- in Bin op (l, (dxl, vl)) (r, (dxr, vr))-- diff Add dx vl vr = (dx, dx)- diff Sub dx vl vr = (dx, negate dx)- diff Mul dx vl vr = (dx * vr, dx * vl)- diff Div dx vl vr = (dx / vr, dx * (-vl/vr^2))- diff Power dx vl vr = let dxl = dx * vl ** (vr - 1)- dv2 = vl * log vl- in (dxl * vr, dxl * dv2)-- combine (Var ix) s = DL.empty- combine (Param ix) s = DL.singleton $ fst s- combine (Const c) s = DL.empty- combine (Uni _ gs) s = gs- combine (Bin op l r) s = DL.append l r--derivative :: Floating a => Function -> a -> a-derivative Id = const 1-derivative Abs = \x -> x / abs x-derivative Sin = cos-derivative Cos = negate.sin-derivative Tan = recip . (**2.0) . cos-derivative Sinh = cosh-derivative Cosh = sinh-derivative Tanh = (1-) . (**2.0) . tanh-derivative ASin = recip . sqrt . (1-) . (^2)-derivative ACos = negate . recip . sqrt . (1-) . (^2)-derivative ATan = recip . (1+) . (^2)-derivative ASinh = recip . sqrt . (1+) . (^2)-derivative ACosh = \x -> 1 / (sqrt (x-1) * sqrt (x+1))-derivative ATanh = recip . (1-) . (^2)-derivative Sqrt = recip . (2*) . sqrt-derivative Cbrt = recip . (3*) . cbrt . (^2)-derivative Square = (2*)-derivative Exp = exp-derivative Log = recip-{-# INLINE derivative #-}---- | Symbolic derivative by a variable-deriveByVar :: Int -> Fix SRTree -> Fix SRTree-deriveByVar = deriveBy False+ -- | leftToRight (left to right) defines the sequence of processing+ leftToRight (Uni f mt) = Uni f <$> mt;+ leftToRight (Bin f ml mr) = Bin f <$> ml <*> mr+ leftToRight (Var ix) = pure (Var ix)+ leftToRight (Param ix) = pure (Param ix)+ leftToRight (Const c) = pure (Const c) --- | Symbolic derivative by a parameter-deriveByParam :: Int -> Fix SRTree -> Fix SRTree-deriveByParam = deriveBy True+ -- | any time we reach a Param ix, it replaces ix with current state+ -- and increments one to the state.+ alg :: SRTree (Fix SRTree) -> State Int (Fix SRTree)+ alg (Var ix) = pure $ var ix+ alg (Param ix) = do iy <- get; modify (+1); pure (param iy)+ alg (Const c) = pure $ Fix $ Const c+ alg (Uni f t) = pure $ Fix (Uni f t)+ alg (Bin f l r) = pure $ Fix (Bin f l r) --- | Relabel the parameters incrementaly starting from 0-relabelParams :: Fix SRTree -> Fix SRTree-relabelParams t = cataM lTor alg t `evalState` 0+-- | Relabel the parameters indices incrementaly starting from 0+--+-- >>> showExpr . relabelParams $ "x0" + "t0" * sin ("t1" + "x1") - "t0"+-- "x0" + "t0" * sin ("t1" + "x1") - "t2"+relabelVars :: Fix SRTree -> Fix SRTree+relabelVars t = cataM leftToRight alg t `evalState` 0 where- lTor (Uni f mt) = Uni f <$> mt;- lTor (Bin f ml mr) = Bin f <$> ml <*> mr- lTor (Var ix) = pure (Var ix)- lTor (Param ix) = pure (Param ix)- lTor (Const c) = pure (Const c)+ -- | leftToRight (left to right) defines the sequence of processing+ leftToRight (Uni f mt) = Uni f <$> mt;+ leftToRight (Bin f ml mr) = Bin f <$> ml <*> mr+ leftToRight (Var ix) = pure (Var ix)+ leftToRight (Param ix) = pure (Param ix)+ leftToRight (Const c) = pure (Const c) + -- | any time we reach a Param ix, it replaces ix with current state+ -- and increments one to the state. alg :: SRTree (Fix SRTree) -> State Int (Fix SRTree)- alg (Var ix) = pure $ var ix- alg (Param ix) = do iy <- get; modify (+1); pure (param iy)- alg (Const c) = pure $ Fix $ Const c- alg (Uni f t) = pure $ Fix (Uni f t)+ alg (Var ix) = do iy <- get; modify (+1); pure (var iy)+ alg (Param ix) = pure $ param ix+ alg (Const c) = pure $ Fix $ Const c+ alg (Uni f t) = pure $ Fix (Uni f t) alg (Bin f l r) = pure $ Fix (Bin f l r) -- | Change constant values to a parameter, returning the changed tree and a list -- of parameter values+--+-- >>> snd . constsToParam $ "x0" * 2 + 3.14 * sin (5 * "x1")+-- [2.0,3.14,5.0] constsToParam :: Fix SRTree -> (Fix SRTree, [Double]) constsToParam = first relabelParams . cata alg where first f (x, y) = (f x, y) - alg (Var ix) = (Fix $ Var ix, [])- alg (Param ix) = (Fix $ Param ix, [1.0])- alg (Const c) = (Fix $ Param 0, [c])- alg (Uni f t) = (Fix $ Uni f (fst t), snd t)+ -- | If the tree already contains a parameter+ -- it will return a default value of 1.0+ -- whenever it finds a constant, it changes that+ -- to a parameter and adds its content to the singleton list+ alg (Var ix) = (Fix $ Var ix, [])+ alg (Param ix) = (Fix $ Param ix, [1.0])+ alg (Const c) = (Fix $ Param 0, [c])+ alg (Uni f t) = (Fix $ Uni f (fst t), snd t) alg (Bin f l r) = (Fix (Bin f (fst l) (fst r)), snd l <> snd r) -- | Same as `constsToParam` but does not change constant values that -- can be converted to integer without loss of precision+--+-- >>> snd . floatConstsToParam $ "x0" * 2 + 3.14 * sin (5 * "x1")+-- [3.14] floatConstsToParam :: Fix SRTree -> (Fix SRTree, [Double]) floatConstsToParam = first relabelParams . cata alg where- first f (x, y) = (f x, y)+ first f (x, y) = (f x, y)+ combine f (x, y) (z, w) = (f x z, y <> w)+ isInt x = floor x == ceiling x - alg (Var ix) = (Fix $ Var ix, [])- alg (Param ix) = (Fix $ Param ix, [1.0])- alg (Const c) = if floor c == ceiling c then (Fix $ Const c, []) else (Fix $ Param 0, [c])- alg (Uni f t) = (Fix $ Uni f (fst t), snd t)- alg (Bin f l r) = (Fix (Bin f (fst l) (fst r)), snd l <> snd r)+ alg (Var ix) = (var ix, [])+ alg (Param ix) = (param ix, [1.0])+ alg (Const c) = if isInt c then (constv c, []) else (param 0, [c])+ alg (Uni f t) = first (Fix . Uni f) t -- (Fix $ Uni f (fst t), snd t)+ alg (Bin f l r) = combine ((Fix .) . Bin f) l r -- (Fix (Bin f (fst l) (fst r)), snd l <> snd r) -- | Convert the parameters into constants in the tree+--+-- >>> showExpr . paramsToConst [1.1, 2.2, 3.3] $ "x0" + "t0" * sin ("t1" * "x0" - "t2")+-- x0 + 1.1 * sin(2.2 * x0 - 3.3) paramsToConst :: [Double] -> Fix SRTree -> Fix SRTree paramsToConst theta = cata alg where- alg (Var ix) = Fix $ Var ix- alg (Param ix) = Fix $ Const (theta !! ix)- alg (Const c) = Fix $ Const c- alg (Uni f t) = Fix $ Uni f t+ alg (Var ix) = Fix $ Var ix+ alg (Param ix) = Fix $ Const (theta !! ix)+ alg (Const c) = Fix $ Const c+ alg (Uni f t) = Fix $ Uni f t alg (Bin f l r) = Fix $ Bin f l r
src/Data/SRTree/Print.hs view
@@ -1,7 +1,9 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE LambdaCase #-} ----------------------------------------------------------------------------- -- | -- Module : Data.SRTree.Print --- Copyright : (c) Fabricio Olivetti 2021 - 2021+-- Copyright : (c) Fabricio Olivetti 2021 - 2024 -- License : BSD3 -- Maintainer : fabricio.olivetti@gmail.com -- Stability : experimental@@ -12,7 +14,9 @@ ----------------------------------------------------------------------------- module Data.SRTree.Print ( showExpr+ , showExprWithVars , printExpr+ , printExprWithVars , showTikz , printTikz , showPython@@ -22,42 +26,89 @@ ) where -import Control.Monad.Reader ( asks, runReader, Reader )-import Data.Char ( toLower )-+import Control.Monad.Reader (Reader, asks, runReader)+import Data.Char (toLower) import Data.SRTree.Internal-import Data.SRTree.Recursion+import Data.SRTree.Recursion (cata) +-- | converts a tree with protected operators to+-- a conventional math tree+removeProtection :: Fix SRTree -> Fix SRTree+removeProtection = cata $+ \case+ Var ix -> Fix (Var ix)+ Param ix -> Fix (Param ix)+ Const x -> Fix (Const x)+ Uni SqrtAbs t -> sqrt (abs t)+ Uni LogAbs t -> log (abs t)+ Uni Cube t -> t ** 3+ Uni f t -> Fix (Uni f t)+ Bin AQ l r -> l / sqrt (1 + r*r)+ Bin PowerAbs l r -> abs l ** r+ Bin op l r -> Fix (Bin op l r)++-- | convert a tree into a string in math notation +--+-- >>> showExpr $ "x0" + sin ( tanh ("t0" + 2) )+-- "(x0 + Sin(Tanh((t0 + 2.0))))" showExpr :: Fix SRTree -> String-showExpr = cata alg- where- alg (Var ix) = 'x' : show ix- alg (Param ix) = 't' : show ix- alg (Const c) = show c- alg (Bin op l r) = concat ["(", l, " ", showOp op, " ", r, ")"]- alg (Uni f t) = concat [show f, "(", t, ")"]+showExpr = cata alg . removeProtection+ where alg = \case+ Var ix -> 'x' : show ix+ Param ix -> 't' : show ix+ Const c -> show c+ Bin op l r -> concat ["(", l, " ", showOp op, " ", r, ")"]+ Uni f t -> concat [show f, "(", t, ")"] +-- | convert a tree into a string in math notation+-- given named vars.+--+-- >>> showExprWithVar ["mu", "eps"] $ "x0" + sin ( "x1" * tanh ("t0" + 2) )+-- "(mu + Sin(Tanh(eps * (t0 + 2.0))))"+showExprWithVars :: [String] -> Fix SRTree -> String+showExprWithVars varnames = cata alg . removeProtection+ where alg = \case+ Var ix -> varnames !! ix+ Param ix -> 't' : show ix+ Const c -> show c+ Bin op l r -> concat ["(", l, " ", showOp op, " ", r, ")"]+ Uni f t -> concat [show f, "(", t, ")"]++-- | prints the expression printExpr :: Fix SRTree -> IO () printExpr = putStrLn . showExpr +-- | prints the expression+printExprWithVars :: [String] -> Fix SRTree -> IO ()+printExprWithVars varnames = putStrLn . showExprWithVars varnames++-- how to display an operator +showOp :: Op -> String showOp Add = "+" showOp Sub = "-" showOp Mul = "*" showOp Div = "/" showOp Power = "^"+showOp AQ = "_aq_"+showOp PowerAbs = "||^" {-# INLINE showOp #-} -- | Displays a tree as a numpy compatible expression.+--+-- >>> showPython $ "x0" + sin ( tanh ("t0" + 2) )+-- "(x[:, 0] + np.sin(np.tanh((t[:, 0] + 2.0))))" showPython :: Fix SRTree -> String-showPython = cata alg+showPython = cata alg . removeProtection where- alg (Var ix) = concat ["x[:, ", show ix, "]"]- alg (Param ix) = concat ["t[:, ", show ix, "]"]- alg (Const c) = show c- alg (Bin Power l r) = concat [l, " ** ", r]- alg (Bin op l r) = concat ["(", l, " ", showOp op, " ", r, ")"]- alg (Uni f t) = concat [pyFun f, "(", t, ")"]+ alg = \case+ Var ix -> concat ["x[:, ", show ix, "]"]+ Param ix -> concat ["t[:, ", show ix, "]"]+ Const c -> show c+ Bin Power l r -> concat [l, " ** ", r]+ Bin op l r -> concat ["(", l, " ", showOp op, " ", r, ")"]+ Uni f t -> concat [pyFun f, "(", t, ")"] + pyFun Id = "" pyFun Abs = "np.abs" pyFun Sin = "np.sin"@@ -76,39 +127,49 @@ pyFun Square = "np.square" pyFun Log = "np.log" pyFun Exp = "np.exp"+ pyFun Cbrt = "np.cbrt"+ pyFun Recip = "np.reciprocal" +-- | print the expression in numpy notation printPython :: Fix SRTree -> IO () printPython = putStrLn . showPython --- | Displays a tree as a sympy compatible expression.+-- | Displays a tree as a LaTeX compatible expression.+--+-- >>> showLatex $ "x0" + sin ( tanh ("t0" + 2) )+-- "\\left(x_{, 0} + \\operatorname{sin}(\\operatorname{tanh}(\\left(\\theta_{, 0} + 2.0\\right)))\\right)" showLatex :: Fix SRTree -> String-showLatex = cata alg+showLatex = cata alg . removeProtection where- alg (Var ix) = concat ["x_{, ", show ix, "}"]- alg (Param ix) = concat ["\\theta_{, ", show ix, "}"]- alg (Const c) = show c- alg (Bin Power l r) = concat [l, "^{", r, "}"]- alg (Bin op l r) = concat ["\\left(", l, " ", showOp op, " ", r, "\\right)"]- alg (Uni Abs t) = concat ["\\left |", t, "\\right |"]- alg (Uni f t) = concat [showLatexFun f, "(", t, ")"]+ alg = \case+ Var ix -> concat ["x_{, ", show ix, "}"]+ Param ix -> concat ["\\theta_{, ", show ix, "}"]+ Const c -> show c+ Bin Power l r -> concat [l, "^{", r, "}"]+ Bin op l r -> concat ["\\left(", l, " ", showOp op, " ", r, "\\right)"]+ Uni Abs t -> concat ["\\left |", t, "\\right |"]+ Uni f t -> concat [showLatexFun f, "(", t, ")"] showLatexFun :: Function -> String showLatexFun f = mconcat ["\\operatorname{", map toLower $ show f, "}"] {-# INLINE showLatexFun #-} +-- | prints expression in LaTeX notation. printLatex :: Fix SRTree -> IO () printLatex = putStrLn . showLatex -- | Displays a tree in Tikz format showTikz :: Fix SRTree -> String-showTikz = cata alg+showTikz = cata alg . removeProtection where+ alg = \case+ Var ix -> concat ["[$x_{, ", show ix, "}$]\n"]+ Param ix -> concat ["[$\\theta_{, ", show ix, "}$]\n"]+ Const c -> concat ["[$", show (roundN 2 c), "$]\n"]+ Bin op l r -> concat ["[", showOpTikz op, l, r, "]\n"]+ Uni f t -> concat ["[", map toLower $ show f, t, "]\n"]+ roundN n x = let ten = 10^n in (/ ten) . fromIntegral . round $ x*ten- alg (Var ix) = concat ["[$x_{, ", show ix, "}$]\n"]- alg (Param ix) = concat ["[$\\theta_{, ", show ix, "}$]\n"]- alg (Const c) = concat ["[$", show (roundN 2 c), "$]\n"]- alg (Bin op l r) = concat ["[", showOpTikz op, l, r, "]\n"]- alg (Uni f t) = concat ["[", map toLower $ show f, t, "]\n"] showOpTikz Add = "+\n" showOpTikz Sub = "-\n"@@ -116,4 +177,6 @@ showOpTikz Div = "÷\n" showOpTikz Power = "\\^{}\n" +-- | prints the tree in TikZ format +printTikz :: Fix SRTree -> IO () printTikz = putStrLn . showTikz
src/Data/SRTree/Random.hs view
@@ -2,7 +2,7 @@ ----------------------------------------------------------------------------- -- | -- Module : Data.SRTree.Random --- Copyright : (c) Fabricio Olivetti 2021 - 2021+-- Copyright : (c) Fabricio Olivetti 2021 - 2024 -- License : BSD3 -- Maintainer : fabricio.olivetti@gmail.com -- Stability : experimental@@ -29,13 +29,11 @@ ) where -import System.Random -import Control.Monad.State -import Control.Monad.Reader +import Control.Monad.Reader (ReaderT, asks, runReaderT)+import Control.Monad.State.Strict ( MonadState(state), MonadTrans(lift), StateT ) import Data.Maybe (fromJust)- import Data.SRTree.Internal-import Data.SRTree.Recursion+import System.Random (Random (random, randomR), StdGen, mkStdGen) -- * Class definition of properties that a certain parameter type has. --@@ -90,10 +88,10 @@ {-# INLINE replaceChild #-} -- Replace the children of a binary tree.-replaceChildren :: Fix SRTree -> Fix SRTree -> Fix SRTree -> Maybe (Fix SRTree)-replaceChildren (Fix (Bin f _ _)) l r = Just $ Fix (Bin f l r)-replaceChildren _ _ _ = Nothing-{-# INLINE replaceChildren #-}+replaceFixChildren :: Fix SRTree -> Fix SRTree -> Fix SRTree -> Maybe (Fix SRTree)+replaceFixChildren (Fix (Bin f _ _)) l r = Just $ Fix (Bin f l r)+replaceFixChildren _ _ _ = Nothing+{-# INLINE replaceFixChildren #-} -- | RndTree is a Monad Transformer to generate random trees of type `SRTree ix val` -- given the parameters `p ix val` using the random number generator `StdGen`.@@ -149,6 +147,11 @@ 6 -> pure . Fix $ Bin Power 0 0 -- | Returns a random tree with a limited budget, the parameter `p` must have every property.+--+-- >>> let treeGen = runReaderT (randomTree 12) (P [0,1] (-10, 10) (2, 3) [Log, Exp])+-- >>> tree <- evalStateT treeGen (mkStdGen 52)+-- >>> showExpr tree+-- "(-2.7631152121655838 / Exp((x0 / ((x0 * -7.681722660704317) - Log(3.378309080134594)))))" randomTree :: HasEverything p => Int -> RndTree p randomTree 0 = do coin <- lift toss@@ -160,9 +163,14 @@ fromJust <$> case arity node of 0 -> pure $ Just node 1 -> replaceChild node <$> randomTree (budget - 1)- 2 -> replaceChildren node <$> randomTree (budget `div` 2) <*> randomTree (budget `div` 2)+ 2 -> replaceFixChildren node <$> randomTree (budget `div` 2) <*> randomTree (budget `div` 2) -- | Returns a random tree with a approximately a number `n` of nodes, the parameter `p` must have every property.+--+-- >>> let treeGen = runReaderT (randomTreeBalanced 10) (P [0,1] (-10, 10) (2, 3) [Log, Exp])+-- >>> tree <- evalStateT treeGen (mkStdGen 42)+-- >>> showExpr tree+-- "Exp(Log((((7.784360517385774 * x0) - (3.6412224491658223 ^ x1)) ^ ((x0 ^ -4.09764995657091) + Log(-7.710216839988497)))))" randomTreeBalanced :: HasEverything p => Int -> RndTree p randomTreeBalanced n | n <= 1 = do coin <- lift toss@@ -173,4 +181,4 @@ node <- randomNonTerminal fromJust <$> case arity node of 1 -> replaceChild node <$> randomTreeBalanced (n - 1)- 2 -> replaceChildren node <$> randomTreeBalanced (n `div` 2) <*> randomTreeBalanced (n `div` 2) + 2 -> replaceFixChildren node <$> randomTreeBalanced (n `div` 2) <*> randomTreeBalanced (n `div` 2)
src/Data/SRTree/Recursion.hs view
@@ -1,5 +1,17 @@ {-# language RankNTypes #-} {-# language DeriveFunctor #-}+-----------------------------------------------------------------------------+-- |+-- Module : Data.SRTree.Recursion +-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : FlexibleInstances, DeriveFunctor, ScopedTypeVariables+--+-- Recursion schemes+--+----------------------------------------------------------------------------- module Data.SRTree.Recursion where import Control.Monad ( (>=>) )
+ src/Text/ParseSR.hs view
@@ -0,0 +1,302 @@+{-# language OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module : Text.ParseSR+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : ConstraintKinds+--+-- Functions to parse a string representing an expression+--+-----------------------------------------------------------------------------+module Text.ParseSR ( parseSR, showOutput, SRAlgs(..), Output(..) ) + where++import Control.Applicative ((<|>))+import Data.Attoparsec.ByteString.Char8+import Data.Attoparsec.Expr+import qualified Data.ByteString.Char8 as B+import Data.Char (toLower)+import Data.List (sortOn)+import Data.SRTree+import qualified Data.SRTree.Print as P+import Debug.Trace (trace)++-- * Data types++-- | Parser of a symbolic regression tree with `Int` variable index and+-- numerical values represented as `Double`. The numerical values type+-- can be changed with `fmap`.+type ParseTree = Parser (Fix SRTree)++-- * Data types and caller functions++-- | Supported algorithms.+data SRAlgs = TIR | HL | OPERON | BINGO | GOMEA | PYSR | SBP | EPLEX deriving (Show, Read, Enum, Bounded)++-- | Supported outputs.+data Output = PYTHON | MATH | TIKZ | LATEX deriving (Show, Read, Enum, Bounded)++-- | Returns the corresponding function from Data.SRTree.Print for a given `Output`.+showOutput :: Output -> Fix SRTree -> String+showOutput PYTHON = P.showPython+showOutput MATH = P.showExpr+showOutput TIKZ = P.showTikz+showOutput LATEX = P.showLatex++-- | Calls the corresponding parser for a given `SRAlgs`+--+-- >>> fmap (showOutput MATH) $ parseSR OPERON "lambda,theta" False "lambda ^ 2 - sin(theta*3*lambda)"+-- Right "((x0 ^ 2.0) - Sin(((x1 * 3.0) * x0)))"+parseSR :: SRAlgs -> B.ByteString -> Bool -> B.ByteString -> Either String (Fix SRTree)+parseSR HL header reparam = eitherResult . (`feed` "") . parse (parseHL reparam $ splitHeader header) . putEOL . B.strip+parseSR BINGO header reparam = eitherResult . (`feed` "") . parse (parseBingo reparam $ splitHeader header) . putEOL . B.strip+parseSR TIR header reparam = eitherResult . (`feed` "") . parse (parseTIR reparam $ splitHeader header) . putEOL . B.strip+parseSR OPERON header reparam = eitherResult . (`feed` "") . parse (parseOperon reparam $ splitHeader header) . putEOL . B.strip+parseSR GOMEA header reparam = eitherResult . (`feed` "") . parse (parseGOMEA reparam $ splitHeader header) . putEOL . B.strip+parseSR SBP header reparam = eitherResult . (`feed` "") . parse (parseGOMEA reparam $ splitHeader header) . putEOL . B.strip+parseSR EPLEX header reparam = eitherResult . (`feed` "") . parse (parseGOMEA reparam $ splitHeader header) . putEOL . B.strip+parseSR PYSR header reparam = eitherResult . (`feed` "") . parse (parsePySR reparam $ splitHeader header) . putEOL . B.strip++eitherResult' :: Show r => Result r -> Either String r+eitherResult' res = trace (show res) $ eitherResult res++-- * Parsers++-- | Creates a parser for a binary operator+binary :: B.ByteString -> (a -> a -> a) -> Assoc -> Operator B.ByteString a+binary name fun = Infix (do{ string (B.cons ' ' (B.snoc name ' ')) <|> string name; pure fun })++-- | Creates a parser for a unary function+prefix :: B.ByteString -> (a -> a) -> Operator B.ByteString a+prefix name fun = Prefix (do{ string name; pure fun })++-- | Envelopes the parser in parens+parens :: Parser a -> Parser a+parens e = do{ string "("; e' <- e; string ")"; pure e' } <?> "parens"++-- | Parse an expression using a user-defined parser given by the `Operator` lists containing+-- the name of the functions and operators of that SR algorithm, a list of parsers `binFuns` for binary functions+-- a parser `var` for variables, a boolean indicating whether to change floating point values to free+-- parameters variables, and a list of variable names with their corresponding indexes.+parseExpr :: [[Operator B.ByteString (Fix SRTree)]] -> [ParseTree -> ParseTree] -> ParseTree -> Bool -> [(B.ByteString, Int)] -> ParseTree+parseExpr table binFuns var reparam header = do e <- relabelParams <$> expr+ many1' space+ pure e+ where+ term = parens expr <|> enclosedAbs expr <|> choice (map ($ expr) binFuns) <|> coef <|> varC <?> "term"+ expr = buildExpressionParser table term+ coef = if reparam + then do eNumber <- intOrDouble+ case eNumber of+ Left x -> pure $ fromIntegral x+ Right _ -> pure $ param 0+ else Fix . Const <$> signed double <?> "const"+ varC = if null header+ then var+ else var <|> varHeader++ varHeader = choice $ map (uncurry getParserVar) $ sortOn (negate . B.length . fst) header+ getParserVar k v = (string k <|> enveloped k) >> pure (Fix $ Var v)+ enveloped s = (char ' ' <|> char '(') >> string s >> (char ' ' <|> char ')') >> pure ""++enumerate :: [a] -> [(a, Int)]+enumerate = (`zip` [0..])++splitHeader :: B.ByteString -> [(B.ByteString, Int)]+splitHeader = enumerate . B.split ','++-- | Tries to parse as an `Int`, if it fails, +-- parse as a Double.+intOrDouble :: Parser (Either Int Double)+intOrDouble = eitherP parseInt (signed double)+ where+ parseInt :: Parser Int+ parseInt = do x <- signed decimal+ c <- peekChar+ case c of + Just '.' -> digit >> pure 0+ Just 'e' -> digit >> pure 0+ Just 'E' -> digit >> pure 0+ _ -> pure x++putEOL :: B.ByteString -> B.ByteString+putEOL bs | B.last bs == '\n' = bs+ | otherwise = B.snoc bs '\n'++-- * Special case functions++-- | analytic quotient+aq :: Fix SRTree -> Fix SRTree -> Fix SRTree+aq x y = x / sqrt (1 + y ** 2)++log1p :: Fix SRTree -> Fix SRTree+log1p x = log (1 + x)++log10 :: Fix SRTree -> Fix SRTree+log10 x = log x / log 10++log2 :: Fix SRTree -> Fix SRTree+log2 x = log x / log 2++cbrt :: Fix SRTree -> Fix SRTree+cbrt x = x ** (1/3)++-- Parse `abs` functions as | x |+enclosedAbs :: Num a => Parser a -> Parser a+enclosedAbs expr = do char '|'+ e <- expr+ char '|'+ pure $ abs e++-- | Parser for binary functions+binFun :: B.ByteString -> (a -> a -> a) -> Parser a -> Parser a+binFun name f expr = do string name+ many' space >> char '(' >> many' space+ e1 <- expr+ many' space >> char ',' >> many' space -- many' space >> char ',' >> many' space+ e2 <- expr+ many' space >> char ')'+ pure $ f e1 e2 ++-- * Custom parsers for SR algorithms++-- | parser for Transformation-Interaction-Rational.+parseTIR :: Bool -> [(B.ByteString, Int)] -> ParseTree+parseTIR = parseExpr (prefixOps : binOps) binFuns var+ where+ binFuns = [ ]+ prefixOps = map (uncurry prefix)+ [ ("id", id), ("abs", abs)+ , ("sinh", sinh), ("cosh", cosh), ("tanh", tanh)+ , ("sin", sin), ("cos", cos), ("tan", tan)+ , ("asinh", asinh), ("acosh", acosh), ("atanh", atanh)+ , ("asin", asin), ("acos", acos), ("atan", atan)+ , ("sqrt", sqrt), ("cbrt", cbrt), ("square", (**2))+ , ("log", log), ("exp", exp)+ , ("Id", id), ("Abs", abs)+ , ("Sinh", sinh), ("Cosh", cosh), ("Tanh", tanh)+ , ("Sin", sin), ("Cos", cos), ("Tan", tan)+ , ("ASinh", asinh), ("ACosh", acosh), ("ATanh", atanh)+ , ("ASin", asin), ("ACos", acos), ("ATan", atan)+ , ("Sqrt", sqrt), ("Cbrt", cbrt), ("Square", (**2))+ , ("Log", log), ("Exp", exp)+ ]+ binOps = [[binary "^" (**) AssocLeft], [binary "**" (**) AssocLeft]+ , [binary "*" (*) AssocLeft, binary "/" (/) AssocLeft]+ , [binary "+" (+) AssocLeft, binary "-" (-) AssocLeft]+ ]+ var = do char 'x'+ ix <- decimal+ pure $ Fix $ Var ix+ <?> "var"++-- | parser for Operon.+parseOperon :: Bool -> [(B.ByteString, Int)] -> ParseTree+parseOperon = parseExpr (prefixOps : binOps) binFuns var+ where+ binFuns = [ binFun "pow" (**) ]+ prefixOps = map (uncurry prefix)+ [ ("abs", abs), ("cbrt", cbrt)+ , ("acos", acos), ("cosh", cosh), ("cos", cos)+ , ("asin", asin), ("sinh", sinh), ("sin", sin)+ , ("exp", exp), ("log", log)+ , ("sqrt", sqrt), ("square", (**2))+ , ("atan", atan), ("tanh", tanh), ("tan", tan)+ ]+ binOps = [[binary "^" (**) AssocLeft]+ , [binary "*" (*) AssocLeft, binary "/" (/) AssocLeft]+ , [binary "+" (+) AssocLeft, binary "-" (-) AssocLeft]+ ]+ var = do char 'X'+ ix <- decimal+ pure $ Fix $ Var (ix - 1) -- Operon is not 0-based+ <?> "var"++-- | parser for HeuristicLab.+parseHL :: Bool -> [(B.ByteString, Int)] -> ParseTree+parseHL = parseExpr (prefixOps : binOps) binFuns var+ where+ binFuns = [ binFun "aq" aq ]+ prefixOps = map (uncurry prefix)+ [ ("logabs", log.abs), ("sqrtabs", sqrt.abs) -- the longer versions should come first+ , ("abs", abs), ("exp", exp), ("log", log)+ , ("sqrt", sqrt), ("sqr", (**2)), ("cube", (**3))+ , ("cbrt", cbrt), ("sin", sin), ("cos", cos)+ , ("tan", tan), ("tanh", tanh)+ ]+ binOps = [[binary "^" (**) AssocLeft]+ , [binary "*" (*) AssocLeft, binary "/" (/) AssocLeft]+ , [binary "+" (+) AssocLeft, binary "-" (-) AssocLeft]+ ]+ var = do char 'x'+ ix <- decimal+ pure $ Fix $ Var ix+ <?> "var"++-- | parser for Bingo+parseBingo :: Bool -> [(B.ByteString, Int)] -> ParseTree+parseBingo = parseExpr (prefixOps : binOps) binFuns var+ where+ binFuns = []+ prefixOps = map (uncurry prefix)+ [ ("abs", abs), ("exp", exp), ("log", log.abs)+ , ("sqrt", sqrt.abs)+ , ("sinh", sinh), ("cosh", cosh)+ , ("sin", sin), ("cos", cos)+ ]+ binOps = [[binary "^" (**) AssocLeft]+ , [binary "/" (/) AssocLeft, binary "" (*) AssocLeft]+ , [binary "+" (+) AssocLeft, binary "-" (-) AssocLeft]+ ]+ var = do string "X_"+ ix <- decimal+ pure $ Fix $ Var ix+ <?> "var"++-- | parser for GOMEA+parseGOMEA :: Bool -> [(B.ByteString, Int)] -> ParseTree+parseGOMEA = parseExpr (prefixOps : binOps) binFuns var+ where+ binFuns = []+ prefixOps = map (uncurry prefix)+ [ ("exp", exp), ("plog", log.abs)+ , ("sqrt", sqrt.abs)+ , ("sin", sin), ("cos", cos)+ ]+ binOps = [[binary "^" (**) AssocLeft]+ , [binary "/" (/) AssocLeft, binary "*" (*) AssocLeft, binary "aq" aq AssocLeft]+ , [binary "+" (+) AssocLeft, binary "-" (-) AssocLeft]+ ]+ var = do string "x"+ ix <- decimal+ pure $ Fix $ Var ix+ <?> "var"++-- | parser for PySR+parsePySR :: Bool -> [(B.ByteString, Int)] -> ParseTree+parsePySR = parseExpr (prefixOps : binOps) binFuns var+ where+ binFuns = [ binFun "pow" (**) ]+ prefixOps = map (uncurry prefix)+ [ ("abs", abs), ("exp", exp)+ , ("square", (**2)), ("cube", (**3)), ("neg", negate)+ , ("acosh_abs", acosh . (+1) . abs), ("acosh", acosh), ("asinh", asinh)+ , ("acos", acos), ("asin", asin), ("atan", atan)+ , ("sqrt_abs", sqrt.abs), ("sqrt", sqrt)+ , ("sinh", sinh), ("cosh", cosh), ("tanh", tanh)+ , ("sin", sin), ("cos", cos), ("tan", tan)+ , ("log10", log10), ("log2", log2), ("log1p", log1p) + , ("log_abs", log.abs), ("log10_abs", log10 . abs)+ , ("log", log)+ ]+ binOps = [[binary "^" (**) AssocLeft]+ , [binary "/" (/) AssocLeft, binary "*" (*) AssocLeft]+ , [binary "+" (+) AssocLeft, binary "-" (-) AssocLeft]+ ]+ var = do string "x"+ ix <- decimal+ pure $ Fix $ Var ix+ <?> "var"
+ src/Text/ParseSR/IO.hs view
@@ -0,0 +1,73 @@+{-# language LambdaCase #-}+-----------------------------------------------------------------------------+-- |+-- Module : Text.ParseSR.IO+-- Copyright : (c) Fabricio Olivetti 2021 - 2024+-- License : BSD3+-- Maintainer : fabricio.olivetti@gmail.com+-- Stability : experimental+-- Portability : ConstraintKinds+--+-- Functions to parse multiple expressions from stdin or a text file.+--+-----------------------------------------------------------------------------+module Text.ParseSR.IO ( withInput, withOutput, withOutputDebug )+ where++-- import Data.SRTree.EqSat1+import Algorithm.EqSat.Simplify ( simplifyEqSatDefault )+import Control.Monad (forM_, unless)+import qualified Data.ByteString.Char8 as B+import Data.SRTree+import Data.SRTree.Recursion (Fix (..))+import System.IO+import Text.ParseSR (Output, SRAlgs, parseSR, showOutput)++-- | given a filename, the symbolic regression algorithm, a string of variables name, +-- and two booleans indicating whether to convert float values to parameters and +-- whether to simplify the expression or not, it will read the file and parse everything +-- returning a list of either an error message or a tree.+--+-- empty filename defaults to stdin +withInput :: String -> SRAlgs -> String -> Bool -> Bool -> IO [Either String (Fix SRTree)]+withInput fname sr hd param simpl = do+ h <- if null fname then pure stdin else openFile fname ReadMode+ contents <- hGetLines h + let myParserFun = parseSR sr (B.pack hd) param . B.pack+ -- myParser = if simpl then fmap simplifyEqSat . myParserFun else myParserFun+ myParser = if simpl then fmap simplifyEqSatDefault . myParserFun else myParserFun+ es = map myParser $ filter (not . null) contents+ unless (null fname) $ hClose h+ pure es++-- | outputs a list of either error or trees to a file using the Output format. +--+-- empty filename defaults to stdout +withOutput :: String -> Output -> [Either String (Fix SRTree)] -> IO ()+withOutput fname output exprs = do+ h <- if null fname then pure stdout else openFile fname WriteMode+ forM_ exprs $ \case + Left err -> hPutStrLn h $ "invalid expression: " <> err+ Right ex -> hPutStrLn h (showOutput output ex)+ unless (null fname) $ hClose h++-- | debug version of output function to check the invalid parsers+withOutputDebug :: String -> Output -> [Either String (Fix SRTree, Fix SRTree)] -> IO ()+withOutputDebug fname output exprs = do+ h <- if null fname then pure stdout else openFile fname WriteMode+ forM_ exprs $ \case + Left err -> hPutStrLn h $ "invalid expression: " <> err+ Right (t1, t2) -> do + hPutStrLn h ("First: " <> showOutput output t1)+ hPutStrLn h ("Second: " <> showOutput output t2)+ hFlush h+ unless (null fname) $ hClose h++hGetLines :: Handle -> IO [String]+hGetLines h = do+ done <- hIsEOF h+ if done+ then return []+ else do+ line <- hGetLine h+ (line :) <$> hGetLines h
srtree.cabal view
@@ -1,12 +1,12 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.2.+-- This file has been generated from package.yaml by hpack version 0.37.0. -- -- see: https://github.com/sol/hpack name: srtree-version: 1.0.0.5-synopsis: A general framework to work with Symbolic Regression expression trees.+version: 2.0.0.0+synopsis: A general library to work with Symbolic Regression expression trees. description: A Symbolic Regression Tree data structure to work with mathematical expressions with support to first order derivative and simplification; category: Math, Data, Data Structures homepage: https://github.com/folivetti/srtree#readme@@ -27,24 +27,270 @@ library exposed-modules:+ Algorithm.EqSat+ Algorithm.EqSat.Build+ Algorithm.EqSat.DB+ Algorithm.EqSat.Egraph+ Algorithm.EqSat.Info+ Algorithm.EqSat.Queries+ Algorithm.EqSat.Simplify+ Algorithm.Massiv.Utils+ Algorithm.SRTree.AD+ Algorithm.SRTree.ConfidenceIntervals+ Algorithm.SRTree.Likelihoods+ Algorithm.SRTree.ModelSelection+ Algorithm.SRTree.NonlinearOpt+ Algorithm.SRTree.Opt Data.SRTree+ Data.SRTree.Datasets+ Data.SRTree.Derivative+ Data.SRTree.Eval Data.SRTree.Internal Data.SRTree.Print Data.SRTree.Random Data.SRTree.Recursion+ Text.ParseSR+ Text.ParseSR.IO other-modules: Paths_srtree hs-source-dirs: src+ ghc-options: -fwarn-incomplete-patterns build-depends:- base >=4.16 && <5- , containers ==0.6.*+ attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2+ , base >=4.16 && <5+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8 , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1 , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2 , random ==1.2.*+ , split >=0.2.5 && <0.3+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.* , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8 default-language: Haskell2010 +executable egraphGP+ main-is: Main.hs+ other-modules:+ Random+ Paths_srtree+ hs-source-dirs:+ apps/egraphGP+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2+ build-depends:+ attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2+ , base >=4.16 && <5+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8+ , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1+ , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2+ , optparse-applicative >=0.17 && <0.19+ , random ==1.2.*+ , split >=0.2.5 && <0.3+ , srtree+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.*+ , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8+ default-language: Haskell2010++executable eqsatrepr+ main-is: Main.hs+ other-modules:+ Paths_srtree+ hs-source-dirs:+ apps/eqsatrepr+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -optc-O3+ build-depends:+ attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2+ , base >=4.16 && <5+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8+ , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1+ , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2+ , random ==1.2.*+ , split >=0.2.5 && <0.3+ , srtree+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.*+ , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8+ default-language: Haskell2010++executable ieeexplore+ main-is: Main.hs+ other-modules:+ Paths_srtree+ hs-source-dirs:+ apps/ieeexplore+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -optc-O3+ build-depends:+ attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2+ , base >=4.16 && <5+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8+ , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1+ , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2+ , optparse-applicative >=0.17 && <0.19+ , random ==1.2.*+ , split >=0.2.5 && <0.3+ , srtree+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.*+ , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8+ default-language: Haskell2010++executable srsimplify+ main-is: Main.hs+ other-modules:+ Paths_srtree+ hs-source-dirs:+ apps/srsimplify+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -optc-O3+ build-depends:+ attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2+ , base >=4.16 && <5+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8+ , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1+ , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2+ , optparse-applicative >=0.17 && <0.19+ , random ==1.2.*+ , split >=0.2.5 && <0.3+ , srtree+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.*+ , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8+ default-language: Haskell2010++executable srtools+ main-is: Main.hs+ other-modules:+ Args+ IO+ Report+ Paths_srtree+ hs-source-dirs:+ apps/srtools+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -optc-O3+ build-depends:+ attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2+ , base >=4.16 && <5+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8+ , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1+ , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2+ , normaldistribution >=1.1.0.3 && <1.2+ , optparse-applicative >=0.17 && <0.19+ , random ==1.2.*+ , split >=0.2.5 && <0.3+ , srtree+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.*+ , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8+ default-language: Haskell2010++executable tinygp+ main-is: Main.hs+ other-modules:+ GP+ Initialization+ Paths_srtree+ hs-source-dirs:+ apps/tinygp+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -optc-O3+ build-depends:+ attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2+ , base >=4.16 && <5+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8+ , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1+ , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2+ , optparse-applicative >=0.17 && <0.19+ , random ==1.2.*+ , split >=0.2.5 && <0.3+ , srtree+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.*+ , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8+ default-language: Haskell2010+ test-suite srtree-test type: exitcode-stdio-1.0 main-is: Spec.hs@@ -56,11 +302,27 @@ build-depends: HUnit , ad+ , attoparsec >=0.14.4 && <0.15+ , attoparsec-expr >=0.1.1.2 && <0.2 , base >=4.16 && <5- , containers ==0.6.*+ , bytestring ==0.11.*+ , containers >=0.6.7 && <0.8 , dlist ==1.0.*+ , exceptions >=0.10.7 && <0.11+ , filepath >=1.4.0.0 && <1.6+ , hashable >=1.4.4.0 && <1.6+ , ieee754 >=0.8.0 && <0.9+ , lens >=5.2.3 && <5.4+ , list-shuffle >=1.0.0.1 && <1.1+ , massiv >=1.0.4.0 && <1.1 , mtl >=2.2 && <2.4+ , nlopt-haskell >=0.1.3.0 && <0.2 , random ==1.2.*+ , split >=0.2.5 && <0.3 , srtree+ , statistics >=0.16.2.1 && <0.17+ , transformers >=0.6.1.0 && <0.7+ , unordered-containers ==0.2.* , vector >=0.12 && <0.14+ , zlib >=0.6.3 && <0.8 default-language: Haskell2010
test/Spec.hs view
@@ -1,8 +1,15 @@ import Data.SRTree+import Data.SRTree.Eval+import Data.SRTree.Derivative+import Data.SRTree.Datasets+import Algorithm.SRTree.AD import qualified Data.Vector as V import Numeric.AD.Double ( grad ) import Test.HUnit +import qualified Data.Massiv.Array as M+import Data.Massiv.Array (D, S, Ix1, Ix2, Comp(..), Sz(..))+import qualified Foreign as M -- test expressions exprs = [@@ -13,6 +20,8 @@ , 1 / param 0 * param 1 , param 0 + param 1 + param 0 * param 1 + sin (param 0) + sin (param 1) + cos (param 0) + cos (param 1) + sin (param 0 * param 1) + cos (param 0 * param 1) , sin (exp (param 0) + param 1)+ , param 0 / param 1+ , param 0 ** param 1 ] -- autodiff with multiple occurrences of vars@@ -24,6 +33,8 @@ , grad (\[x,y] -> 1 / x * y) [2,3] , grad (\[x,y] -> x + y + x * y + sin x + sin y + cos x + cos y + sin (x * y) + cos (x * y)) [2,3] , grad (\[x,y] -> sin (exp x + y)) [2,3]+ , grad (\[x,y] -> x/y) [2,3]+ , grad (\[x,y] -> x ** y) [2,3] ] -- autodiff with single occurrences of vars@@ -35,30 +46,39 @@ , grad (\[x,y] -> 1 / x * y) [2,3] , grad (\[a,b,c,d,e,f,g,h,i,j,k,l] -> a + b + c * d + sin e + sin f + cos g + cos h + sin (i * j) + cos (k * l)) [2,3,2,3,2,3,2,3,2,3,2,3] , grad (\[x,y] -> sin (exp x + y)) [2,3]+ , grad (\[x,y] -> x/y) [2,3]+ , grad (\[x,y] -> x ** y) [2,3] ] -- xs is empty since we are interested in theta-xs :: V.Vector a-xs = V.empty+xs :: M.Array S Ix2 Double+xs = M.singleton 0++xs' :: M.Array S Ix2 Double +xs' = M.singleton 0 ++err = M.singleton 1+ -- theta values-thetaMulti, thetaSingle :: V.Vector Double-thetaMulti = V.fromList [2.0, 3.0]-thetaSingle = V.fromList [2.0, 3.0, 2.0, 3.0, 2.0, 3.0, 2.0, 3.0, 2.0, 3.0, 2.0, 3.0]+thetaMulti, thetaSingle :: M.Array S Ix1 Double+thetaMulti = M.fromList Seq [2.0, 3.0]+thetaSingle = M.fromList Seq [2.0, 3.0, 2.0, 3.0, 2.0, 3.0, 2.0, 3.0, 2.0, 3.0, 2.0, 3.0] -- values from forward mode-forwardVals :: [[Double]]-forwardVals = map (forwardMode xs thetaMulti id) exprs+-- forwardVals :: [[Double]]+forwardVals = map (M.toList . snd . forwardMode xs' thetaMulti err) exprs -- values from grad -- we must relabel the parameters of the expression to sequence values-gradVals :: [(Double, [Double])]-gradVals = map (gradParamsFwd xs thetaSingle id . relabelParams) exprs+--gradVals :: [(Double, [Double])]+gradVals = map (M.toList . snd . forwardModeUnique xs' thetaSingle err . relabelParams) exprs+gradVals' = map (M.toList . snd . reverseModeUnique xs' thetaSingle err . relabelParams) exprs -- values of the evaluated expressions-exprVals :: [Double]-exprVals = map (evalTree xs thetaSingle id . relabelParams) exprs+--exprVals :: [Double]+exprVals = map (evalTree xs' thetaSingle . relabelParams) exprs -refGrad :: [(Double, [Double])]+--refGrad :: [(Double, [Double])] refGrad = zip exprVals autoDiffSingle testDiff :: (Eq a, Show a) => String -> String -> a -> a -> Test@@ -67,10 +87,12 @@ tests :: Test tests = TestList $ zipWith (testDiff "forward mode" "autodiff x forward mode") autoDiffMult forwardVals- <> zipWith (testDiff "opt. grad. parameters" "(evalTree, autodiff) x gradVals") refGrad gradVals- <> zipWith (testDiff "deriveByParam" "deriveByParam x autodiff") (map head autoDiffSingle) (map (head.snd) gradVals)+ <> zipWith (testDiff "forward mode" "autodiff x forward mode unique") autoDiffSingle gradVals+ <> zipWith (testDiff "reverse mode" "autodiff x reverse mode unique") autoDiffSingle gradVals' main :: IO () main = do result <- runTestTT tests putStrLn $ showCounts result+ --ds <- loadDataset "test/wine.csv:3:10:alcohol:liver,deaths,heart" True+ --print ds