packages feed

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 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