ampersand-3.0.0: src/lib/DatabaseDesign/Ampersand/ADL1/Lattices.hs
{-# OPTIONS_GHC -Wall -XFlexibleInstances #-}
module DatabaseDesign.Ampersand.ADL1.Lattices (findExact,findSubsets,optimize1,Op1EqualitySystem,addEquality,emptySystem,FreeLattice(..),getGroups,isInSystem) where
import qualified Data.IntMap as IntMap
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.IntSet as IntSet
import Data.List (sort, partition)
-- optimisations possible for the EqualitySystem(s):
-- (1) apply optimize1 inline, that is: don't use EqualitySystem but use ES1 instead
-- (2) include the transitively dependent rules recursively
data EqualitySystem a
= ES (Map.Map a Int) -- whatever this is a system of
(IntMap.IntMap -- map for: whenever you encounter this element i in your set y
[( IntSet.IntSet -- when you find this set (that is: if it is a subset of y)
, IntSet.IntSet -- add this set
)]
)
emptySystem :: EqualitySystem a
emptySystem = ES Map.empty IntMap.empty
isInSystem :: (Ord a) => Op1EqualitySystem a -> a -> Bool
isInSystem (ES1 t _ _) a = Map.member a t
-- | getGroups: create groups of concepts (type variable: concept).
-- 1. Each concept is in precisely one group.
-- 2. Two concepts are in the same group if there is a path of classify-rules between those concepts.
-- The purpose of this is to know whether two concepts are comparable or not. Atoms of concepts within a group can be compared.
-- Atoms of concepts in different groups may never be compared.
getGroups :: (Ord concept, SetLike set) => Op1EqualitySystem concept -> [set concept]
getGroups (ES1 tran _ imap)
= [fromList [a | (a,i) <- Map.toList tran, not . IntSet.null $ IntSet.intersection i r] | r <- IntMap.elems res]
where
iml :: [(Int,[(IntSet.IntSet,IntSet.IntSet)])]
iml = IntMap.toList imap
(_, _, res) = foldr getLists (0, IntMap.empty, IntMap.empty) ([(IntSet.insert a (IntSet.union b c)) | (a,bc) <- iml, (b,c)<-bc] ++ Map.elems tran)
getLists :: IntSet.IntSet -> (Int, IntMap.IntMap Int, IntMap.IntMap (IntSet.IntSet)) -> (Int, IntMap.IntMap Int, IntMap.IntMap (IntSet.IntSet))
getLists im (acc, allElems, rev) -- TODO: this might be made more efficiently by using Array as the last element
= if not (IntMap.null overlap) then
(acc, newElems, newRev)
else (acc+1, IntMap.union (IntMap.fromSet (const acc) im) allElems, IntMap.insert acc im rev)
where
overlap = IntMap.intersection allElems (IntMap.fromSet id im) -- overlap between im and the previously treated elements
oldKeys = IntMap.elems overlap -- sets to which the overlapping items belong
newKey = head oldKeys -- get any key name
oldKeySet = IntSet.fromList oldKeys -- remove duplicates, provide efficient lookup
-- newRev' is all items that will remain the same
-- newItm' is all (old) items that must be renamed
~(newItm', newRev') = IntMap.partitionWithKey (\k _ -> IntSet.member k oldKeySet) rev
newItm :: IntSet.IntSet
newItm = IntSet.unions (im : IntMap.elems newItm') -- all
newRev = IntMap.insert newKey newItm newRev'
newElems = IntMap.union (IntMap.fromSet (const newKey) newItm) allElems -- overwrites some of the allElems items with the new key
findExact :: (Ord a, SetLike x) => Op1EqualitySystem a -> FreeLattice a -> x a -- returns the empty set on a failure
findExact = findWith lookupInRevMap (\x -> fromList [x])
findSubsets :: (Ord a, SetLike x) => Op1EqualitySystem a -> FreeLattice a -> [x a] -- returns a list of largest subsets
findSubsets = findWith findSubsetInRevMap (\x -> [fromList [x]])
findWith :: Ord a
=> ([Int] -> RevMap a -> b) -- Function that finds the normalized form
-> (a -> b) -- Shorthand in case the FreeLattice does not need to go through the translation process
-> Op1EqualitySystem a -- system in which the FreeLattice elements can be found
-> FreeLattice a -- the FreeLattice that needs translation
-> b
findWith f f2 es@(ES1 _ back _) trmUnsimplified
= case trm of
Atom x -> f2 x
_ -> f (IntSet.toList (case trm' of
Just t -> intersections (map it t)
Nothing -> IntSet.empty
)
) back
where it = simplifySet es
intersections [] = IntSet.empty
intersections x = foldr1 IntSet.intersection x
trm' = latticeToTranslatable es trm
trm = simpl trmUnsimplified
simpl (Meet a b)
= case (simpl a, simpl b) of
(Atom a', Atom b') | a'==b' -> Atom a'
(a',b') -> Meet a' b'
simpl (Join a b)
= case (simpl a, simpl b) of
(Atom a', Atom b') | a'==b' -> Atom a'
(a',b') -> Join a' b'
simpl (Atom x) = Atom x
simplifySet :: Op1EqualitySystem t -> IntSet.IntSet -> IntSet.IntSet
simplifySet (ES1 _ _ imap) x = imapTranslate imap x IntSet.empty
latticeToTranslatable :: Ord a => Op1EqualitySystem a -> FreeLattice a -> Maybe [IntSet.IntSet]
latticeToTranslatable (ES1 m _ _) lt = t lt
where
t (Atom a) = do{r<-Map.lookup a m;return [r]}
t (Meet a b) = do{a'<-t a;b'<- t b;return [IntSet.union ta tb | ta <- a', tb <- b']}
t (Join a b) = do{a'<-t a;b'<- t b;return (a'++b')}
-- how to lookup something in a RevMap (Precondition: list is sorted!)
lookupInRevMap :: (Ord a, SetLike x) => [Int] -> RevMap a -> x a
lookupInRevMap [] (RevMap st _) = fromSet st
lookupInRevMap (a:as) (RevMap _ mp)
= case IntMap.lookup a mp of
Nothing -> slEmpty
Just rm -> lookupInRevMap as rm
-- a bit slower: suppose we could not find our element in the RevMap, we find all subsets of it (as a RevMap)
findSubsetAsRevMap :: (Ord a) => [Int] -> RevMap a -> RevMap a
findSubsetAsRevMap [] (RevMap st _) = RevMap st IntMap.empty
findSubsetAsRevMap lst (RevMap st mp)
= RevMap st (IntMap.fromList [ (l, rm)
| (l, rst) <- listAndRest lst
, (Just mp') <- [IntMap.lookup l mp]
, let rm@(RevMap st2 mp2) = findSubsetAsRevMap rst mp'
, not (Set.null st2 && IntMap.null mp2)
] )
-- fins the largest subsets! (endpoints only)
findSubsetInRevMap :: (Ord a, SetLike x) => [Int] -> RevMap a -> [x a]
findSubsetInRevMap lst rm = largestSubset (findSubsetAsRevMap lst rm)
largestSubset :: (Ord a, SetLike x) => RevMap a -> [x a]
largestSubset i
= elimSubsets (endPoints i)
where elimSubsets ((a,v):as) = v : elimSubsets (filter (\x -> not (IntSet.isSubsetOf (fst x) a)) as)
elimSubsets [] = []
endPoints :: (Ord a, SetLike x) => RevMap a -> [(IntSet.IntSet,x a)]
endPoints (RevMap st im)
= if (IntMap.null im) then (if slNull st then [] else [(IntSet.empty,fromSet st)]) else concat (map endPoints' (IntMap.toList im))
where endPoints' (i,rm) = map addi (endPoints rm)
where addi (lst,elm) = (IntSet.insert i lst,elm)
listAndRest :: [t] -> [(t, [t])]
listAndRest [] = []
listAndRest (a:rst) = (a,rst):listAndRest rst
data RevMap a
= RevMap (Set.Set a) -- all elements equivalent to this point in the map
(IntMap.IntMap (RevMap a)) -- recursive
deriving Show
data Op1EqualitySystem a
= ES1 (Map.Map a (IntSet.IntSet))
(RevMap a)
(IntMap.IntMap -- map for: whenever you encounter this element i in your set y
[( IntSet.IntSet -- when you find this set (that is: if it is a subset of y)
, IntSet.IntSet -- add this set
)]
)
-- TODO: this function can be optimised a lot
reverseMap :: (Ord a) => [(a,[Int])] -> RevMap a
reverseMap lst
= RevMap (Set.fromList (map fst empties)) (buildMap rest)
where
(empties,rest) = partition (null . snd) lst
buildMap [] = IntMap.empty
buildMap o@((_,~(f:_)):_)
= IntMap.insert f (reverseMap (map tail2 h)) (buildMap tl)
where tail2 (a,b) = (a, tail b)
(h,tl) = partition ((== f) . head . snd) o
optimize1 :: Ord a => EqualitySystem a -> Op1EqualitySystem a
optimize1 (ES oldmap oldimap)
= ES1 newmap
(reverseMap (Map.toList (Map.map IntSet.toList newmap)))
(IntMap.mapMaybe maybeMapper oldimap)
where notEmpty [] = Nothing
notEmpty a = Just a
maybeMapper :: [(IntSet.IntSet,IntSet.IntSet)] -> Maybe [(IntSet.IntSet,IntSet.IntSet)]
maybeMapper x = notEmpty [ (s1,imapTranslate oldimap s2 (IntSet.empty))
| (s1,s2) <- x
, not (IntSet.null s1)
, not (IntSet.null s2)
]
newmap = Map.map (\x -> imapTranslate oldimap (IntSet.singleton x) (IntSet.empty)) oldmap
addEquality :: (Ord a, SetLike x) => (x a, x a) -> EqualitySystem a -> EqualitySystem a
addEquality (set1, set2) eqSys0
= addEquality' eqSys2 ns1 ns2
where
(eqSys1, ns1) = translateWith eqSys0 set1
(eqSys2, ns2) = translateWith eqSys1 set2
addEquality' :: Ord a => EqualitySystem a -> IntSet.IntSet -> IntSet.IntSet -> EqualitySystem a
addEquality' ~(ES nms imap) set1 set2
= ES nms (addRule (addRule imap set1 set1 uni) set2 (IntSet.difference set2 set1) uni)
where
uni = IntSet.union set1 set2
addRule :: IntMap.IntMap [(IntSet.IntSet, IntSet.IntSet)] -> IntSet.IntSet -> IntSet.IntSet -> IntSet.IntSet -> IntMap.IntMap [(IntSet.IntSet, IntSet.IntSet)]
addRule oldimap origSet triggers newSet
= foldl updateMapForTrigger oldimap (IntSet.toList triggers)
where dif = IntSet.difference newSet origSet
updateMapForTrigger :: IntMap.IntMap [(IntSet.IntSet, IntSet.IntSet)] -> Int -> IntMap.IntMap [(IntSet.IntSet, IntSet.IntSet)]
updateMapForTrigger mp trigger
= IntMap.insertWith (++) trigger [(IntSet.delete trigger origSet, dif)] mp
translateWith :: (Ord a, SetLike x) => EqualitySystem a -> x a -> (EqualitySystem a, IntSet.IntSet)
translateWith ~(ES nomenclature imap) inSet
= ( ES newNomenclature imap
, IntSet.fromList$ map (newNomenclature Map.!) (getList inSet)
)
where
newNomenclature
= foldr (\x y -> if Map.member x y then y else Map.insert x (Map.size y) y) nomenclature (getList inSet)
imapTranslate :: IntMap.IntMap [(IntSet.IntSet, IntSet.IntSet)] -> IntSet.IntSet -> IntSet.IntSet -> IntSet.IntSet
imapTranslate imap tds doneSet
= case IntSet.minView tds of
Nothing -> doneSet
Just (todo,set) -> imapTranslate imap (newSet todo set) (IntSet.insert todo doneSet)
where
newSet todo set
= case IntMap.lookup todo imap of
Nothing -> set
Just lst -> IntSet.unions (set:[IntSet.difference tl doneSet | (fl,tl) <- lst, IntSet.isSubsetOf fl doneSet])
data FreeLattice a
= Join (FreeLattice a) (FreeLattice a)
| Meet (FreeLattice a) (FreeLattice a)
| Atom a
instance SetLike [] where
fromList = id
fromSet = Set.toList
toSet = Set.fromList
getList = id
slUnion a b = mrgUnion a b
slIsect a b = mrgIsect a b
slFold = foldl
slNull = null
slSize = length
instance SetLike Set.Set where
slIsect = Set.intersection
slUnion = Set.union
slEmpty = Set.empty
slUnions = Set.unions
slMap = Set.map
getList = Set.toList
fromList = Set.fromList
fromSet = id
isElemOf = Set.member
slFold f = Set.fold (flip f)
slNull = Set.null
slSize = Set.size
slInsert = Set.insert
toSet = id
class SetLike x where -- I dislike having to put Ord everywhere, is there another way? (Without including a in the class)
slIsect :: Ord a => x a -> x a -> x a
slUnion :: Ord a => x a -> x a -> x a
getList :: Ord a => x a -> [a]
fromList :: Ord a => [a] -> x a
fromSet :: Ord a => Set.Set a -> x a
slMap :: (Ord a,Ord b) => (a -> b) -> x a -> x b
slMap f = fromList . nub' . sort . (map f) . getList
slEmpty :: Ord a => x a
slEmpty = fromList []
slUnions :: Ord a => [x a] -> x a
slUnions = foldr slUnion slEmpty
isElemOf :: Ord a => a -> x a -> Bool
isElemOf e mp = (e `elem` getList mp)
slFold :: Ord b => (a -> b -> a) -> a -> x b -> a
slFold f u xs = foldl f u (getList xs)
slSize :: Ord a => x a -> Int
slSize = length . getList
slNull :: Ord a => x a -> Bool
slNull = null . getList
slInsert :: Ord a => a -> x a -> x a
slInsert x = slUnion (fromList [x])
toSet :: Ord a => x a -> Set.Set a
nub' :: Eq a => [a] -> [a]
nub' (a:b:bs) | a == b = nub' (b:bs)
| otherwise = a:nub' (b:bs)
nub' rst = rst
mrgUnion :: (Ord a) => [a] -> [a] -> [a]
mrgUnion (a:as) (b:bs) | a<b = a:mrgUnion as (b:bs)
| a==b = a:mrgUnion as bs
| otherwise = b:mrgUnion (a:as) bs
mrgUnion a b = a ++ b -- since either a or b is the empty list
{-# SPECIALIZE mrgUnion :: [Int] -> [Int] -> [Int] #-}
mrgIsect :: (Ord a) => [a] -> [a] -> [a]
mrgIsect (a:as) (b:bs) | a<b = mrgIsect as (b:bs)
| a==b = b: mrgIsect as bs
| otherwise = mrgIsect (a:as) bs
mrgIsect _ _ = [] -- since either a or b is the empty list
{-# SPECIALIZE mrgIsect :: [Int] -> [Int] -> [Int] #-}