mirror of
https://git.cs.ou.nl/joshua.moerman/mealy-decompose.git
synced 2025-04-30 02:07:44 +02:00
Basic LStar in Haskell
This commit is contained in:
parent
f7b3d27478
commit
7e3019a47a
2 changed files with 210 additions and 0 deletions
|
@ -27,6 +27,7 @@ library
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
DotParser,
|
DotParser,
|
||||||
DotWriter,
|
DotWriter,
|
||||||
|
LStar,
|
||||||
Mealy,
|
Mealy,
|
||||||
MealyRefine,
|
MealyRefine,
|
||||||
Merger,
|
Merger,
|
||||||
|
|
209
src/LStar.hs
Normal file
209
src/LStar.hs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
module LStar where
|
||||||
|
|
||||||
|
-- LStar voor Mealy machines. Input zijn woorden van type i, en output zijn
|
||||||
|
-- elementen van type o. (We gebruiken alleen de laatste output.)
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.State.Strict
|
||||||
|
|
||||||
|
import Data.Foldable (minimumBy)
|
||||||
|
import Data.Function (on)
|
||||||
|
import Data.Functor.Identity
|
||||||
|
import Data.List (tails)
|
||||||
|
import Data.Map.Strict qualified as Map
|
||||||
|
import Data.Map.Merge.Strict qualified as MapMerge
|
||||||
|
import Data.Set qualified as Set
|
||||||
|
import Prelude hiding (Word)
|
||||||
|
|
||||||
|
-----------------------------------
|
||||||
|
-- Datastructuren en basis functies
|
||||||
|
|
||||||
|
-- Een woord is niets anders dan een lijst.
|
||||||
|
-- TODO: Een keertje uitvogelen of Data.Sequence efficienter is of niet.
|
||||||
|
type Word i = [i]
|
||||||
|
|
||||||
|
-- Membership queries. Voor Mealy machines levert dat een o op. De parameter
|
||||||
|
-- f is voor een monad. Dat is nodig als de membership queries echt moeten
|
||||||
|
-- praten met een SUL, of als je bijv. een tellertje wilt bijhouden.
|
||||||
|
type MQ f i o = Word i -> f o
|
||||||
|
|
||||||
|
-- Invariant: dom(content) = `rows × columns` ∪ `rows × alph × columns`
|
||||||
|
-- TODO: misschien hoeven we geen Set datastructuur te gebruiken, maar
|
||||||
|
-- volstaan lijsten. Het zou ook met een Trie kunnen.
|
||||||
|
data LStarState i o = LStarState
|
||||||
|
{ content :: Map.Map (Word i) o
|
||||||
|
, rowIndices :: Set.Set (Word i)
|
||||||
|
, colIndices :: Set.Set (Word i)
|
||||||
|
, alphabet :: [i]
|
||||||
|
}
|
||||||
|
deriving (Eq, Ord, Show, Read)
|
||||||
|
|
||||||
|
-- We maken onderscheid in "rij-index" en "rij". De index is het woord wat bij
|
||||||
|
-- de rij hoort. De rij zelf is gegeven door de inhoud van de rij, dus een
|
||||||
|
-- functie van kolom naar o. Dit doen we met een Map data structuur.
|
||||||
|
type Row i o = Map.Map (Word i) o
|
||||||
|
|
||||||
|
-- Kent aan een rij-index de rij (content) toe.
|
||||||
|
row :: Ord i => LStarState i o -> Word i -> Row i o
|
||||||
|
row LStarState{..} prefix = Map.fromSet (\suffix -> content Map.! (prefix <> suffix)) colIndices
|
||||||
|
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
-- LStar functies
|
||||||
|
|
||||||
|
-- Geeft alle rijen waarvoor de tabel nog niet gesloten is.
|
||||||
|
-- TODO: toekenning van "lage rij" -> "hoge rij" ook uitrekenen, zodat we
|
||||||
|
-- later meteen een automaat kunnen bouwen.
|
||||||
|
closednessDefects :: (Ord i, Ord o) => LStarState i o -> [Word i]
|
||||||
|
closednessDefects table@LStarState{..} = defects
|
||||||
|
where
|
||||||
|
alphabetSingletons = Set.fromList $ map pure alphabet
|
||||||
|
allExtendedRowIndices = Set.map (uncurry (<>)) $ Set.cartesianProduct rowIndices alphabetSingletons
|
||||||
|
extendedRowIndices = allExtendedRowIndices `Set.difference` rowIndices
|
||||||
|
upperRows = Set.map (row table) rowIndices
|
||||||
|
defects = [r | r <- Set.toList extendedRowIndices, row table r `Set.notMember` upperRows]
|
||||||
|
|
||||||
|
-- Geeft alle paren van symbool en kolom (a, s) die toegevoegd moeten worden
|
||||||
|
-- om de tabel consistent te maken. Merk op: het paar (a, s) zou vaker voor
|
||||||
|
-- kunnen komen als er meerdere equivalente rijen zijn.
|
||||||
|
inconsistencies :: (Ord i, Eq o) => LStarState i o -> [(i, Word i)]
|
||||||
|
inconsistencies table@LStarState{..} = defects
|
||||||
|
where
|
||||||
|
equivalentPairs = [(r1, r2) | r1 <- Set.toList rowIndices, r2 <- Set.toList rowIndices, r1 < r2, row table r1 == row table r2]
|
||||||
|
defects = [(a, s) | (r1, r2) <- equivalentPairs, a <- alphabet, let d = differenceOfRows r1 r2 a, s <- Map.keys d]
|
||||||
|
differenceOfRows r1 r2 a = differenceOfMaps (row table (r1 <> [a])) (row table (r2 ++ [a]))
|
||||||
|
differenceOfMaps m1 m2 = MapMerge.merge MapMerge.dropMissing MapMerge.dropMissing (MapMerge.zipWithMaybeMatched (\_ x y -> if x == y then Nothing else Just ())) m1 m2
|
||||||
|
|
||||||
|
-- Preconditie: tabel is gesloten en consistent
|
||||||
|
-- TODO: misschien checken of de automaat uniek is? De constructie van
|
||||||
|
-- transitions kiest de eerst-mogelijke successor-state, en misschien zijn
|
||||||
|
-- er meer.
|
||||||
|
createHypothesis :: (Ord i, Ord o) => LStarState i o -> (Int, Int, Map.Map (Int, i) Int, Map.Map (Int, i) o)
|
||||||
|
createHypothesis table@LStarState{..} = (initialState, Map.size row2IntMap, transitions, outputs)
|
||||||
|
where
|
||||||
|
rowIndicesLs = Set.toList rowIndices
|
||||||
|
upperRows = map (row table) $ rowIndicesLs
|
||||||
|
row2IntMap = Map.fromList $ zip upperRows [0..]
|
||||||
|
row2Int = (Map.!) row2IntMap . row table
|
||||||
|
transitions = Map.fromList [((row2Int r, a), row2Int (r <> [a])) | r <- rowIndicesLs, a <- alphabet]
|
||||||
|
outputs = Map.fromList [((row2Int r, a), o) | r <- rowIndicesLs, a <- alphabet, let o = content Map.! (r <> [a])]
|
||||||
|
initialState = row2Int []
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------
|
||||||
|
-- Initialisatie en observaties toevoegen
|
||||||
|
|
||||||
|
-- Een lege tabel heeft altijd een "epsilon-rij" en voor elk symbool een kolom.
|
||||||
|
-- (Omdat het Mealy machines zijn.)
|
||||||
|
initialiseA :: (Applicative f, Ord i) => [i] -> MQ f i o -> f (LStarState i o)
|
||||||
|
initialiseA alphabet mq = initialiseWithA alphabet [[]] (map pure alphabet) mq
|
||||||
|
|
||||||
|
-- We kunnen ook een tabel maken met voorgedefinieerde rijen en kolommen.
|
||||||
|
initialiseWithA :: (Applicative f, Ord i) => [i] -> [Word i] -> [Word i] -> MQ f i o -> f (LStarState i o)
|
||||||
|
initialiseWithA alphabet rowIdcs colIdcs mq = (\content -> LStarState{..}) <$> contentA
|
||||||
|
where
|
||||||
|
rowIndices = Set.fromList rowIdcs
|
||||||
|
rowIdcsExt = rowIndices <> Set.fromList [r <> [a] | r <- rowIdcs, a <- alphabet]
|
||||||
|
colIndices = Set.fromList colIdcs
|
||||||
|
domain = Map.fromSet (const ()) . Set.map (uncurry (<>)) $ Set.cartesianProduct rowIdcsExt colIndices
|
||||||
|
contentA = Map.traverseWithKey (\w _ -> mq w) domain
|
||||||
|
|
||||||
|
-- preconditie: newRowIndices is disjunct van de huidige rowIndices en de
|
||||||
|
-- vereniging is prefix-gesloten. (Wordt niet gechecked.)
|
||||||
|
addRowsA :: (Applicative f, Ord i) => [Word i] -> MQ f i o -> LStarState i o -> f (LStarState i o)
|
||||||
|
addRowsA newRowIndices mq table@LStarState{..} = (\newContent -> table
|
||||||
|
{ content = content <> newContent
|
||||||
|
, rowIndices = rowIndices <> newRowIndicesSet }) <$> contentA
|
||||||
|
where
|
||||||
|
newRowIndicesExt = Set.fromList [r <> [a] | r <- newRowIndices, a <- alphabet]
|
||||||
|
newRowIndicesSet = Set.fromList newRowIndices
|
||||||
|
newRowIndices2 = (newRowIndicesSet <> newRowIndicesExt) `Set.difference` rowIndices
|
||||||
|
domain = Map.fromSet (const ()) . Set.map (uncurry (<>)) $ Set.cartesianProduct newRowIndices2 colIndices
|
||||||
|
contentA = Map.traverseWithKey (\w _ -> mq w) domain
|
||||||
|
|
||||||
|
-- preconditie: zie addRows (?)
|
||||||
|
addColumnsA :: (Applicative f, Ord i) => [Word i] -> MQ f i o -> LStarState i o -> f (LStarState i o)
|
||||||
|
addColumnsA newColIndices mq table@LStarState{..} = (\newContent -> table
|
||||||
|
{ content = content <> newContent
|
||||||
|
, colIndices = colIndices <> newColIndicesSet }) <$> contentA
|
||||||
|
where
|
||||||
|
newColIndicesExt = Set.fromList [[a] <> c | c <- newColIndices, a <- alphabet]
|
||||||
|
newColIndicesSet = Set.fromList newColIndices
|
||||||
|
newColIndices2 = (newColIndicesSet <> newColIndicesExt) `Set.difference` colIndices
|
||||||
|
domain = Map.fromSet (const ()) . Set.map (uncurry (<>)) $ Set.cartesianProduct rowIndices newColIndices2
|
||||||
|
contentA = Map.traverseWithKey (\w _ -> mq w) domain
|
||||||
|
|
||||||
|
|
||||||
|
------------------
|
||||||
|
-- LStar algoritme
|
||||||
|
|
||||||
|
-- geeft True als de tabel is gewijzigd.
|
||||||
|
closeA :: (Monad f, Ord i, Ord o) => MQ f i o -> LStarState i o -> f (LStarState i o, Bool)
|
||||||
|
closeA mq = loop False where
|
||||||
|
loop acc table = case closednessDefects table of
|
||||||
|
[] -> pure (table, acc)
|
||||||
|
w:_ -> addRowsA [w] mq table >>= loop True
|
||||||
|
|
||||||
|
-- geeft True als de tabel is gewijzigd.
|
||||||
|
makeConsistentA :: (Monad f, Ord i, Eq o) => MQ f i o -> LStarState i o -> f (LStarState i o, Bool)
|
||||||
|
makeConsistentA mq = loop False where
|
||||||
|
loop acc table = case inconsistencies table of
|
||||||
|
[] -> pure (table, acc)
|
||||||
|
(a, w):_ -> addColumnsA [a:w] mq table >>= loop True
|
||||||
|
|
||||||
|
makeClosedAndConsistentA :: (Monad f, Ord i, Ord o) => MQ f i o -> LStarState i o -> f (LStarState i o, Bool)
|
||||||
|
makeClosedAndConsistentA mq = loop False where
|
||||||
|
loop acc table1 = do
|
||||||
|
(table2, b2) <- closeA mq table1
|
||||||
|
(table3, b3) <- makeConsistentA mq table2
|
||||||
|
if b3
|
||||||
|
then loop True table3
|
||||||
|
else pure (table3, acc || b2)
|
||||||
|
|
||||||
|
-- We voegen tegenvoorbeelden toe als kolommen. Dat is vaak het meest
|
||||||
|
-- efficient. We houden de kolommen wel suffix-gesloten, dat is iets
|
||||||
|
-- eenvoudiger.
|
||||||
|
processCounterexampleA :: (Applicative f, Ord i) => Word i -> MQ f i o -> LStarState i o -> f (LStarState i o)
|
||||||
|
processCounterexampleA ce mq table@LStarState{..} = addColumnsA newSuffixes mq table where
|
||||||
|
removePrefix [] w2 = Just w2
|
||||||
|
removePrefix _ [] = Nothing
|
||||||
|
removePrefix (a:w1) (b:w2)
|
||||||
|
| a == b = removePrefix w1 w2
|
||||||
|
| otherwise = Nothing
|
||||||
|
shortestSuffix = minimumBy (compare `on` length) [suf | r <- Set.toList rowIndices, Just suf <- [removePrefix r ce]]
|
||||||
|
newSuffixes = filter (not . null) . tails . drop 1 $ shortestSuffix
|
||||||
|
|
||||||
|
|
||||||
|
-------------
|
||||||
|
-- MQ wrapper
|
||||||
|
|
||||||
|
-- Bijhouden hoeveel membership queries er zijn gesteld
|
||||||
|
countingMQ :: Monad f => MQ f i o -> MQ (StateT Int f) i o
|
||||||
|
countingMQ mq w = modify' succ >> lift (mq w)
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
-- Minder generieke API
|
||||||
|
|
||||||
|
-- Als je geen monad nodig hebt, kun je een simpele membership query omzetten
|
||||||
|
-- naar het MQ type.
|
||||||
|
simpleMQ :: (Word i -> o) -> MQ Identity i o
|
||||||
|
simpleMQ = (.) Identity
|
||||||
|
|
||||||
|
initialise :: Ord i => [i] -> (Word i -> o) -> LStarState i o
|
||||||
|
initialise alphabet = runIdentity . initialiseA alphabet . simpleMQ
|
||||||
|
|
||||||
|
initialiseWith :: Ord i => [i] -> [Word i] -> [Word i] -> (Word i -> o) -> LStarState i o
|
||||||
|
initialiseWith alphabet rowIdcs colIdcs = runIdentity . initialiseWithA alphabet rowIdcs colIdcs . simpleMQ
|
||||||
|
|
||||||
|
addRows, addColumns :: Ord i => [Word i] -> (Word i -> o) -> LStarState i o -> LStarState i o
|
||||||
|
addRows newRowIndices mq = runIdentity . addRowsA newRowIndices (simpleMQ mq)
|
||||||
|
addColumns newColIndices mq = runIdentity . addColumnsA newColIndices (simpleMQ mq)
|
||||||
|
|
||||||
|
close, makeConsistent, makeClosedAndConsistent :: (Ord i, Ord o) => (Word i -> o) -> LStarState i o -> (LStarState i o, Bool)
|
||||||
|
close mq = runIdentity . closeA (simpleMQ mq)
|
||||||
|
makeConsistent mq = runIdentity . makeConsistentA (simpleMQ mq)
|
||||||
|
makeClosedAndConsistent mq = runIdentity . makeClosedAndConsistentA (simpleMQ mq)
|
||||||
|
|
||||||
|
processCounterexample :: Ord i => Word i -> (Word i -> o) -> LStarState i o -> LStarState i o
|
||||||
|
processCounterexample ce mq = runIdentity . processCounterexampleA ce (simpleMQ mq)
|
Loading…
Add table
Reference in a new issue