haal-0.3.0.0: src/Haal/BlackBox.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- | This module defines the BlackBox type class as well as the Automaton and SUL
sub classes.
-}
module Haal.BlackBox (
Automaton (..),
SUL (..),
StateID,
Finite,
FiniteEq,
FiniteOrd,
inputs,
outputs,
walk,
stepPure,
walkPure,
resetPure,
initial,
distinguish,
accessSequences,
localCharacterizingSet,
globalCharacterizingSet,
reachable,
)
where
import Control.Monad.Identity (Identity, runIdentity)
import qualified Data.Bifunctor as Bif
import qualified Data.List as List
import qualified Data.Map as Map
import qualified Data.Set as Set
{- | The 'StateID' type is an alias for an integer that represents the state of the automaton.
- It is used as a default type for the state of learned automata.
-}
type StateID = Int
{- | The 'SUL' type class defines the basic interface for a black box automaton.
It provides methods to step through the automaton and retrieve the current state.
It also requires a monad m, that may be 'Identity' in case of a pure SUL, or 'IO'
in case of an external program that performs IO.
-}
class (Monad m) => SUL sul m where
step :: sul i o -> i -> m (sul i o, o)
reset :: sul i o -> m (sul i o)
-- | Finite is an alias for (Enum, Bounded).
type Finite i = (Enum i, Bounded i)
-- | FiniteEq is an alias for (Eq, Finite).
type FiniteEq i = (Eq i, Finite i)
-- | FiniteOrd is an alias for (Ord, Bounded).
type FiniteOrd i = (Ord i, Finite i)
-- | Generalization of 'step' that operates on a list of inputs.
walk :: (SUL sul m) => sul i o -> [i] -> m (sul i o, [o])
walk sul [] = pure (sul, [])
walk sul (x : xs) = do
(sul', o) <- step sul x
(sul'', os) <- walk sul' xs
pure (sul'', o : os)
-- | Return a Set containing only the valid inputs of the SUL.
inputs :: (FiniteOrd i) => sul i o -> Set.Set i
inputs _ = Set.fromList [minBound .. maxBound]
-- | Return a Set containing only the valid outputs of the SUL.
outputs :: (FiniteOrd o) => sul i o -> Set.Set o
outputs _ = Set.fromList [minBound .. maxBound]
{- | The 'Automaton' type class extends the 'SUL' type class and adds
support for automata operations. Automatons are models, not programs,
so they are pure and operate in the Identity monad.
-}
class (SUL (aut s) Identity) => Automaton aut s where
transitions ::
(FiniteOrd i, FiniteOrd s) =>
aut s i o ->
Map.Map (s, i) (s, o)
states :: (FiniteOrd s) => aut s i o -> Set.Set s
current :: aut s i o -> s
update :: aut s i o -> s -> aut s i o
-- | Pure instance of 'step'.
stepPure :: (SUL sul Identity) => sul i o -> i -> (sul i o, o)
stepPure sul i = runIdentity (step sul i)
-- | Pure instance of 'walk'.
walkPure :: (SUL sul Identity) => sul i o -> [i] -> (sul i o, [o])
walkPure sul i = runIdentity (walk sul i)
-- | Pure instance of 'reset'.
resetPure :: (SUL sul Identity) => sul i o -> sul i o
resetPure sul = runIdentity (reset sul)
-- | Return the initial state of an automaton.
initial :: (Automaton aut s) => aut s i o -> s
initial = current . resetPure
-- | Return the set of reachable states of an automaton.
reachable :: forall s i o aut. (Automaton aut s, Ord s, FiniteOrd i) => aut s i o -> Set.Set s
reachable aut = bfs [initial aut] $ Set.singleton (initial aut)
where
alphabet = inputs aut
bfs :: [s] -> Set.Set s -> Set.Set s
bfs [] visited = visited
bfs (st : queue) visited = bfs queue' visited'
where
aut' = update aut st
neighbours = Set.map (current . fst . stepPure aut') alphabet
visited' = visited `Set.union` neighbours
queue' = Set.toList (neighbours `Set.difference` visited) ++ queue
-- | Returns a map containing the shortest sequence to access each reachable state from the initial state.
accessSequences ::
forall s i o aut.
(Automaton aut s, FiniteOrd i, Ord s) =>
aut s i o ->
Map.Map s [i]
accessSequences aut = bfs [(initialSt, [])] (Set.singleton initialSt) (Map.singleton initialSt [])
where
alphabet = Set.toList (inputs aut)
initialSt = initial aut
bfs :: [(s, [i])] -> Set.Set s -> Map.Map s [i] -> Map.Map s [i]
bfs [] _ acc = Map.map List.reverse acc
bfs ((_, prefix) : rest) visited acc =
bfs (rest ++ newQueue) newVisited newMap
where
mo = fst $ walkPure (resetPure aut) (reverse prefix)
successors =
[ (nextState, input : prefix)
| input <- alphabet
, let nextState = current . fst $ stepPure mo input
, nextState `Set.notMember` visited
]
newMap = foldr (uncurry Map.insert) acc successors
newVisited = foldr (Set.insert . fst) visited successors
newQueue = successors
{- | Returns an input sequence that distinguishes the given states in
the given automaton.
-}
distinguish ::
( Automaton aut s
, FiniteOrd i
, Ord s
, Eq o
) =>
aut s i o ->
s ->
s ->
[i]
distinguish m s1 s2 = explore Map.empty [(s1, s2, [])]
where
alphabet = Set.toList (inputs m)
explore _ [] = []
explore visited ((q1, q2, prefix) : queue)
| Just symbol <- discrepancy = reverse (symbol : prefix)
| otherwise = explore newVisited (queue ++ newQueue)
where
newVisited = Map.insert (q1, q2) prefix visited
mo1 = update m q1
mo2 = update m q2
(nextStates1, outputs1) = unzip $ map (stepAndCurrent mo1) alphabet
(nextStates2, outputs2) = unzip $ map (stepAndCurrent mo2) alphabet
discrepancy = snd <$> List.find fst (zip (zipWith (/=) outputs1 outputs2) alphabet)
appended = map (: prefix) alphabet
toBeVisited = Map.fromList $ zip (zip nextStates1 nextStates2) appended
newQueue = [(s1', s2', p) | ((s1', s2'), p) <- Map.toList toBeVisited, (s1', s2') `Map.notMember` visited]
stepAndCurrent mo i = Bif.first current (stepPure mo i)
{- | Returns a set of lists of inputs that can be used to distinguish between the given state and
- any other state of the automaton.
-}
localCharacterizingSet ::
( Automaton aut s
, FiniteOrd i
, FiniteOrd s
, Eq o
) =>
aut s i o ->
s ->
Set.Set [i]
localCharacterizingSet m s = Set.fromList [d s sx | sx <- Set.toList $ states m, s /= sx]
where
d = distinguish m
{- | Returns a set of lists of inputs that can be used to distinguish between any two different states
of the automaton.
-}
globalCharacterizingSet ::
( Automaton aut s
, FiniteOrd i
, FiniteOrd s
, Eq o
) =>
aut s i o ->
Set.Set [i]
globalCharacterizingSet m = Set.fromList [d s1 s2 | s1 <- sts, s2 <- sts, s1 < s2]
where
sts = Set.toList $ states m
d = distinguish m