hegg 0.2.0.0 → 0.3.0.0
raw patch · 24 files changed
+771/−265 lines, 24 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
- Data.Equality.Analysis: -- | Domain of data stored in e-class according to e-class analysis
- Data.Equality.Analysis: type Domain l;
- Data.Equality.Analysis: }
- Data.Equality.Graph.Classes: instance (GHC.Show.Show (Data.Equality.Analysis.Domain l), Data.Functor.Classes.Show1 l) => GHC.Show.Show (Data.Equality.Graph.Classes.EClass l)
- Data.Equality.Saturation.Scheduler: instance GHC.Show.Show Data.Equality.Saturation.Scheduler.BoSchStat
- Data.Equality.Saturation.Scheduler: type Stat s;
- Data.Equality.Utils.SizedList: instance GHC.Exts.IsList (Data.Equality.Utils.SizedList.SList a)
+ Data.Equality.Analysis: instance (Data.Equality.Language.Language l, Data.Equality.Analysis.Analysis a l, Data.Equality.Analysis.Analysis b l) => Data.Equality.Analysis.Analysis (a, b) l
+ Data.Equality.Analysis: instance Data.Equality.Analysis.Analysis () l
+ Data.Equality.Graph: represent :: forall a l. (Analysis a l, Language l) => Fix l -> EGraph a l -> (ClassId, EGraph a l)
+ Data.Equality.Graph.Classes: instance (GHC.Show.Show a, Data.Functor.Classes.Show1 l) => GHC.Show.Show (Data.Equality.Graph.Classes.EClass a l)
+ Data.Equality.Saturation.Rewrites: instance Data.Functor.Classes.Show1 lang => GHC.Show.Show (Data.Equality.Saturation.Rewrites.Rewrite anl lang)
+ Data.Equality.Saturation.Scheduler: BackoffScheduler :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> BackoffScheduler
+ Data.Equality.Saturation.Scheduler: [banLength] :: BackoffScheduler -> {-# UNPACK #-} !Int
+ Data.Equality.Saturation.Scheduler: [matchLimit] :: BackoffScheduler -> {-# UNPACK #-} !Int
+ Data.Equality.Saturation.Scheduler: data Stat s;
+ Data.Equality.Saturation.Scheduler: defaultBackoffScheduler :: BackoffScheduler
+ Data.Equality.Saturation.Scheduler: instance GHC.Show.Show (Data.Equality.Saturation.Scheduler.Stat Data.Equality.Saturation.Scheduler.BackoffScheduler)
+ Data.Equality.Utils.SizedList: instance GHC.IsList.IsList (Data.Equality.Utils.SizedList.SList a)
- Data.Equality.Analysis: class Eq (Domain l) => Analysis (l :: Type -> Type) where {
+ Data.Equality.Analysis: class Eq domain => Analysis domain (l :: Type -> Type)
- Data.Equality.Analysis: joinA :: Analysis l => Domain l -> Domain l -> Domain l
+ Data.Equality.Analysis: joinA :: Analysis domain l => domain -> domain -> domain
- Data.Equality.Analysis: makeA :: Analysis l => ENode l -> EGraph l -> Domain l
+ Data.Equality.Analysis: makeA :: Analysis domain l => l domain -> domain
- Data.Equality.Analysis: modifyA :: Analysis l => ClassId -> EGraph l -> EGraph l
+ Data.Equality.Analysis: modifyA :: Analysis domain l => EClass domain l -> (EClass domain l, [Fix l])
- Data.Equality.Extraction: extractBest :: forall lang cost. (Language lang, Ord cost) => EGraph lang -> CostFunction lang cost -> ClassId -> Fix lang
+ Data.Equality.Extraction: extractBest :: forall anl lang cost. (Language lang, Ord cost) => EGraph anl lang -> CostFunction lang cost -> ClassId -> Fix lang
- Data.Equality.Graph: add :: forall l. Language l => ENode l -> EGraph l -> (ClassId, EGraph l)
+ Data.Equality.Graph: add :: forall a l. (Analysis a l, Language l) => ENode l -> EGraph a l -> (ClassId, EGraph a l)
- Data.Equality.Graph: canonicalize :: Functor l => ENode l -> EGraph l -> ENode l
+ Data.Equality.Graph: canonicalize :: Functor l => ENode l -> EGraph a l -> ENode l
- Data.Equality.Graph: data EGraph l
+ Data.Equality.Graph: data EGraph analysis language
- Data.Equality.Graph: emptyEGraph :: Language l => EGraph l
+ Data.Equality.Graph: emptyEGraph :: Language l => EGraph a l
- Data.Equality.Graph: find :: ClassId -> EGraph l -> ClassId
+ Data.Equality.Graph: find :: ClassId -> EGraph a l -> ClassId
- Data.Equality.Graph: merge :: forall l. Language l => ClassId -> ClassId -> EGraph l -> (ClassId, EGraph l)
+ Data.Equality.Graph: merge :: forall a l. (Analysis a l, Language l) => ClassId -> ClassId -> EGraph a l -> (ClassId, EGraph a l)
- Data.Equality.Graph: rebuild :: Language l => EGraph l -> EGraph l
+ Data.Equality.Graph: rebuild :: (Analysis a l, Language l) => EGraph a l -> EGraph a l
- Data.Equality.Graph.Classes: EClass :: {-# UNPACK #-} !ClassId -> !Set (ENode l) -> Domain l -> !SList (ClassId, ENode l) -> EClass l
+ Data.Equality.Graph.Classes: EClass :: {-# UNPACK #-} !ClassId -> !Set (ENode language) -> analysis_domain -> !SList (ClassId, ENode language) -> EClass analysis_domain language
- Data.Equality.Graph.Classes: [eClassData] :: EClass l -> Domain l
+ Data.Equality.Graph.Classes: [eClassData] :: EClass analysis_domain language -> analysis_domain
- Data.Equality.Graph.Classes: [eClassId] :: EClass l -> {-# UNPACK #-} !ClassId
+ Data.Equality.Graph.Classes: [eClassId] :: EClass analysis_domain language -> {-# UNPACK #-} !ClassId
- Data.Equality.Graph.Classes: [eClassNodes] :: EClass l -> !Set (ENode l)
+ Data.Equality.Graph.Classes: [eClassNodes] :: EClass analysis_domain language -> !Set (ENode language)
- Data.Equality.Graph.Classes: [eClassParents] :: EClass l -> !SList (ClassId, ENode l)
+ Data.Equality.Graph.Classes: [eClassParents] :: EClass analysis_domain language -> !SList (ClassId, ENode language)
- Data.Equality.Graph.Classes: data EClass l
+ Data.Equality.Graph.Classes: data EClass analysis_domain language
- Data.Equality.Graph.Lens: _class :: ClassId -> Lens' (EGraph l) (EClass l)
+ Data.Equality.Graph.Lens: _class :: ClassId -> Lens' (EGraph a l) (EClass a l)
- Data.Equality.Graph.Lens: _classes :: Lens' (EGraph l) (ClassIdMap (EClass l))
+ Data.Equality.Graph.Lens: _classes :: Lens' (EGraph a l) (ClassIdMap (EClass a l))
- Data.Equality.Graph.Lens: _data :: Lens' (EClass l) (Domain l)
+ Data.Equality.Graph.Lens: _data :: Lens' (EClass domain l) domain
- Data.Equality.Graph.Lens: _memo :: Lens' (EGraph l) (NodeMap l ClassId)
+ Data.Equality.Graph.Lens: _memo :: Lens' (EGraph a l) (NodeMap l ClassId)
- Data.Equality.Graph.Lens: _nodes :: Lens' (EClass l) (Set (ENode l))
+ Data.Equality.Graph.Lens: _nodes :: Lens' (EClass a l) (Set (ENode l))
- Data.Equality.Graph.Lens: _parents :: Lens' (EClass l) (SList (ClassId, ENode l))
+ Data.Equality.Graph.Lens: _parents :: Lens' (EClass a l) (SList (ClassId, ENode l))
- Data.Equality.Graph.Monad: add :: Language l => ENode l -> EGraphM l ClassId
+ Data.Equality.Graph.Monad: add :: (Analysis anl l, Language l) => ENode l -> EGraphM anl l ClassId
- Data.Equality.Graph.Monad: canonicalize :: Functor l => ENode l -> EGraph l -> ENode l
+ Data.Equality.Graph.Monad: canonicalize :: Functor l => ENode l -> EGraph a l -> ENode l
- Data.Equality.Graph.Monad: data EGraph l
+ Data.Equality.Graph.Monad: data EGraph analysis language
- Data.Equality.Graph.Monad: egraph :: Language l => EGraphM l a -> (a, EGraph l)
+ Data.Equality.Graph.Monad: egraph :: Language l => EGraphM anl l a -> (a, EGraph anl l)
- Data.Equality.Graph.Monad: emptyEGraph :: Language l => EGraph l
+ Data.Equality.Graph.Monad: emptyEGraph :: Language l => EGraph a l
- Data.Equality.Graph.Monad: find :: ClassId -> EGraph l -> ClassId
+ Data.Equality.Graph.Monad: find :: ClassId -> EGraph a l -> ClassId
- Data.Equality.Graph.Monad: merge :: Language l => ClassId -> ClassId -> EGraphM l ClassId
+ Data.Equality.Graph.Monad: merge :: (Analysis anl l, Language l) => ClassId -> ClassId -> EGraphM anl l ClassId
- Data.Equality.Graph.Monad: rebuild :: Language l => EGraphM l ()
+ Data.Equality.Graph.Monad: rebuild :: (Analysis anl l, Language l) => EGraphM anl l ()
- Data.Equality.Graph.Monad: represent :: Language l => Fix l -> EGraphM l ClassId
+ Data.Equality.Graph.Monad: represent :: (Analysis anl l, Language l) => Fix l -> EGraphM anl l ClassId
- Data.Equality.Graph.Monad: runEGraphM :: EGraph l -> EGraphM l a -> (a, EGraph l)
+ Data.Equality.Graph.Monad: runEGraphM :: EGraph anl l -> EGraphM anl l a -> (a, EGraph anl l)
- Data.Equality.Graph.Monad: type EGraphM s = State (EGraph s)
+ Data.Equality.Graph.Monad: type EGraphM a l = State (EGraph a l)
- Data.Equality.Language: class (Analysis l, Traversable l, Ord1 l) => Language l
+ Data.Equality.Language: class (Traversable l, Ord1 l) => Language l
- Data.Equality.Matching: eGraphToDatabase :: Language l => EGraph l -> Database l
+ Data.Equality.Matching: eGraphToDatabase :: Language l => EGraph a l -> Database l
- Data.Equality.Saturation: (:=) :: !Pattern lang -> !Pattern lang -> Rewrite lang
+ Data.Equality.Saturation: (:=) :: !Pattern lang -> !Pattern lang -> Rewrite anl lang
- Data.Equality.Saturation: (:|) :: !Rewrite lang -> !RewriteCondition lang -> Rewrite lang
+ Data.Equality.Saturation: (:|) :: !Rewrite anl lang -> !RewriteCondition anl lang -> Rewrite anl lang
- Data.Equality.Saturation: data Rewrite lang
+ Data.Equality.Saturation: data Rewrite anl lang
- Data.Equality.Saturation: equalitySaturation :: forall l cost. (Language l, Ord cost) => Fix l -> [Rewrite l] -> CostFunction l cost -> (Fix l, EGraph l)
+ Data.Equality.Saturation: equalitySaturation :: forall a l cost. (Analysis a l, Language l, Ord cost) => Fix l -> [Rewrite a l] -> CostFunction l cost -> (Fix l, EGraph a l)
- Data.Equality.Saturation: equalitySaturation' :: forall l schd cost. (Language l, Scheduler schd, Ord cost) => Proxy schd -> Fix l -> [Rewrite l] -> CostFunction l cost -> (Fix l, EGraph l)
+ Data.Equality.Saturation: equalitySaturation' :: forall a l schd cost. (Analysis a l, Language l, Scheduler schd, Ord cost) => schd -> Fix l -> [Rewrite a l] -> CostFunction l cost -> (Fix l, EGraph a l)
- Data.Equality.Saturation: runEqualitySaturation :: forall l schd. (Language l, Scheduler schd) => Proxy schd -> [Rewrite l] -> EGraphM l ()
+ Data.Equality.Saturation: runEqualitySaturation :: forall a l schd. (Analysis a l, Language l, Scheduler schd) => schd -> [Rewrite a l] -> EGraphM a l ()
- Data.Equality.Saturation: type RewriteCondition lang = Subst -> EGraph lang -> Bool
+ Data.Equality.Saturation: type RewriteCondition anl lang = Subst -> EGraph anl lang -> Bool
- Data.Equality.Saturation.Rewrites: (:=) :: !Pattern lang -> !Pattern lang -> Rewrite lang
+ Data.Equality.Saturation.Rewrites: (:=) :: !Pattern lang -> !Pattern lang -> Rewrite anl lang
- Data.Equality.Saturation.Rewrites: (:|) :: !Rewrite lang -> !RewriteCondition lang -> Rewrite lang
+ Data.Equality.Saturation.Rewrites: (:|) :: !Rewrite anl lang -> !RewriteCondition anl lang -> Rewrite anl lang
- Data.Equality.Saturation.Rewrites: data Rewrite lang
+ Data.Equality.Saturation.Rewrites: data Rewrite anl lang
- Data.Equality.Saturation.Rewrites: type RewriteCondition lang = Subst -> EGraph lang -> Bool
+ Data.Equality.Saturation.Rewrites: type RewriteCondition anl lang = Subst -> EGraph anl lang -> Bool
- Data.Equality.Saturation.Scheduler: updateStats :: Scheduler s => Int -> Int -> Maybe (Stat s) -> IntMap (Stat s) -> [Match] -> IntMap (Stat s)
+ Data.Equality.Saturation.Scheduler: updateStats :: Scheduler s => s -> Int -> Int -> Maybe (Stat s) -> IntMap (Stat s) -> [Match] -> IntMap (Stat s)
Files
- CHANGELOG.md +31/−0
- README.md +90/−6
- hegg.cabal +5/−3
- src/Data/Equality/Analysis.hs +96/−18
- src/Data/Equality/Extraction.hs +7/−13
- src/Data/Equality/Graph.hs +72/−40
- src/Data/Equality/Graph/Classes.hs +5/−7
- src/Data/Equality/Graph/Classes.hs-boot +2/−2
- src/Data/Equality/Graph/Internal.hs +7/−8
- src/Data/Equality/Graph/Internal.hs-boot +3/−3
- src/Data/Equality/Graph/Lens.hs +7/−8
- src/Data/Equality/Graph/Monad.hs +8/−7
- src/Data/Equality/Language.hs +1/−3
- src/Data/Equality/Matching.hs +1/−2
- src/Data/Equality/Saturation.hs +25/−26
- src/Data/Equality/Saturation/Rewrites.hs +9/−3
- src/Data/Equality/Saturation/Scheduler.hs +21/−15
- test/Invariants.hs +7/−15
- test/Lambda.hs +123/−52
- test/SimpleSym.hs +25/−9
- test/Sym.hs +23/−25
- test/T1.hs +145/−0
- test/T2.hs +49/−0
- test/Test.hs +9/−0
CHANGELOG.md view
@@ -1,5 +1,36 @@ # Revision history for hegg +## Unreleased++## 0.3.0.0 -- 2022-12-09++* A better `Analysis` tutorial in the README.++* Complete `Analysis` redesign.+ * The `Analysis` class now has two type parameters: a `domain` and a+ `language`, and no longer has an associated type family+ * The analysis no longer has any knowledge of the e-graph:+ * `makeA` now has type `l domain -> domain`, that is, to make a domain+ of a new node we only have to take into consideration the data of+ the sub-nodes of the new node.+ * `joinA` is unchanged.+ * `modifyA` now has type `EClass domain lang -> (EClass domain lang,+ [Fix lang])`. It takes an e-class and optionally modifies it,+ possibly by adding nodes to it. The return value is the modified+ e-class, and a list of expressions from the language to add to the+ e-class.+ * We can now compose analysis and create language-polymorphic analysis. Such+ two examples are the analysis with domain `()` which regardless of the+ language simply ignores the domain: `instance Analysis () l`; and the+ second example is the product of analysis, which composes two separate+ analysis into one: `instance (Analysis a l, Analysis b l) => Analysis+ (a,b) l`.+ * An `EGraph` now also has two type parameters instead of one (the latter is+ the language is the former the domain of the analysis).++* Allow customization of Schedulers through parameters (by accepting a scheduler+ rather than a proxy for it)+ ## 0.2.0.0 -- 2022-09-19 * Expose `runEqualitySaturation` to run equality saturation on existing e-graphs
README.md view
@@ -96,14 +96,94 @@ can be read about e-class analysis in the [`Data.Equality.Analsysis`]() module and in the paper. -We could easily define constant folding (`2+2` being simplified to `4`) through-an `Analysis` instance, but for the sake of simplicity we'll simply define the-analysis data as `()` and always ignore it.+We can easily define constant folding (`2+2` being simplified to `4`) through+an `Analysis` instance. +An `Analysis` is defined over a `domain` and a `language`. To define constant+folding, we'll say the domain is `Maybe Double` to attach a value of that type to+each e-class, where `Nothing` indicates the e-class does not currently have a+constant value and `Just i` means the e-class has constant value `i`.+ ```hs-instance Analysis SymExpr where- type Domain SymExpr = ()- makeA _ _ = ()+instance Analysis (Maybe Double) SymExpr+ makeA = ...+ joinA = ...+ modifyA = ...+```++Let's now understand and implement the three methods of the analysis instance we want.++`makeA` is called when a new e-node is added to a new e-class, and constructs+for the new e-class a new value of the domain to be associated with it, always+by accessing the associated data of the node's children data. Its type is `l+domain -> domain`, so note that the e-node's children associated data is+directly available in place of the actual children.++We want to associate constant data to the e-class, so we must find if the+e-node has a constant value or otherwise return `Nothing`:++```hs+makeA :: SymExpr (Maybe Double) -> Maybe Int+makeA = \case+ Const x -> Just x+ Symbol _ -> Nothing+ x :+: y -> (+) <$> x <*> y+ x :*: y -> (*) <$> x <*> y+ x :/: y -> (/) <$> x <*> y+```+ +`joinA` is called when e-classes c1 c2 are being merged into c. In this case, we+must join the e-class data from both classes to form the e-class data to be+associated with new e-class c. Its type is `domain -> domain -> domain`. In our+case, to merge `Just _` with `Nothing` we simply take the `Just`, and if we+merge two e-classes with a constant value (that is, both are `Just`), then the+constant value is the same (or something went very wrong) and we just keep it.++```hs+joinA :: Maybe Double -> Maybe Double -> Maybe Double+joinA Nothing (Just x) = Just x+joinA (Just x) Nothing = Just x+joinA Nothing Nothing = Nothing+joinA (Just x) (Just y) = if x == y then Just x else error "ouch, that shouldn't have happened"+```++Finally, `modifyA` describes how an e-class should (optionally) be modified+according to the e-class data and what new language expressions are to be added+to the e-class also w.r.t. the e-class data.+Its type is `EClass domain l -> (EClass domain l, [Fix l])`, where the argument+is the class to modify, the first element of the return tuple is the+(optionally) modified e-class and the second element is a list of the+expressions to represent and merge with this e-class.+For our example, if the e-class has a constant value associated to it, we want+to create a new e-class with that constant value and merge it to this e-class.++```hs+-- import Data.Equality.Graph.Lens ((^.), _data)+modifyA :: EClass (Maybe Double) SymExpr -> (EClass (Maybe Double) SymExpr, [Fix SymExpr])+modifyA c = case c^._data of+ Nothing -> (c, [])+ Just i -> (c, [Fix (Const i)])+```++Modify is a bit trickier than the other methods, but it allows our e-graph to+change based on the e-class analysis data. Note that the method is optional and+there's a default implementation for it which doesn't change the e-class or adds+anything to it. Analysis data can be otherwise used, e.g., to inform rewrite+conditions.++By instancing this e-class analysis, all e-classes that have a constant value+associated to them will also have an e-node with a constant value. This is great+for our simple symbolic library because it means if we ever find e.g. an+expression equal to `3+1`, we'll also know it to be equal to `4`, which is a+better result than `3+1` (we've then successfully implemented constant folding).++If, otherwise, we didn't want to use an analysis, we could specify the analysis+domain as `()` which will make the analysis do nothing, because there's an+instance polymorphic over `lang` for `()` that looks like this:++```hs+instance Analysis () lang where+ makeA _ = () joinA _ _ = () ``` @@ -202,6 +282,10 @@ let expr = fst (equalitySaturation e1 rewrites cost) ``` And upon printing we'd see `expr = Symbol "x"`!++If we had instead `e2 = Fix (Fix (Fix (Symbol "x") :/: Fix (Symbol "x")) :+:+(Fix (Const 3))) -- (x/x)+3`, we'd get `expr = Const 4` because of our rewrite+rules put together with our constant folding! This was a first introduction which skipped over some details but that tried to walk through fundamental concepts for using e-graphs and equality saturation
hegg.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.4 name: hegg-version: 0.2.0.0-Tested-With: GHC ==9.4.1 || ==9.2.2 || ==9.0.2 || ==8.10.7+version: 0.3.0.0+Tested-With: GHC ==9.4.2 || ==9.2.2 || ==9.0.2 || ==8.10.7 synopsis: Fast equality saturation in Haskell description: Fast equality saturation and equality graphs based on "egg:@@ -107,7 +107,9 @@ type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Test.hs- other-modules: Invariants, Sym, Lambda, SimpleSym+ other-modules: Invariants, Sym, Lambda, SimpleSym,+ T1, T2+ other-extensions: OverloadedStrings build-depends: base, hegg,
src/Data/Equality/Analysis.hs view
@@ -1,4 +1,8 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -- joinA+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-}@@ -23,41 +27,115 @@ module Data.Equality.Analysis where import Data.Kind (Type)--import Data.Equality.Graph.Classes.Id-import Data.Equality.Graph.Nodes--import {-# SOURCE #-} Data.Equality.Graph.Internal (EGraph)+import Control.Arrow ((***)) --- | The e-class analysis defined for a language @l@.-class Eq (Domain l) => Analysis (l :: Type -> Type) where+import Data.Equality.Utils+import Data.Equality.Language+import Data.Equality.Graph.Classes - -- | Domain of data stored in e-class according to e-class analysis- type Domain l+-- | An e-class analysis with domain @domain@ defined for a language @l@.+--+-- The @domain@ is the type of the domain of the e-class analysis, that is, the+-- type of the data stored in an e-class according to this e-class analysis+class Eq domain => Analysis domain (l :: Type -> Type) where -- | When a new e-node is added into a new, singleton e-class, construct a- -- new value of the domain to be associated with the new e-class, typically- -- by accessing the associated data of n's children- makeA :: ENode l -> EGraph l -> Domain l+ -- new value of the domain to be associated with the new e-class, by+ -- accessing the associated data of the node's children+ --+ -- The argument is the e-node term populated with its children data+ --+ -- === Example+ --+ -- @+ -- -- domain = Maybe Double+ -- makeA :: Expr (Maybe Double) -> Maybe Double+ -- makeA = \case+ -- BinOp Div e1 e2 -> liftA2 (/) e1 e2+ -- BinOp Sub e1 e2 -> liftA2 (-) e1 e2+ -- BinOp Mul e1 e2 -> liftA2 (*) e1 e2+ -- BinOp Add e1 e2 -> liftA2 (+) e1 e2+ -- Const x -> Just x+ -- Sym _ -> Nothing+ -- @+ makeA :: l domain -> domain -- | When e-classes c1 c2 are being merged into c, join d_c1 and -- d_c2 into a new value d_c to be associated with the new -- e-class c- joinA :: Domain l -> Domain l -> Domain l+ joinA :: domain -> domain -> domain - -- | Optionaly modify the e-class c (based on d_c), typically by adding an+ -- | Optionally modify the e-class c (based on d_c), typically by adding an -- e-node to c. Modify should be idempotent if no other changes occur to -- the e-class, i.e., modify(modify(c)) = modify(c) --+ -- The return value of the modify function is both the modified class and+ -- the expressions (in their fixed-point form) to add to this class. We+ -- can't manually add them because not only would it skip some of the+ -- internal steps of representing + merging, but also because it's+ -- impossible to add any expression with depth > 0 without access to the+ -- e-graph (since we must represent every sub-expression in the e-graph+ -- first).+ --+ -- That's why we must return the modified class and the expressions to add+ -- to this class.+ -- -- === Example -- -- Pruning an e-class with a constant value of all its nodes except for the- -- leaf values+ -- leaf values, and adding a constant value node -- -- @ -- -- Prune all except leaf e-nodes- -- modify (_class i._nodes %~ S.filter (null . children))+ -- modifyA cl =+ -- case cl^._data of+ -- Nothing -> (cl, [])+ -- Just d -> ((_nodes %~ S.filter (F.null .unNode)) cl, [Fix (Const d)]) -- @- modifyA :: ClassId -> EGraph l -> EGraph l- modifyA _ = id+ modifyA :: EClass domain l -> (EClass domain l, [Fix l])+ modifyA c = (c, []) {-# INLINE modifyA #-}+++-- | The simplest analysis that defines the domain to be () and does nothing+-- otherwise+instance forall l. Analysis () l where+ makeA _ = ()+ joinA = (<>)+++-- This instance is not necessarily well behaved for any two analysis, so care+-- must be taken when using it.+--+-- A possible criterion is:+--+-- For any two analysis, where 'modifyA' is called @m1@ and @m2@ respectively,+-- this instance is well behaved if @m1@ and @m2@ commute.+--+-- That is, if @m1@ and @m2@ satisfy the following law:+-- @+-- m1 . m2 = m2 . m1+-- @+--+-- A simple criterion that should suffice for commutativity. If:+-- * The modify function only depends on the analysis value, and+-- * The modify function doesn't change the analysis value+-- Then any two such functions commute.+--+-- Note: there are weaker (or at least different) criteria for this instance to+-- be well behaved.+instance (Language l, Analysis a l, Analysis b l) => Analysis (a, b) l where++ makeA :: l (a, b) -> (a, b)+ makeA g = (makeA @a (fst <$> g), makeA @b (snd <$> g))++ joinA :: (a,b) -> (a,b) -> (a,b)+ joinA (x,y) = joinA @a @l x *** joinA @b @l y++ modifyA :: EClass (a, b) l -> (EClass (a, b) l, [Fix l])+ modifyA c =+ let (ca, la) = modifyA @a (c { eClassData = fst (eClassData c) })+ (cb, lb) = modifyA @b (c { eClassData = snd (eClassData c) })+ in ( EClass (eClassId c) (eClassNodes ca <> eClassNodes cb) (eClassData ca, eClassData cb) (eClassParents ca <> eClassParents cb)+ , la <> lb+ )
src/Data/Equality/Extraction.hs view
@@ -1,10 +1,4 @@-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} {-| Given an e-graph representing expressions of our language, we might want to@@ -45,9 +39,9 @@ -- @ -- -- For a real example you might want to check out the source code of 'Data.Equality.Saturation.equalitySaturation''-extractBest :: forall lang cost+extractBest :: forall anl lang cost . (Language lang, Ord cost)- => EGraph lang -- ^ The e-graph out of which we are extracting an expression+ => EGraph anl lang -- ^ The e-graph out of which we are extracting an expression -> CostFunction lang cost -- ^ The cost function to define /best/ -> ClassId -- ^ The e-class from which we'll extract the expression -> Fix lang -- ^ The resulting /best/ expression, in its fixed point form.@@ -66,14 +60,14 @@ where -- | Find the lowest cost of all e-classes in an e-graph in an extraction- findCosts :: ClassIdMap (EClass lang) -> ClassIdMap (CostWithExpr lang cost) -> ClassIdMap (CostWithExpr lang cost)+ findCosts :: ClassIdMap (EClass anl lang) -> ClassIdMap (CostWithExpr lang cost) -> ClassIdMap (CostWithExpr lang cost) findCosts eclasses current = let (modified, updated) = IM.foldlWithKey f (False, current) eclasses {-# INLINE f #-}- f :: (Bool, ClassIdMap (CostWithExpr lang cost)) -> Int -> EClass lang -> (Bool, ClassIdMap (CostWithExpr lang cost))- f = \acc@(_, beingUpdated) i' EClass{eClassNodes = nodes} ->+ f :: (Bool, ClassIdMap (CostWithExpr lang cost)) -> Int -> EClass anl lang -> (Bool, ClassIdMap (CostWithExpr lang cost))+ f acc@(_, beingUpdated) i' EClass{eClassNodes = nodes} = let currentCost = IM.lookup i' beingUpdated @@ -107,7 +101,7 @@ nodeTotalCost :: Traversable lang => ClassIdMap (CostWithExpr lang cost) -> ENode lang -> Maybe (CostWithExpr lang cost) nodeTotalCost m (Node n) = do expr <- traverse ((`IM.lookup` m) . flip find egr) n- return $ CostWithExpr (cost ((fst . unCWE) <$> expr), (Fix $ (snd . unCWE) <$> expr))+ return $ CostWithExpr (cost (fst . unCWE <$> expr), Fix $ snd . unCWE <$> expr) {-# INLINE nodeTotalCost #-} {-# INLINABLE extractBest #-} @@ -140,7 +134,7 @@ -- | Find the current best node and its cost in an equivalence class given only the class and the current extraction -- This is not necessarily the best node in the e-graph, only the best in the current extraction state findBest :: ClassId -> ClassIdMap (CostWithExpr lang a) -> Maybe (CostWithExpr lang a)-findBest i = IM.lookup i+findBest = IM.lookup {-# INLINE findBest #-} newtype CostWithExpr lang a = CostWithExpr { unCWE :: (a, Fix lang) }
src/Data/Equality/Graph.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE TupleSections #-} -- {-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE BlockArguments #-} {-# LANGUAGE FlexibleContexts #-}@@ -20,7 +19,7 @@ , emptyEGraph -- ** Transformations- , add, merge, rebuild+ , represent, add, merge, rebuild -- , repair, repairAnal -- ** Querying@@ -38,6 +37,10 @@ import Data.Bifunctor import Data.Containers.ListUtils +import Control.Monad+import Control.Monad.Trans.State+import Control.Exception (assert)+ import qualified Data.IntMap.Strict as IM import qualified Data.Set as S @@ -51,18 +54,24 @@ import Data.Equality.Language import Data.Equality.Graph.Lens +import Data.Equality.Utils+ -- ROMES:TODO: join things built in paralell? -- instance Ord1 l => Semigroup (EGraph l) where -- (<>) eg1 eg2 = undefined -- not so easy -- instance Ord1 l => Monoid (EGraph l) where -- mempty = EGraph emptyUF mempty mempty mempty +-- | Represent an expression (in it's fixed point form) in an e-graph.+-- Returns the updated e-graph and the id from the e-class in which it was represented.+represent :: forall a l. (Analysis a l, Language l) => Fix l -> EGraph a l -> (ClassId, EGraph a l)+represent = cata (flip $ \e -> uncurry add . first Node . (`runState` e) . traverse (gets >=> \(x,e') -> x <$ put e')) -- | Add an e-node to the e-graph -- -- If the e-node is already represented in this e-graph, the class-id of the -- class it's already represented in will be returned.-add :: forall l. Language l => ENode l -> EGraph l -> (ClassId, EGraph l)+add :: forall a l. (Analysis a l, Language l) => ENode l -> EGraph a l -> (ClassId, EGraph a l) add uncanon_e egr = let !new_en = canonicalize uncanon_e egr @@ -76,7 +85,12 @@ (new_eclass_id, new_uf) = makeNewSet (unionFind egr) -- New singleton e-class stores the e-node and its analysis data- new_eclass = EClass new_eclass_id (S.singleton new_en) (makeA new_en egr) mempty+ -- which is modified according to analysis+ --+ -- The modification also produces a list of expressions to+ -- represent and merge with this class, which we'll do before+ -- returning from this function+ (new_eclass, added_nodes) = modifyA $ EClass new_eclass_id (S.singleton new_en) (makeA @a ((\i -> egr^._class i._data @a) <$> unNode new_en)) mempty -- TODO:Performance: All updates can be done to the map first? Parallelize? --@@ -86,7 +100,7 @@ -- And add new e-class to existing e-classes new_parents = ((new_eclass_id, new_en) |:) new_classes = IM.insert new_eclass_id new_eclass $- foldr (IM.adjust ((_parents %~ new_parents)))+ foldr (IM.adjust (_parents %~ new_parents)) (classes egr) (unNode new_en) @@ -115,27 +129,30 @@ -- something else? -- -- So in the end, we do need to addToWorklist to get correct results- new_worklist = (new_eclass_id, new_en):(worklist egr)+ new_worklist = (new_eclass_id, new_en):worklist egr -- Add the e-node's e-class id at the e-node's id new_memo = insertNM new_en new_eclass_id (memo egr) - in ( new_eclass_id+ -- So we have our almost final e-graph. We just need to represent+ -- and merge in it all expressions which resulted from 'modifyA'+ -- above+ egr1 = egr { unionFind = new_uf+ , classes = new_classes+ , worklist = new_worklist+ , memo = new_memo+ } - , egr { unionFind = new_uf- , classes = new_classes- , worklist = new_worklist- , memo = new_memo- }+ egr2 = foldr (representAndMerge new_eclass_id) egr1 added_nodes - -- Modify created node according to analysis- & modifyA new_eclass_id + in ( new_eclass_id+ , egr2 ) {-# INLINABLE add #-} -- | Merge 2 e-classes by id-merge :: forall l. Language l => ClassId -> ClassId -> EGraph l -> (ClassId, EGraph l)+merge :: forall a l. (Analysis a l, Language l) => ClassId -> ClassId -> EGraph a l -> (ClassId, EGraph a l) merge a b egr0 = -- Use canonical ids@@ -159,22 +176,28 @@ -- Make leader the leader in the union find (new_id, new_uf) = unionSets leader sub (unionFind egr0)+ & first (\n -> assert (leader == n) n) -- Update leader class with all e-nodes and parents from the -- subsumed class- updatedLeader = leader_class & _parents %~ (sub_class^._parents <>)- & _nodes %~ (sub_class^._nodes <>)- & _data .~ new_data- new_data = joinA @l (leader_class^._data) (sub_class^._data)+ (updatedLeader, added_nodes) = leader_class+ & _parents %~ (sub_class^._parents <>)+ & _nodes %~ (sub_class^._nodes <>)+ & _data .~ new_data+ & modifyA + new_data = joinA @a @l (leader_class^._data) (sub_class^._data)+ -- Update leader in classes so that it has all nodes and parents -- from subsumed class, and delete the subsumed class- new_classes = ((IM.insert leader updatedLeader) . (IM.delete sub)) (classes egr0)+ --+ -- Additionally modify the e-class according to the analysis+ new_classes = (IM.insert leader updatedLeader . IM.delete sub) (classes egr0) -- Add all subsumed parents to worklist We can do this instead of -- adding the new e-class itself to the worklist because it would end -- up adding its parents anyway- new_worklist = toListSL (sub_class^._parents) <> (worklist egr0)+ new_worklist = toListSL (sub_class^._parents) <> worklist egr0 -- If the new_data is different from the classes, the parents of the -- class whose data is different from the merged must be put on the@@ -186,13 +209,13 @@ (if new_data /= (leader_class^._data) then toListSL (leader_class^._parents) else mempty) <>- (analysisWorklist egr0)+ analysisWorklist egr0 -- ROMES:TODO: The code that makes the -1 * cos test pass when some other things are tweaked -- new_memo = foldr (`insertNM` leader) (memo egr0) (sub_class^._nodes) -- Build new e-graph- new_egr = egr0+ egr1 = egr0 { unionFind = new_uf , classes = new_classes -- , memo = new_memo@@ -200,10 +223,9 @@ , analysisWorklist = new_analysis_worklist } - -- Modify according to analysis- & modifyA new_id+ egr2 = foldr (representAndMerge leader) egr1 added_nodes - in (new_id, new_egr)+ in (new_id, egr2) {-# INLINEABLE merge #-} @@ -212,12 +234,12 @@ -- similar to other approaches in how it restores congruence; but it uniquely -- allows the client to choose when to restore invariants in the context of a -- larger algorithm like equality saturation.-rebuild :: Language l => EGraph l -> EGraph l+rebuild :: (Analysis a l, Language l) => EGraph a l -> EGraph a l rebuild (EGraph uf cls mm wl awl) = -- empty worklists -- repair deduplicated e-classes let- emptiedEgr = (EGraph uf cls mm mempty mempty)+ emptiedEgr = EGraph uf cls mm mempty mempty wl' = nubOrd $ bimap (`find` emptiedEgr) (`canonicalize` emptiedEgr) <$> wl egr' = foldr repair emptiedEgr wl'@@ -234,7 +256,7 @@ -- ROMES:TODO: find repair_id could be shared between repair and repairAnal? -- | Repair a single worklist entry.-repair :: forall l. Language l => (ClassId, ENode l) -> EGraph l -> EGraph l+repair :: forall a l. (Analysis a l, Language l) => (ClassId, ENode l) -> EGraph a l -> EGraph a l repair (repair_id, node) egr = -- TODO We're no longer deleting the uncanonicalized node, how much does it matter that the structure keeps growing?@@ -247,20 +269,25 @@ {-# INLINE repair #-} -- | Repair a single analysis-worklist entry.-repairAnal :: forall l. Language l => (ClassId, ENode l) -> EGraph l -> EGraph l+repairAnal :: forall a l. (Analysis a l, Language l) => (ClassId, ENode l) -> EGraph a l -> EGraph a l repairAnal (repair_id, node) egr = let- c = (egr^._classes) IM.! repair_id- new_data = joinA @l (c^._data) (makeA node egr)+ c1 = (egr^._classes) IM.! repair_id+ new_data = joinA @a @l (c1^._data) (makeA @a ((\i -> egr^._class i^._data @a) <$> unNode node))+ (c2, added_nodes) = modifyA (c1 & _data .~ new_data) in -- Take action if the new_data is different from the existing data- if c^._data /= new_data+ if c1^._data /= new_data -- Merge result is different from original class data, update class -- with new_data- then egr { analysisWorklist = toListSL (c^._parents) <> analysisWorklist egr- }- & _classes %~ (IM.adjust (_data .~ new_data) repair_id)- & modifyA repair_id+ then+ let+ new_classes = IM.insert repair_id c2 (classes egr)+ egr1 = egr { analysisWorklist = toListSL (c1^._parents) <> analysisWorklist egr+ , classes = new_classes+ }+ egr2 = foldr (representAndMerge repair_id) egr1 added_nodes+ in egr2 else egr {-# INLINE repairAnal #-} @@ -272,17 +299,22 @@ -- that their e-class ids are represented by the same e-class canonical ids -- -- canonicalize(𝑓(𝑎,𝑏,𝑐,...)) = 𝑓((find 𝑎), (find 𝑏), (find 𝑐),...)-canonicalize :: Functor l => ENode l -> EGraph l -> ENode l+canonicalize :: Functor l => ENode l -> EGraph a l -> ENode l canonicalize (Node enode) eg = Node $ fmap (`find` eg) enode {-# INLINE canonicalize #-} -- | Find the canonical representation of an e-class id in the e-graph -- Invariant: The e-class id always exists.-find :: ClassId -> EGraph l -> ClassId+find :: ClassId -> EGraph a l -> ClassId find cid = findRepr cid . unionFind {-# INLINE find #-} -- | The empty e-graph. Nothing is represented in it yet.-emptyEGraph :: Language l => EGraph l+emptyEGraph :: Language l => EGraph a l emptyEGraph = EGraph emptyUF mempty mempty mempty mempty {-# INLINE emptyEGraph #-}++-- | Represent an expression (in fix-point form) and merge it with the e-class with the given id+representAndMerge :: (Analysis a l, Language l) => ClassId -> Fix l -> EGraph a l -> EGraph a l+representAndMerge o f g = case represent f g of+ (i, e) -> snd $ merge o i e
src/Data/Equality/Graph/Classes.hs view
@@ -17,21 +17,19 @@ import Data.Equality.Utils.SizedList -import Data.Equality.Analysis- -- | An e-class (an equivalence class of terms) of a language @l@. -- -- Intuitively, an e-graph is a set of equivalence classes (e-classes). Each -- e-class is a set of e-nodes representing equivalent terms from a given -- language, and an e-node is a function symbol paired with a list of children -- e-classes.-data EClass l = EClass+data EClass analysis_domain language = EClass { eClassId :: {-# UNPACK #-} !ClassId -- ^ E-class identifier- , eClassNodes :: !(S.Set (ENode l)) -- ^ E-nodes in this class- , eClassData :: Domain l -- ^ The analysis data associated with this eclass.- , eClassParents :: !(SList (ClassId, ENode l)) -- ^ E-nodes which are parents of this e-class and their corresponding e-class ids.+ , eClassNodes :: !(S.Set (ENode language)) -- ^ E-nodes in this class+ , eClassData :: analysis_domain -- ^ The analysis data associated with this eclass.+ , eClassParents :: !(SList (ClassId, ENode language)) -- ^ E-nodes which are parents of this e-class and their corresponding e-class ids. } -instance (Show (Domain l), Show1 l) => Show (EClass l) where+instance (Show a, Show1 l) => Show (EClass a l) where show (EClass a b d (SList c _)) = "Id: " <> show a <> "\nNodes: " <> show b <> "\nParents: " <> show c <> "\nData: " <> show d
src/Data/Equality/Graph/Classes.hs-boot view
@@ -4,5 +4,5 @@ import Data.Kind -type role EClass nominal-data EClass (l :: Type -> Type)+type role EClass representational nominal+data EClass a (l :: Type -> Type)
src/Data/Equality/Graph/Internal.hs view
@@ -11,19 +11,18 @@ import Data.Equality.Graph.ReprUnionFind import Data.Equality.Graph.Classes import Data.Equality.Graph.Nodes-import Data.Equality.Analysis -- | E-graph representing terms of language @l@. -- -- Intuitively, an e-graph is a set of equivalence classes (e-classes). Each e-class is a -- set of e-nodes representing equivalent terms from a given language, and an e-node is a function -- symbol paired with a list of children e-classes.-data EGraph l = EGraph- { unionFind :: !ReprUnionFind -- ^ Union find like structure to find canonical representation of an e-class id- , classes :: !(ClassIdMap (EClass l)) -- ^ Map canonical e-class ids to their e-classes- , memo :: !(Memo l) -- ^ Hashcons maps all canonical e-nodes to their e-class ids- , worklist :: !(Worklist l) -- ^ Worklist of e-class ids that need to be upward merged- , analysisWorklist :: !(Worklist l) -- ^ Like 'worklist' but for analysis repairing+data EGraph analysis language = EGraph+ { unionFind :: !ReprUnionFind -- ^ Union find like structure to find canonical representation of an e-class id+ , classes :: !(ClassIdMap (EClass analysis language)) -- ^ Map canonical e-class ids to their e-classes+ , memo :: !(Memo language) -- ^ Hashcons maps all canonical e-nodes to their e-class ids+ , worklist :: !(Worklist language) -- ^ Worklist of e-class ids that need to be upward merged+ , analysisWorklist :: !(Worklist language) -- ^ Like 'worklist' but for analysis repairing } -- | The hashcons 𝐻 is a map from e-nodes to e-class ids@@ -32,7 +31,7 @@ -- | Maintained worklist of e-class ids that need to be “upward merged” type Worklist l = [(ClassId, ENode l)] -instance (Show (Domain l), Show1 l) => Show (EGraph l) where+instance (Show a, Show1 l) => Show (EGraph a l) where show (EGraph a b c d e) = "UnionFind: " <> show a <> "\n\nE-Classes: " <> show b <>
src/Data/Equality/Graph/Internal.hs-boot view
@@ -4,6 +4,6 @@ import Data.Kind -type EGraph :: (Type -> Type) -> Type-type role EGraph nominal-data EGraph l+type role EGraph representational nominal+type EGraph :: Type -> (Type -> Type) -> Type+data EGraph a l
src/Data/Equality/Graph/Lens.hs view
@@ -20,7 +20,6 @@ import Data.Equality.Graph.Nodes import Data.Equality.Graph.Classes import Data.Equality.Graph.ReprUnionFind-import Data.Equality.Analysis -- | A 'Lens'' as defined in other lenses libraries type Lens' s a = forall f. Functor f => (a -> f a) -> (s -> f s)@@ -39,7 +38,7 @@ -- | Lens for the e-class with the given id in an e-graph -- -- Calls 'error' when the e-class doesn't exist-_class :: ClassId -> Lens' (EGraph l) (EClass l)+_class :: ClassId -> Lens' (EGraph a l) (EClass a l) _class i afa s = let canon_id = findRepr i (unionFind s) in (\c' -> s { classes = IM.insert canon_id c' (classes s) }) <$> afa (classes s IM.! canon_id)@@ -47,27 +46,27 @@ -- | Lens for the memo of e-nodes in an e-graph, that is, a mapping from -- e-nodes to the e-class they're represented in-_memo :: Lens' (EGraph l) (NodeMap l ClassId)+_memo :: Lens' (EGraph a l) (NodeMap l ClassId) _memo afa egr = (\m1 -> egr {memo = m1}) <$> afa (memo egr) {-# INLINE _memo #-} -- | Lens for the map of existing classes by id in an e-graph-_classes :: Lens' (EGraph l) (ClassIdMap (EClass l))+_classes :: Lens' (EGraph a l) (ClassIdMap (EClass a l)) _classes afa egr = (\m1 -> egr {classes = m1}) <$> afa (classes egr) {-# INLINE _classes #-} -- | Lens for the 'Domain' of an e-class-_data :: Lens' (EClass l) (Domain l)+_data :: Lens' (EClass domain l) domain _data afa EClass{..} = (\d1 -> EClass eClassId eClassNodes d1 eClassParents) <$> afa eClassData {-# INLINE _data #-} -- | Lens for the parent e-classes of an e-class-_parents :: Lens' (EClass l) (SList (ClassId, ENode l))-_parents afa EClass{..} = (\ps -> EClass eClassId eClassNodes eClassData ps) <$> afa eClassParents+_parents :: Lens' (EClass a l) (SList (ClassId, ENode l))+_parents afa EClass{..} = EClass eClassId eClassNodes eClassData <$> afa eClassParents {-# INLINE _parents #-} -- | Lens for the e-nodes in an e-class-_nodes :: Lens' (EClass l) (S.Set (ENode l))+_nodes :: Lens' (EClass a l) (S.Set (ENode l)) _nodes afa EClass{..} = (\ns -> EClass eClassId ns eClassData eClassParents) <$> afa eClassNodes {-# INLINE _nodes #-}
src/Data/Equality/Graph/Monad.hs view
@@ -28,11 +28,12 @@ import Data.Equality.Utils (Fix, cata) +import Data.Equality.Analysis import Data.Equality.Graph (EGraph, ClassId, Language, ENode(..)) import qualified Data.Equality.Graph as EG -- | E-graph stateful computation-type EGraphM s = State (EGraph s)+type EGraphM a l = State (EGraph a l) -- | Run EGraph computation on an empty e-graph --@@ -43,18 +44,18 @@ -- id2 <- represent t2 -- merge id1 id2 -- @-egraph :: Language l => EGraphM l a -> (a, EGraph l)+egraph :: Language l => EGraphM anl l a -> (a, EGraph anl l) egraph = runEGraphM EG.emptyEGraph {-# INLINE egraph #-} -- | Represent an expression (@Fix l@) in an e-graph by recursively -- representing sub expressions-represent :: Language l => Fix l -> EGraphM l ClassId+represent :: (Analysis anl l, Language l) => Fix l -> EGraphM anl l ClassId represent = cata $ sequence >=> add . Node {-# INLINE represent #-} -- | Add an e-node to the e-graph-add :: Language l => ENode l -> EGraphM l ClassId+add :: (Analysis anl l, Language l) => ENode l -> EGraphM anl l ClassId add = StateT . fmap pure . EG.add {-# INLINE add #-} @@ -62,7 +63,7 @@ -- -- E-graph invariants may be broken by merging, and 'rebuild' should be used -- /eventually/ to restore them-merge :: Language l => ClassId -> ClassId -> EGraphM l ClassId+merge :: (Analysis anl l, Language l) => ClassId -> ClassId -> EGraphM anl l ClassId merge a b = StateT (pure <$> EG.merge a b) {-# INLINE merge #-} @@ -73,11 +74,11 @@ -- 'rebuild') -- -- The paper describing rebuilding in detail is https://arxiv.org/abs/2004.03082-rebuild :: Language l => EGraphM l ()+rebuild :: (Analysis anl l, Language l) => EGraphM anl l () rebuild = StateT (pure . ((),). EG.rebuild) {-# INLINE rebuild #-} -- | Run 'EGraphM' computation on a given e-graph-runEGraphM :: EGraph l -> EGraphM l a -> (a, EGraph l)+runEGraphM :: EGraph anl l -> EGraphM anl l a -> (a, EGraph anl l) runEGraphM = flip runState {-# INLINE runEGraphM #-}
src/Data/Equality/Language.hs view
@@ -30,8 +30,6 @@ import Data.Functor.Classes -import Data.Equality.Analysis- -- | A 'Language' is the required constraint on /expressions/ that are to be -- represented in an e-graph. --@@ -40,5 +38,5 @@ -- e-graphs), note that it must satisfy the other class constraints. In -- particular an 'Data.Equality.Analysis.Analysis' must be defined for the -- language.-class (Analysis l, Traversable l, Ord1 l) => Language l where+class (Traversable l, Ord1 l) => Language l where
src/Data/Equality/Matching.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} {-|@@ -69,7 +68,7 @@ in mapMaybe f (genericJoin db q) -- | Convert an e-graph into a database-eGraphToDatabase :: Language l => EGraph l -> Database l+eGraphToDatabase :: Language l => EGraph a l -> Database l eGraphToDatabase egr = foldrWithKeyNM' addENodeToDB (DB mempty) (egr^._memo) where
src/Data/Equality/Saturation.hs view
@@ -48,14 +48,13 @@ import Data.Bifunctor import Control.Monad -import Data.Proxy- import Data.Equality.Utils import Data.Equality.Graph.Nodes import Data.Equality.Graph.Lens import qualified Data.Equality.Graph as G import Data.Equality.Graph.Monad import Data.Equality.Language+import Data.Equality.Analysis import Data.Equality.Graph.Classes import Data.Equality.Matching import Data.Equality.Matching.Database@@ -65,33 +64,33 @@ import Data.Equality.Saturation.Scheduler -- | Equality saturation with defaults-equalitySaturation :: forall l cost- . (Language l, Ord cost)+equalitySaturation :: forall a l cost+ . (Analysis a l, Language l, Ord cost) => Fix l -- ^ Expression to run equality saturation on- -> [Rewrite l] -- ^ List of rewrite rules+ -> [Rewrite a l] -- ^ List of rewrite rules -> CostFunction l cost -- ^ Cost function to extract the best equivalent representation- -> (Fix l, EGraph l) -- ^ Best equivalent expression and resulting e-graph-equalitySaturation = equalitySaturation' (Proxy @BackoffScheduler)+ -> (Fix l, EGraph a l) -- ^ Best equivalent expression and resulting e-graph+equalitySaturation = equalitySaturation' defaultBackoffScheduler -- | Run equality saturation on an expression given a list of rewrites, and -- extract the best equivalent expression according to the given cost function -- -- This variant takes all arguments instead of using defaults-equalitySaturation' :: forall l schd cost- . (Language l, Scheduler schd, Ord cost)- => Proxy schd -- ^ Proxy for the scheduler to use+equalitySaturation' :: forall a l schd cost+ . (Analysis a l, Language l, Scheduler schd, Ord cost)+ => schd -- ^ Scheduler to use -> Fix l -- ^ Expression to run equality saturation on- -> [Rewrite l] -- ^ List of rewrite rules+ -> [Rewrite a l] -- ^ List of rewrite rules -> CostFunction l cost -- ^ Cost function to extract the best equivalent representation- -> (Fix l, EGraph l) -- ^ Best equivalent expression and resulting e-graph-equalitySaturation' proxy expr rewrites cost = egraph $ do+ -> (Fix l, EGraph a l) -- ^ Best equivalent expression and resulting e-graph+equalitySaturation' schd expr rewrites cost = egraph $ do -- Represent expression as an e-graph origClass <- represent expr -- Run equality saturation (by applying non-destructively all rewrites)- runEqualitySaturation proxy rewrites+ runEqualitySaturation schd rewrites -- Extract best solution from the e-class of the original expression gets $ \g -> extractBest g cost origClass@@ -100,17 +99,17 @@ -- | Run equality saturation on an e-graph by non-destructively applying all -- given rewrite rules until saturation (using the given 'Scheduler')-runEqualitySaturation :: forall l schd- . (Language l, Scheduler schd)- => Proxy schd -- ^ Proxy for the scheduler to use- -> [Rewrite l] -- ^ List of rewrite rules- -> EGraphM l ()-runEqualitySaturation _ rewrites = runEqualitySaturation' 0 mempty where -- Start at iteration 0+runEqualitySaturation :: forall a l schd+ . (Analysis a l, Language l, Scheduler schd)+ => schd -- ^ Scheduler to use+ -> [Rewrite a l] -- ^ List of rewrite rules+ -> EGraphM a l ()+runEqualitySaturation schd rewrites = runEqualitySaturation' 0 mempty where -- Start at iteration 0 -- Take map each rewrite rule to stats on its usage so we can do -- backoff scheduling. Each rewrite rule is assigned an integer -- (corresponding to its position in the list of rewrite rules)- runEqualitySaturation' :: Int -> IM.IntMap (Stat schd) -> EGraphM l ()+ runEqualitySaturation' :: Int -> IM.IntMap (Stat schd) -> EGraphM a l () runEqualitySaturation' 30 _ = return () -- Stop after X iterations runEqualitySaturation' i stats = do @@ -140,7 +139,7 @@ && IM.size afterClasses == IM.size beforeClasses) (runEqualitySaturation' (i+1) newStats) - matchWithScheduler :: Database l -> Int -> IM.IntMap (Stat schd) -> (Int, Rewrite l) -> ([(Rewrite l, Match)], IM.IntMap (Stat schd))+ matchWithScheduler :: Database l -> Int -> IM.IntMap (Stat schd) -> (Int, Rewrite a l) -> ([(Rewrite a l, Match)], IM.IntMap (Stat schd)) matchWithScheduler db i stats = \case (rw_id, rw :| cnd) -> first (map (first (:| cnd))) $ matchWithScheduler db i stats (rw_id, rw) (rw_id, lhs := rhs) -> do@@ -156,11 +155,11 @@ let matches' = ematch db lhs -- Add rewrite to the e-match substitutions -- Backoff scheduler: update stats- let newStats = updateStats @schd i rw_id x stats matches'+ let newStats = updateStats schd i rw_id x stats matches' (map (lhs := rhs,) matches', newStats) - applyMatchesRhs :: (Rewrite l, Match) -> EGraphM l ()+ applyMatchesRhs :: (Rewrite a l, Match) -> EGraphM a l () applyMatchesRhs = \case (rw :| cond, m@(Match subst _)) -> do@@ -189,12 +188,12 @@ return () -- | Represent a pattern in the e-graph a pattern given substitions- reprPat :: Subst -> l (Pattern l) -> EGraphM l ClassId+ reprPat :: Subst -> l (Pattern l) -> EGraphM a l ClassId reprPat subst = add . Node <=< traverse \case VariablePattern v -> case IM.lookup v subst of Nothing -> error "impossible: couldn't find v in subst?" Just i -> return i NonVariablePattern p -> reprPat subst p- {-# INLINEABLE runEqualitySaturation #-}+
src/Data/Equality/Saturation/Rewrites.hs view
@@ -8,6 +8,8 @@ -} module Data.Equality.Saturation.Rewrites where +import Data.Functor.Classes+ import Data.Equality.Graph import Data.Equality.Matching import Data.Equality.Matching.Database@@ -31,8 +33,8 @@ -- -- See the definition of @is_not_zero@ in the documentation for -- 'RewriteCondition'-data Rewrite lang = !(Pattern lang) := !(Pattern lang) -- ^ Trivial Rewrite- | !(Rewrite lang) :| !(RewriteCondition lang) -- ^ Conditional Rewrite+data Rewrite anl lang = !(Pattern lang) := !(Pattern lang) -- ^ Trivial Rewrite+ | !(Rewrite anl lang) :| !(RewriteCondition anl lang) -- ^ Conditional Rewrite infix 3 := infixl 2 :| @@ -48,5 +50,9 @@ -- Just class_id -> -- egr^._class class_id._data /= Just 0 -- @-type RewriteCondition lang = Subst -> EGraph lang -> Bool+type RewriteCondition anl lang = Subst -> EGraph anl lang -> Bool ++instance Show1 lang => Show (Rewrite anl lang) where+ show (rw :| _) = show rw <> " :| <cond>"+ show (lhs := rhs) = show lhs <> " := " <> show rhs
src/Data/Equality/Saturation/Scheduler.hs view
@@ -11,7 +11,7 @@ -} module Data.Equality.Saturation.Scheduler- ( Scheduler(..), BackoffScheduler+ ( Scheduler(..), BackoffScheduler(..), defaultBackoffScheduler ) where import qualified Data.IntMap.Strict as IM@@ -21,10 +21,11 @@ -- being used based on statistics it defines and collects on applied rewrite -- rules. class Scheduler s where- type Stat s+ data Stat s -- | Scheduler: update stats- updateStats :: Int -- ^ Iteration we're in+ updateStats :: s -- ^ The scheduler itself+ -> Int -- ^ Iteration we're in -> Int -- ^ Index of rewrite rule we're updating -> Maybe (Stat s) -- ^ Current stat for this rewrite rule (we already got it so no point in doing a lookup again) -> IM.IntMap (Stat s) -- ^ The current stats map@@ -46,11 +47,23 @@ -- taking an unfair amount of resources. -- -- Originaly in [egg](https://docs.rs/egg/0.6.0/egg/struct.BackoffScheduler.html)-data BackoffScheduler+data BackoffScheduler = BackoffScheduler+ { matchLimit :: {-# UNPACK #-} !Int+ , banLength :: {-# UNPACK #-} !Int }++-- | The default 'BackoffScheduler'.+-- +-- The match limit is set to @1000@ and the ban length is set to @10@.+defaultBackoffScheduler :: BackoffScheduler+defaultBackoffScheduler = BackoffScheduler 1000 10+ instance Scheduler BackoffScheduler where- type Stat BackoffScheduler = BoSchStat+ data Stat BackoffScheduler =+ BSS { bannedUntil :: {-# UNPACK #-} !Int+ , timesBanned :: {-# UNPACK #-} !Int+ } deriving Show - updateStats i rw currentStat stats matches =+ updateStats bos i rw currentStat stats matches = if total_len > threshold @@ -65,16 +78,13 @@ -- TODO: Overall difficult, and buggy at the moment. total_len = sum (map (length . matchSubst) matches) - defaultMatchLimit = 1000- defaultBanLength = 10- bannedN = case currentStat of Nothing -> 0; Just (timesBanned -> n) -> n - threshold = defaultMatchLimit * (2^bannedN)+ threshold = matchLimit bos * (2^bannedN) - ban_length = defaultBanLength * (2^bannedN)+ ban_length = banLength bos * (2^bannedN) updateBans = \case Nothing -> Just (BSS (i + ban_length) 1)@@ -82,7 +92,3 @@ isBanned i s = i < bannedUntil s --data BoSchStat = BSS { bannedUntil :: {-# UNPACK #-} !Int- , timesBanned :: {-# UNPACK #-} !Int- } deriving Show
test/Invariants.hs view
@@ -9,7 +9,6 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE RecordWildCards #-} module Invariants where import Test.Tasty@@ -27,7 +26,6 @@ import Data.Equality.Graph.Monad as GM import Data.Equality.Graph.Lens import Data.Equality.Graph-import Data.Equality.Analysis import Data.Equality.Extraction import Data.Equality.Saturation import Data.Equality.Matching@@ -40,12 +38,6 @@ newtype SimpleExpr l = SE (Expr l) deriving (Functor, Foldable, Traversable, Show1, Eq1, Ord1, Language) -instance Analysis SimpleExpr where- type Domain SimpleExpr = ()- makeA _ _ = ()- joinA = (<>)- modifyA _ = id- -- | When a rewrite of type "x":=c where x is a pattern variable and c is a -- constant is used in equality saturation of any expression, all e-classes -- should be merged into a single one, since all classes are equal to c and@@ -53,11 +45,11 @@ patFoldAllClasses :: forall l. (Language l, Num (Pattern l)) => Fix l -> Integer -> Bool patFoldAllClasses expr i =- case IM.toList $ (eg^._classes) of+ case IM.toList (eg^._classes) of [_] -> True _ -> False where- eg :: EGraph l+ eg :: EGraph () l eg = snd $ equalitySaturation expr [VariablePattern 1:=fromInteger i] (error "Cost function shouldn't be used" :: CostFunction l Int) -- | Test 'compileToQuery'.@@ -93,7 +85,7 @@ -- | If we match a singleton variable pattern against an e-graph, we should get -- a match on all e-classes in the e-graph-ematchSingletonVar :: Language lang => Var -> EGraph lang -> Bool+ematchSingletonVar :: Language lang => Var -> EGraph () lang -> Bool ematchSingletonVar v eg = let db = eGraphToDatabase eg@@ -122,7 +114,7 @@ -- -- ROMES:TODO Should I rebuild it here? Then the property test is that after rebuilding ...HashConsInvariant hashConsInvariant :: forall l. Language l- => EGraph l -> Bool+ => EGraph () l -> Bool hashConsInvariant eg = all f (IM.toList (eg^._classes)) where@@ -134,7 +126,7 @@ Just i' -> i' == find i eg benchSaturate :: forall l. Language l- => [Rewrite l] -> (l Int -> Int) -> Fix l -> Bool+ => [Rewrite () l] -> (l Int -> Int) -> Fix l -> Bool benchSaturate rws cost expr = equalitySaturation expr rws cost `seq` True @@ -142,12 +134,12 @@ -- ROMES:TODO: Property: Extract expression after equality saturation is always better or equal to the original expression -- ROMES:TODO: Use action trick https://jaspervdj.be/posts/2015-03-13-practical-testing-in-haskell.html-instance Arbitrary (EGraph SimpleExpr) where+instance Arbitrary (EGraph () SimpleExpr) where arbitrary = sized $ \n -> do exps <- forM [0..n] $ const arbitrary -- rws :: [Rewrite Expr] <- forM [0..n] $ const arbitrary (ids, eg) <- return $ egraph $- mapM represent exps+ mapM GM.represent exps ids1 <- sublistOf ids ids2 <- sublistOf ids return $ snd $ runEGraphM eg $ do
test/Lambda.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-}@@ -8,9 +10,14 @@ {-# LANGUAGE DeriveTraversable #-} module Lambda where +import Data.String+ import Test.Tasty import Test.Tasty.HUnit +import Data.Maybe++import qualified Data.IntMap as IM import qualified Data.Set as S import Control.Applicative ((<|>))@@ -19,26 +26,29 @@ import Data.Ord.Deriving import Text.Show.Deriving -import Data.Equality.Graph.Lens-import Data.Equality.Graph.Monad as GM import Data.Equality.Graph import Data.Equality.Extraction import Data.Equality.Analysis import Data.Equality.Saturation import Data.Equality.Matching+import Data.Equality.Matching.Database as D+import Data.Equality.Graph.Lens data Lambda a- = Bool Bool- | Num Int- | Var a+ = Bool !Bool+ | Num !Int+ | Symbol !String+ | Use a+ | Subst a a a+ | Add a a | Eq a a+ | If a a a+ | App a a | Lam a a | Let a a a | LFix a a- | If a a a- | Symbol String deriving ( Eq, Ord, Functor , Foldable, Traversable )@@ -47,72 +57,109 @@ deriveOrd1 ''Lambda deriveShow1 ''Lambda -data Data = Data { free :: S.Set ClassId- , constant :: Maybe (Fix Lambda)- } deriving Eq--evalL :: EGraph Lambda -> Lambda ClassId -> Maybe (Fix Lambda)-evalL egr = \case- Bool n -> Just (Fix $ Bool n)- Num n -> Just (Fix $ Num n)+evalL :: Lambda (Maybe (Lambda ())) -> Maybe (Lambda ())+evalL = \case+ Bool n -> Just (Bool n)+ Num n -> Just (Num n) Add a b -> do- a' <- constant (egr^._class a._data) >>= num- b' <- constant (egr^._class b._data) >>= num- return (Fix $ Num $ a' + b')+ a' <- a >>= num+ b' <- b >>= num+ return (Num $ a' + b') Eq a b -> do- a' <- constant (egr^._class a._data)- b' <- constant (egr^._class b._data)- return (Fix $ Bool $ a' == b')+ a' <- a+ b' <- b+ return (Bool $ a' == b') _ -> Nothing where- num :: Fix Lambda -> Maybe Int+ num :: Lambda () -> Maybe Int num = \case- Fix (Num i) -> Just i+ Num i -> Just i _ -> Nothing -instance Analysis Lambda where- type Domain Lambda = Data+type FreeVars = S.Set String+-- the lambda evaluator analysis is a combined analysis of the free variable analysis and the constant folding analysis+type LA = (FreeVars, Maybe (Lambda ())) - makeA n egr =- let- freeVs = case unNode n of- Var x -> S.singleton x- Let v a b ->- free (egr^._class a._data) <> S.delete v (free (egr^._class b._data))- Lam v a -> S.delete v (free (egr^._class a._data))- LFix v a -> S.delete v (free (egr^._class a._data))- _ -> mconcat (map (\i -> free $ egr^._class i._data) (children n))+-- Constant folding for lambda evaluator+instance Analysis (Maybe (Lambda ())) Lambda where+ makeA = evalL+ joinA = (<|>)+ modifyA c = case c^._data of+ Nothing -> (c, [])+ Just v -> (c, [f v])+ where+ f = \case+ Bool b -> Fix $ Bool b+ Num i -> Fix $ Num i+ _ -> error "impossible, lambda () can't construct this"+ - cnst = evalL egr (unNode n)- in- Data freeVs cnst+-- Free variable analysis for lambda+instance Analysis FreeVars Lambda where+ makeA = \case+ Use x -> x+ Let v a b -> (b S.\\ v) <> a+ Lam v a -> a S.\\ v+ LFix v a -> a S.\\ v+ Bool _ -> mempty+ Num _ -> mempty+ Add a b -> a <> b+ Eq a b -> a <> b+ App a b -> a <> b+ If a b c -> a <> b <> c+ Symbol x -> S.singleton x+ Subst a b c -> b <> a <> c - joinA (Data fv1 c1) (Data fv2 c2) =- Data (fv1 `S.intersection` fv2) (c1 <|> c2)+ joinA = (<>) - -- modifyA :: ClassId -> EGraph l -> EGraph l- modifyA i egr = - case constant (egr^._class i._data) of- Nothing -> egr- Just c -> snd $ runEGraphM egr $ do- new_c <- represent c- GM.merge i new_c instance Language Lambda instance Num (Fix Lambda) where fromInteger = Fix . Num . fromInteger- (+) = error "todo..."+ (+) a b = Fix $ Add a b (-) = error "todo..." (*) = error "todo..." abs = error "todo..." signum = error "todo..." -rules :: [Rewrite Lambda]+unsafeGetSubst :: Pattern Lambda -> D.Subst -> ClassId+unsafeGetSubst (NonVariablePattern _) _ = error "unsafeGetSubst: NonVariablePattern; expecting VariablePattern"+unsafeGetSubst (VariablePattern v) subst = case IM.lookup v subst of+ Nothing -> error "Searching for non existent bound var in conditional"+ Just class_id -> class_id++isConst :: Pattern Lambda -> RewriteCondition LA Lambda+isConst v subst egr = isJust $ snd $ egr^._class (unsafeGetSubst v subst)._data++isNotSameVar :: Pattern Lambda -> Pattern Lambda -> RewriteCondition LA Lambda+isNotSameVar v1 v2 subst egr = find (unsafeGetSubst v1 subst) egr /= find (unsafeGetSubst v2 subst) egr++rules :: [Rewrite LA Lambda] rules = [ ifP trP "x" "y" := "x" , ifP flP "x" "y" := "y"- -- , ifP (pat $ eq (varP "x") "e" "then" "else") := "else" :| if ...+ -- , ifP (pat $ Eq (pat $ Use "x") "e") "then" "else" := "else" :| conditionEqual (pat $ Let "x" "e" "then") (pat $ Let "x" "e" "else")++ , pat (Add "x" "y") := pat (Add "y" "x")+ , pat (Add (pat $ Add "x" "y") "z") := pat (Add "x" $ pat $ Add "y" "z")+ , pat (Eq "x" "y") := pat (Eq "y" "x")++ -- substitution introduction+ , pat (LFix "v" "e") := pat (Let "v" (pat $ LFix "v" "e") "e")+ , pat (App (pat $ Lam "v" "body") "e") := pat (Let "v" "e" "body")++ -- substitution propagation+ , pat (Let "v" "e" (pat $ App "a" "b")) := pat (App (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b"))+ , pat (Let "v" "e" (pat $ Add "a" "b")) := pat (Add (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b"))+ , pat (Let "v" "e" (pat $ Eq "a" "b")) := pat (Eq (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b"))+ , pat (Let "v" "e" (pat $ If "a" "b" "c")) := pat (If (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b") (pat $ Let "v" "e" "c"))++ -- substitution elimination+ , pat (Let "v" "e" "c") := "c" :| isConst "c" -- let const+ , pat (Let "v1" "e" (pat $ Use "v1")) := "e" -- let var same+ , pat (Let "v1" "e" (pat $ Use "v2")) := "v2" :| isNotSameVar "v1" "v2" -- let var diff+ , pat (Let "v1" "e" (pat $ Lam "v1" "body")) := pat (Lam "v1" "body") -- let lam same ] rewrite :: Fix Lambda -> Fix Lambda@@ -125,18 +172,33 @@ , testCase "if fl" $ rewrite (ifL fl 1 2) @?= 2- ] + , testCase "lambda_under" $+ -- \x -> 4 + ((\y -> y) 4) = \x -> 8+ rewrite (lam "x" (4 + app (lam "y" (var "y")) 4)) @?= lam "x" 8 + {-+ This test requires at least the ConditionEqual rewrite condition helper+ and possibly dynamic rewrites. It would also be better to improve+ rewrite conditions before continuing down this path. + For the analysis patch, being able to define the analysis+ compositionally and without expressiveness problems is good enough. + , testCase "lambda_compose_many" $+ rewrite (Fix (Let "compose" (lam "f" (lam "g" (lam "x" (app (var "f") (app (var "g") (var "x"))))))+ (Fix $ Let "add1" (lam "y" (Fix $ Add (var "y") 1)) (app (app (var "compose") (var "add1"))+ (app (app (var "compose") (var "add1"))+ (app (app (var "compose") (var "add1"))+ (var "add1"))))))) @?= lam "x" (Fix $ Add "x" 5)+ -}+ ]+ ifP :: Pattern Lambda -> Pattern Lambda -> Pattern Lambda -> Pattern Lambda ifP a b c = pat (If a b c) trP, flP :: Pattern Lambda trP = pat (Bool True) flP = pat (Bool False)-varP :: Pattern Lambda -> Pattern Lambda-varP x = pat (Var x) -- TODO: recursion-schemes extension in separate package ifL :: Fix Lambda -> Fix Lambda -> Fix Lambda -> Fix Lambda@@ -144,3 +206,12 @@ tr, fl :: Fix Lambda tr = Fix $ Bool True fl = Fix $ Bool False+lam :: Fix Lambda -> Fix Lambda -> Fix Lambda+lam i = Fix . Lam i+var :: Fix Lambda -> Fix Lambda+var = Fix . Use+app :: Fix Lambda -> Fix Lambda -> Fix Lambda+app x y = Fix $ App x y++instance IsString (Fix Lambda) where+ fromString = Fix . Symbol
test/SimpleSym.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DeriveTraversable #-}@@ -17,6 +19,7 @@ import Data.Equality.Saturation import Data.Equality.Language import Data.Equality.Analysis+import Data.Equality.Graph.Lens ((^.), _data) data SymExpr a = Const Double | Symbol String@@ -31,13 +34,25 @@ deriveOrd1 ''SymExpr deriveShow1 ''SymExpr -instance Analysis SymExpr where- type Domain SymExpr = ()- makeA _ _ = ()- joinA _ _ = ()- instance Language SymExpr +instance Analysis (Maybe Double) SymExpr where+ makeA = \case+ Const x -> Just x+ Symbol _ -> Nothing+ x :+: y -> (+) <$> x <*> y+ x :*: y -> (*) <$> x <*> y+ x :/: y -> (/) <$> x <*> y++ joinA Nothing (Just x) = Just x+ joinA (Just x) Nothing = Just x+ joinA Nothing Nothing = Nothing+ joinA (Just x) (Just y) = if x == y then Just x else error "ouch, that shouldn't have happened"++ modifyA c = case c^._data of+ Nothing -> (c, [])+ Just i -> (c, [Fix (Const i)])+ cost :: CostFunction SymExpr Int cost = \case Const _ -> 1@@ -46,20 +61,21 @@ c1 :*: c2 -> c1 + c2 + 3 c1 :/: c2 -> c1 + c2 + 4 -rewrites :: [Rewrite SymExpr]+rewrites :: [Rewrite (Maybe Double) SymExpr] rewrites = [ pat (pat ("a" :*: "b") :/: "c") := pat ("a" :*: pat ("b" :/: "c")) , pat ("x" :/: "x") := pat (Const 1)- , pat ("x" :*: (pat (Const 1))) := "x"+ , pat ("x" :*: pat (Const 1)) := "x" ] rewrite :: Fix SymExpr -> Fix SymExpr rewrite e = fst (equalitySaturation e rewrites cost) e1 :: Fix SymExpr-e1 = Fix (Fix (Fix (Symbol "x") :*: Fix (Const 2)) :/: (Fix (Const 2))) -- (x*2)/2+e1 = Fix (Fix (Fix (Symbol "x") :*: Fix (Const 2)) :/: Fix (Const 2)) -- (x*2)/2 simpleSymTests :: TestTree simpleSymTests = testGroup "Simple Sym"- [ testCase "(a*2)/2 = a" $ rewrite e1 @?= Fix (Symbol "x")+ [ testCase "(a*2)/2 = a" $ rewrite e1 @?= Fix (Symbol "x")+ , testCase "(x/x)+1) = 4" $ rewrite (Fix $ Fix (Const 3) :+: Fix (Fix (Symbol "x") :/: Fix (Symbol "x"))) @?= Fix (Const 4) ]
test/Sym.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-}@@ -25,7 +26,6 @@ import Control.Applicative (liftA2) import Control.Monad (unless) -import Data.Equality.Graph.Monad as GM import Data.Equality.Graph.Lens import Data.Equality.Graph import Data.Equality.Extraction@@ -109,10 +109,9 @@ -- | Define analysis for the @Expr@ language over domain @Maybe Double@ for -- constant folding-instance Analysis Expr where- type Domain Expr = Maybe Double+instance Analysis (Maybe Double) Expr where - makeA (Node e) egr = evalConstant ((\c -> egr^._class c._data) <$> e)+ makeA = evalConstant -- joinA = (<|>) joinA ma mb = do@@ -124,17 +123,16 @@ !_ <- unless (a == b || (a == 0 && b == (-0)) || (a == (-0) && b == 0)) (error "Merged non-equal constants!") return a - modifyA i egr =- case egr ^._class i._data of- Nothing -> egr- Just d -> snd $ runEGraphM egr $ do+ modifyA cl = case cl^._data of+ Nothing -> (cl, [])+ Just d -> ((_nodes %~ S.filter (F.null .unNode)) cl, [Fix (Const d)]) - -- Add constant as e-node- new_c <- represent (Fix $ Const d)- _ <- GM.merge i new_c+ -- -- Add constant as e-node+ -- new_c <- represent (Fix $ Const d)+ -- _ <- GM.merge i new_c - -- Prune all except leaf e-nodes- modify (_class i._nodes %~ S.filter (F.null . unNode))+ -- -- Prune all except leaf e-nodes+ -- modify (_class i._nodes %~ S.filter (F.null . unNode)) @@ -161,19 +159,19 @@ Nothing -> error "Searching for non existent bound var in conditional" Just class_id -> class_id -is_not_zero :: Pattern Expr -> RewriteCondition Expr+is_not_zero :: Pattern Expr -> RewriteCondition (Maybe Double) Expr is_not_zero v subst egr = egr^._class (unsafeGetSubst v subst)._data /= Just 0 -is_sym :: Pattern Expr -> RewriteCondition Expr+is_sym :: Pattern Expr -> RewriteCondition (Maybe Double) Expr is_sym v subst egr = any ((\case (Sym _) -> True; _ -> False) . unNode) (egr^._class (unsafeGetSubst v subst)._nodes) -is_const :: Pattern Expr -> RewriteCondition Expr+is_const :: Pattern Expr -> RewriteCondition (Maybe Double) Expr is_const v subst egr = isJust (egr^._class (unsafeGetSubst v subst)._data) -is_const_or_distinct_var :: Pattern Expr -> Pattern Expr -> RewriteCondition Expr+is_const_or_distinct_var :: Pattern Expr -> Pattern Expr -> RewriteCondition (Maybe Double) Expr is_const_or_distinct_var v w subst egr = let v' = unsafeGetSubst v subst w' = unsafeGetSubst w subst@@ -181,7 +179,7 @@ && (isJust (egr^._class v'._data) || any ((\case (Sym _) -> True; _ -> False) . unNode) (egr^._class v'._nodes)) -rewrites :: [Rewrite Expr]+rewrites :: [Rewrite (Maybe Double) Expr] rewrites = [ "a"+"b" := "b"+"a" -- comm add , "a"*"b" := "b"*"a" -- comm mul@@ -257,9 +255,9 @@ symTests :: TestTree symTests = testGroup "Symbolic" [ testCase "(a*2)/2 = a (custom rules)" $- fst (equalitySaturation (("a"*2)/2) [ ("x"*"y")/"z" := "x"*("y"/"z")- , "y"/"y" := 1- , "x"*1 := "x"] symCost) @?= "a"+ fst (equalitySaturation @(Maybe Double) (("a"*2)/2) [ ("x"*"y")/"z" := "x"*("y"/"z")+ , "y"/"y" := 1+ , "x"*1 := "x"] symCost) @?= "a" , testCase "(a/2)*2 = a (all rules)" $ rewrite (("a"/2)*2) @?= "a"@@ -269,7 +267,7 @@ , testCase "x/y (custom rules)" $ -- without backoff scheduler this will loop forever- fst (equalitySaturation+ fst (equalitySaturation @(Maybe Double) ("x"/"y") [ "x"/"y" := "x"*(1/"y")@@ -282,13 +280,13 @@ fst (equalitySaturation (0+1) rewrites symCost) @?= 1 , testCase "b*(1/b) = 1 (custom rules)" $- fst (equalitySaturation ("b"*(1/"b")) [ "a"*(1/"a") := 1 ] symCost) @?= 1+ fst (equalitySaturation @(Maybe Double) ("b"*(1/"b")) [ "a"*(1/"a") := 1 ] symCost) @?= 1 , testCase "1+1=2 (constant folding)" $- fst (equalitySaturation (1+1) [] symCost) @?= 2+ fst (equalitySaturation @(Maybe Double) (1+1) [] symCost) @?= 2 , testCase "a*(2-1) (1 rule + constant folding)" $- fst (equalitySaturation ("a" * (2-1)) ["x"*1:="x"] symCost) @?= "a"+ fst (equalitySaturation @(Maybe Double) ("a" * (2-1)) ["x"*1:="x"] symCost) @?= "a" , testCase "1+a*(2-1) = 1+a (all + constant folding)" $ rewrite (1+("a"*(2-1))) @?= (1+"a")
+ test/T1.hs view
@@ -0,0 +1,145 @@+{-# language DeriveTraversable #-}+{-# language LambdaCase #-}+{-# language TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -Wno-unused-matches #-}+module T1 (main) where++import Test.Tasty.HUnit+import Data.Eq.Deriving+import Data.Ord.Deriving+import Text.Show.Deriving++import Data.Equality.Graph+import Data.Equality.Matching+import Data.Equality.Saturation+import Data.Equality.Saturation.Scheduler++data TreeF a = VarF Int+ | ConstF Double+ | AddF a a+ | SubF a a+ | MulF a a+ | DivF a a+ | LogF a+ deriving (Functor, Foldable, Traversable)++deriveEq1 ''TreeF+deriveOrd1 ''TreeF+deriveShow1 ''TreeF++instance Num (Fix TreeF) where+ l + r = Fix $ AddF l r+ l - r = Fix $ SubF l r+ l * r = Fix $ MulF l r+ abs = undefined++ negate t = fromInteger (-1) * t+ signum t = undefined+ fromInteger = Fix . ConstF . fromInteger++instance Fractional (Fix TreeF) where+ (/) a b = Fix (DivF a b)+ fromRational = Fix . ConstF . fromRational++instance Floating (Fix TreeF) where+ pi = undefined+ exp = undefined+ log = Fix . LogF+ sqrt = undefined+ sin = undefined+ cos = undefined+ tan = undefined+ asin = undefined+ acos = undefined+ atan = undefined+ sinh = undefined+ cosh = undefined+ tanh = undefined+ asinh = undefined+ acosh = undefined+ atanh = undefined++ l ** r = undefined+ logBase l r = undefined++instance Num (Pattern TreeF) where+ l + r = NonVariablePattern $ AddF l r+ l - r = NonVariablePattern $ SubF l r+ l * r = NonVariablePattern $ MulF l r+ abs = undefined++ negate t = fromInteger (-1) * t+ signum t = undefined+ fromInteger = NonVariablePattern . ConstF . fromInteger++instance Fractional (Pattern TreeF) where+ (/) a b = NonVariablePattern (DivF a b)+ fromRational = NonVariablePattern . ConstF . fromRational++instance Floating (Pattern TreeF) where+ pi = undefined+ exp = undefined+ log = NonVariablePattern . LogF+ sqrt = undefined+ sin = undefined+ cos = undefined+ tan = undefined+ asin = undefined+ acos = undefined+ atan = undefined+ sinh = undefined+ cosh = undefined+ tanh = undefined+ asinh = undefined+ acosh = undefined+ atanh = undefined++ l ** r = undefined+ logBase l r = undefined++instance Language TreeF++cost :: CostFunction TreeF Int+cost = \case+ ConstF _ -> 5+ VarF _ -> 1+ AddF c1 c2 -> c1 + c2 + 2+ SubF c1 c2 -> c1 + c2 + 2+ MulF c1 c2 -> c1 + c2 + 4+ DivF c1 c2 -> c1 + c2 + 5+ LogF c -> c + 2++tmpRewrites :: [Rewrite () TreeF]+tmpRewrites = [+ "x" + "y" := "y" + "x"+ , "x" * "y" := "y" * "x"+ , "x" + ("y" + "z") := ("x" + "y") + "z"+ , "x" * ("y" * "z") := ("x" * "y") * "z"+ , "x" * ("y" / "z") := ("x" * "y") / "z"+ , "x" + 0 := "x"+ , "x" * 1 := "x"+ , "x" * 0 := 0+ , "x" / "x" := 1+ , ("x" * "y") + ("x" * "z") := "x" * ("y" + "z") + , negate ("x" + "y") := negate "x" - "y"+ , 0 - "x" := negate "x"+ , log ("x" * "y") := log "x" + log "y"+ , log ("x" / "y") := log "x" - log "y"+ , log 1 := 0+ ]++rewriteTree :: Fix TreeF -> (Fix TreeF, EGraph () TreeF)+rewriteTree t = equalitySaturation' (BackoffScheduler 1000 15) t tmpRewrites cost++x, y :: Fix TreeF+x = Fix (VarF 0)+y = Fix (VarF 1)++main :: IO ()+main = do+ fst (rewriteTree ((log x) / (x * ((y / y) / y)))) @?= (Fix $ DivF (Fix $ LogF (Fix $ VarF 0)) (Fix $ DivF (Fix $ VarF 0) (Fix $ VarF 1)))+ pure ()+
+ test/T2.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE OverloadedStrings #-}+module T2 where++-- Tests whether this saturates just like mwillsey claims that it does in egg!++import Prelude hiding (not)++import Test.Tasty.HUnit+import Data.Deriving+import Data.Equality.Matching+import Data.Equality.Language+import Data.Equality.Extraction+import Data.Equality.Saturation++data Lang a = And a a+ | Or a a+ | Not a+ | ToElim a+ | Sym Int+ deriving (Functor, Foldable, Traversable)++deriveEq1 ''Lang+deriveOrd1 ''Lang+deriveShow1 ''Lang++instance Language Lang++x, y :: Pattern Lang+x = "x"+y = "y"+not :: Pattern Lang -> Pattern Lang+not = pat . Not++rules :: [Rewrite () Lang]+rules =+ [ pat (x `And` y) := not (pat (not x `Or` not y))+ , pat (x `Or` y) := not (pat (not x `And` not y))+ , not (not x) := pat (ToElim x)+ , pat (ToElim x) := x+ ]++main :: IO ()+main = do+ fst (equalitySaturation (Fix $ (Fix $ Not $ Fix $ Sym 0) `And` (Fix $ Not $ Fix $ Sym 1)) rules depthCost) @?= Fix (Not $ Fix $ (Fix $ Sym 0) `Or` (Fix $ Sym 1))+ pure ()+
test/Test.hs view
@@ -1,18 +1,27 @@+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-} import Test.Tasty+import Test.Tasty.HUnit +import Control.Exception+ -- import Data.Equality.Utils import Invariants import Sym import Lambda import SimpleSym +import qualified T1+import qualified T2+ tests :: TestTree tests = testGroup "Tests" [ symTests , lambdaTests , simpleSymTests , invariants+ , testCase "T1" (T1.main `catch` (\(e :: SomeException) -> assertFailure (show e)))+ , testCase "T2" (T2.main `catch` (\(e :: SomeException) -> assertFailure (show e))) ] main :: IO ()