srtree-3.0.0.0: src/Algorithm/EqSat/Info.hs
-----------------------------------------------------------------------------
-- |
-- 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
import Control.Monad.State
import Data.AEq (AEq ((~==)))
import Data.IntMap (IntMap)
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, Target)
import Data.HashSet (HashSet)
import qualified Data.HashSet as Set
import qualified Data.Set as RangeSet
import qualified Data.IntSet as IntSet
import Algorithm.EqSat.Egraph
import Algorithm.EqSat.Queries
import qualified Data.Set as TrueSet
-- * 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 dl1 p1 sz1) (EData c2 b2 cn2 fit2 dl2 p2 sz2) =
--EData (min c1 c2) b (combineConsts cn1 cn2) (minMaybe fit1 fit2) (bestParam p1 p2 fit1 fit2) (min sz1 sz2)
EData (min c1 c2) (choose b1 b2) (choose cn1 cn2) (maxMaybe fit1 fit2) (choose dl1 dl2) (choose p1 p2) (choose sz1 sz2)
where
isFst = c1 <= c2
choose x y = if isFst then x else y
chooseF x y = if maxIsFst then x else y
maxIsFst = case (fit1, fit2) of
(Nothing, Nothing) -> True
(Nothing, Just f) -> False
(Just f , Nothing) -> True
(Just f1, Just f2) -> f1 >= f2
maxMaybe Nothing x = x
maxMaybe x Nothing = x
maxMaybe x y = max 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 (ParamIx ix) (ConstVal x) = ConstVal x
combineConsts (ConstVal x) (ParamIx ix) = ConstVal x -- p - p = 0
combineConsts x y = error (show x <> " " <> show y)
-- | Fetch consts, cost, and size for all children in a single state traversal
getChildrenData :: ClassStore m => [EClassId] -> EGraphST m [(Consts, Cost, Int)]
getChildrenData ids = do
ids' <- mapM canonical ids
mapM (\cid -> do
ec <- getEClass cid
let d = _info ec
pure (_consts d, _cost d, _size d)) ids'
{-# INLINE getChildrenData #-}
-- | Calculate e-node data (constant values and cost)
makeAnalysis :: ClassStore m => CostFun -> ENode -> EGraphST m EClassData
makeAnalysis costFun enode =
do let cs = eChildren enode
childData <- getChildrenData cs
let (consts', costs', sizes) = unzip3 childData
consts = combineNode enode consts'
cost = costNode enode costs'
sz = sum sizes
enode' <- canonize enode
pure $ EData cost enode' consts Nothing Nothing [] (sz + 1)
where
-- ENAry folds children pairwise (constant folding over a multiset); the
-- binary skeleton cannot represent n children.
combineNode (ENAry op _) cs = foldr1 (\a b -> combineConsts (Bin (toOp op) a b)) cs
combineNode _ cs = combineConsts (replaceChildren cs (fromENode enode))
-- ENAry is a single flattened op node: op cost + sum of child costs.
costNode (ENAry op _) cs = costFun (Bin (toOp op) 0 0) + sum cs
costNode _ cs = costFun (replaceChildren cs (fromENode enode))
getChildrenMinHeight :: ClassStore m => ENode -> EGraphST m Int
getChildrenMinHeight enode = do
let children = eChildren enode
if null children then pure 0 else do
children' <- mapM canonical children
hs <- mapM (fmap _height . getEClass) children'
pure (minimum hs)
-- | update the heights of each e-class
-- won't work if there's no root
calculateHeights :: ClassStore m => EGraphST m ()
calculateHeights =
do queue <- findRootClasses
classes <- allKeys
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 :: ClassStore m => Int -> EClassId -> EGraphST m ()
setHeight x eId' =
do eId <- canonical eId'
ec <- getEClass eId
let ec' = over height (const x) ec
insertClass ec'
setMinHeight :: ClassStore m => Int -> EClassId -> EGraphST m ()
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 :: ClassStore m => EClassId -> EGraphST m [EClassId]
getChildrenEC ec' = do ec <- getEClass ec'
pure $ concatMap eChildren (_eNodes ec)
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 :: ClassStore m => CostFun -> ENode -> EGraphST m Cost
calculateCost f enode =
do let cs = eChildren enode
costs <- traverse (fmap (_cost . _info) . getEClass) cs
pure $ case enode of
ENAry op _ -> f (Bin (toOp op) 0 0) + sum costs
_ -> f (replaceChildren costs (fromENode enode))
-- | check whether an e-node evaluates to a const
calculateConsts :: ClassStore m => ENode -> EGraphST m Consts
calculateConsts enode =
do let cs = eChildren enode
consts <- traverse (fmap (_consts . _info) . getEClass) cs
let c = case enode of
ENAry op _ -> foldr1 (\a b -> combineConsts (Bin (toOp op) a b)) consts
_ -> combineConsts (replaceChildren consts (fromENode enode))
case c 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
--ParamIx x -> ParamIx 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 :: ClassStore m => EClassId -> Double -> [Target] -> EGraphST m ()
insertFitness eId' fit params =
do eId <- canonical eId'
tree <- getBestExpr eId
let p = fromIntegral (length params)
let f_compl = countNodes tree * log (countUniqueTokens tree) + p * (log (2 * pi * exp(1 - log 3)) - log p) / 2.0
ec <- getEClass eId
let oldFit = _fitness . _info $ ec
let newInfo = (_info ec){_fitness = Just fit, _theta = params}
newEc = ec{_info = newInfo}
sz = _size newInfo
insertClass newEc
case oldFit of
Nothing -> modify' $ over (eDB . unevaluated) (IntSet.delete eId)
. over (eDB . fitRangeDB) (insertRange eId fit)
. over (eDB . sizeFitDB) (IntMap.adjust (insertRange eId fit) sz . IntMap.insertWith RangeSet.union sz RangeSet.empty)
. over (eDB . dlRangeDB) (insertRange eId f_compl)
Just oldVal -> modify' $ over (eDB . fitRangeDB) (insertRange eId fit . removeRange eId oldVal)
. over (eDB . sizeFitDB) (IntMap.adjust (insertRange eId fit . removeRange eId oldVal) sz)
insertDL :: ClassStore m => EClassId -> Double -> EGraphST m ()
insertDL eId fit' =
do let fit = negate fit'
ec <- getEClass eId
let sz = _size . _info $ ec
newInfo = (_info ec){_dl = Just fit'}
newEc = ec{_info=newInfo}
insertClass newEc
modify' $ over (eDB . dlRangeDB) (insertRange eId fit)
. over (eDB . sizeDLDB) (IntMap.adjust (insertRange eId fit) sz . IntMap.insertWith RangeSet.union sz RangeSet.empty)