mirror of
https://github.com/Jaxan/nominal-lstar.git
synced 2025-04-27 14:47:45 +02:00
Splits the Teacher.hs into more files
This commit is contained in:
parent
e475c5b471
commit
3cc387a068
4 changed files with 203 additions and 229 deletions
246
src/Teacher.hs
246
src/Teacher.hs
|
@ -1,54 +1,21 @@
|
||||||
{-# LANGUAGE RankNTypes #-}
|
{-# LANGUAGE RankNTypes #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
|
||||||
module Teacher where
|
module Teacher
|
||||||
|
( module Teachers.Teacher
|
||||||
|
, teacherWithTarget
|
||||||
|
, teacherWithTargetNonDet
|
||||||
|
, teacherWithIO
|
||||||
|
, teacherWithTargetAndIO
|
||||||
|
) where
|
||||||
|
|
||||||
import NLambda hiding (alphabet, when)
|
import Teachers.Teacher
|
||||||
|
import Teachers.Whitebox
|
||||||
|
import Teachers.Terminal
|
||||||
|
|
||||||
|
import NLambda hiding (alphabet)
|
||||||
import qualified NLambda (alphabet)
|
import qualified NLambda (alphabet)
|
||||||
|
|
||||||
import Debug.Trace
|
|
||||||
-- Explicit Prelude, as NLambda has quite some clashes
|
|
||||||
import Data.Function (fix)
|
|
||||||
import Data.List (zip, (!!), reverse)
|
|
||||||
import Data.Maybe (Maybe (..))
|
|
||||||
import Prelude (Bool (..), Int, Read, Show, error,
|
|
||||||
length, return, ($), (++), (-), (<),
|
|
||||||
(==), (.), (<=), (+), show, seq, (<$>))
|
|
||||||
import qualified Prelude
|
|
||||||
import Control.Monad.Identity (Identity(..))
|
|
||||||
import Control.Monad (when, forM)
|
|
||||||
import Data.IORef (IORef, readIORef, newIORef, writeIORef)
|
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
|
||||||
|
|
||||||
-- Used in the IO teacher
|
|
||||||
import System.Console.Haskeline
|
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
|
||||||
import Text.Read (readMaybe)
|
|
||||||
|
|
||||||
-- Abstract teacher type (inside the NLambda library, ideally one would like
|
|
||||||
-- an external interface, with Bool as output instead of Formula for instance)
|
|
||||||
-- For now we do actually implement an external teacher, but it is rather hacky.
|
|
||||||
data Teacher i = Teacher
|
|
||||||
-- Given a sequence, check whether it is in the language
|
|
||||||
-- Assumed to be equivariant.
|
|
||||||
-- We slightly change the type to support counting the number of orbits.
|
|
||||||
-- All teacher (except the counting one) implement membership :: [i] -> Formula
|
|
||||||
{ membership :: Set [i] -> Set ([i], Formula)
|
|
||||||
-- Given a hypothesis, returns Nothing when equivalence or a (equivariant)
|
|
||||||
-- set of counter examples. Needs to be quantified over q, because the
|
|
||||||
-- learner may choose the type of the state space.
|
|
||||||
, equivalent :: forall q. (Show q, NominalType q) => Automaton q i -> Maybe (Set [i])
|
|
||||||
-- Returns the alphabet to the learner
|
|
||||||
, alphabet :: Set i
|
|
||||||
}
|
|
||||||
|
|
||||||
-- In order to count membership queries, I had to have the set of inputs
|
|
||||||
-- instead of just mere inputs... It's a bit annoying, since it doesn't
|
|
||||||
-- look like Angluin like this... But this is the best I can do in NLambda
|
|
||||||
-- Here is a function to make it element-wise again:
|
|
||||||
foreachQuery :: NominalType i => ([i] -> Formula) -> Set[i] -> Set ([i], Formula)
|
|
||||||
foreachQuery f qs = map (\q -> (q, f q)) qs
|
|
||||||
|
|
||||||
-- We provide three ways to construct teachers:
|
-- We provide three ways to construct teachers:
|
||||||
-- 1. Fully automatic
|
-- 1. Fully automatic
|
||||||
-- 2. Fully interactive (via IO)
|
-- 2. Fully interactive (via IO)
|
||||||
|
@ -60,18 +27,18 @@ foreachQuery f qs = map (\q -> (q, f q)) qs
|
||||||
-- Only works for DFAs for now, as those can be checked for equivalence
|
-- Only works for DFAs for now, as those can be checked for equivalence
|
||||||
teacherWithTarget :: (NominalType i, NominalType q) => Automaton q i -> Teacher i
|
teacherWithTarget :: (NominalType i, NominalType q) => Automaton q i -> Teacher i
|
||||||
teacherWithTarget aut = Teacher
|
teacherWithTarget aut = Teacher
|
||||||
{ membership = foreachQuery $ automaticMembership aut
|
{ membership = foreachQuery $ accepts aut
|
||||||
, equivalent = automaticEquivalent bisim aut
|
, equivalent = automaticEquivalent bisim aut
|
||||||
, alphabet = automaticAlphabet aut
|
, alphabet = NLambda.alphabet aut
|
||||||
}
|
}
|
||||||
|
|
||||||
-- 1b. This is a fully automatic teacher, which has an internal automaton
|
-- 1b. This is a fully automatic teacher, which has an internal automaton
|
||||||
-- NFA have undecidable equivalence, n is a bound on deoth of bisimulation.
|
-- NFA have undecidable equivalence, n is a bound on deoth of bisimulation.
|
||||||
teacherWithTargetNonDet :: (Show i, Show q, NominalType i, NominalType q) => Int -> Automaton q i -> Teacher i
|
teacherWithTargetNonDet :: (Show i, Show q, NominalType i, NominalType q) => Int -> Automaton q i -> Teacher i
|
||||||
teacherWithTargetNonDet n aut = Teacher
|
teacherWithTargetNonDet n aut = Teacher
|
||||||
{ membership = foreachQuery $ automaticMembership aut
|
{ membership = foreachQuery $ accepts aut
|
||||||
, equivalent = automaticEquivalent (bisimNonDet n) aut
|
, equivalent = automaticEquivalent (bisimNonDet n) aut
|
||||||
, alphabet = automaticAlphabet aut
|
, alphabet = NLambda.alphabet aut
|
||||||
}
|
}
|
||||||
|
|
||||||
-- 2. Will ask everything to someone reading the terminal
|
-- 2. Will ask everything to someone reading the terminal
|
||||||
|
@ -91,56 +58,11 @@ teacherWithIO = Teacher
|
||||||
-- used for NFAs when there was no bounded bisimulation yet
|
-- used for NFAs when there was no bounded bisimulation yet
|
||||||
teacherWithTargetAndIO :: NominalType q => Automaton q Atom -> Teacher Atom
|
teacherWithTargetAndIO :: NominalType q => Automaton q Atom -> Teacher Atom
|
||||||
teacherWithTargetAndIO aut = Teacher
|
teacherWithTargetAndIO aut = Teacher
|
||||||
{ membership = foreachQuery $ automaticMembership aut
|
{ membership = foreachQuery $ accepts aut
|
||||||
, equivalent = ioEquivalent
|
, equivalent = ioEquivalent
|
||||||
, alphabet = atoms
|
, alphabet = atoms
|
||||||
}
|
}
|
||||||
|
|
||||||
-- 4. A teacher with state (hacked, since the types don't allow for it)
|
|
||||||
-- Useful for debugging and so on, but *very very hacky*!
|
|
||||||
countingTeacher :: (Show i, Contextual i, NominalType i) => Teacher i -> Teacher i
|
|
||||||
countingTeacher delegate = Teacher
|
|
||||||
{ membership = \qs -> increaseMQ qs `seq` membership delegate qs
|
|
||||||
, equivalent = \a -> increaseEQ a `seq` equivalent delegate a
|
|
||||||
, alphabet = alphabet delegate
|
|
||||||
}
|
|
||||||
where
|
|
||||||
{-# NOINLINE increaseEQ #-}
|
|
||||||
increaseEQ a = unsafePerformIO $ do
|
|
||||||
i <- readIORef eqCounter
|
|
||||||
let j = i + 1
|
|
||||||
writeIORef eqCounter j
|
|
||||||
return a
|
|
||||||
{-# NOINLINE increaseMQ #-}
|
|
||||||
increaseMQ q = unsafePerformIO $ do
|
|
||||||
new <- newOrbitsInCache q
|
|
||||||
l <- readIORef mqCounter
|
|
||||||
let l2 = new : l
|
|
||||||
writeIORef mqCounter l2
|
|
||||||
return q
|
|
||||||
{-# NOINLINE cache #-}
|
|
||||||
cache = unsafePerformIO $ newIORef empty
|
|
||||||
{-# NOINLINE newOrbitsInCache #-}
|
|
||||||
newOrbitsInCache qs = do
|
|
||||||
oldCache <- readIORef cache
|
|
||||||
let newQs = simplify $ qs \\ oldCache
|
|
||||||
writeIORef cache (simplify $ oldCache `union` qs)
|
|
||||||
return $ setOrbitsMaxNumber newQs
|
|
||||||
|
|
||||||
-- HACK: Counts number of equivalence queries
|
|
||||||
eqCounter :: IORef Int
|
|
||||||
{-# NOINLINE eqCounter #-}
|
|
||||||
eqCounter = unsafePerformIO $ newIORef 0
|
|
||||||
|
|
||||||
-- HACK: Keeps track of membership queries with: # orbits per 'query'
|
|
||||||
mqCounter :: IORef [Int]
|
|
||||||
{-# NOINLINE mqCounter #-}
|
|
||||||
mqCounter = unsafePerformIO $ newIORef []
|
|
||||||
|
|
||||||
|
|
||||||
-- Implementations of above functions
|
|
||||||
--
|
|
||||||
automaticMembership aut input = accepts aut input
|
|
||||||
automaticEquivalent bisimlator aut hypo = case solve isEq of
|
automaticEquivalent bisimlator aut hypo = case solve isEq of
|
||||||
Nothing -> error "should be solved"
|
Nothing -> error "should be solved"
|
||||||
Just True -> Nothing
|
Just True -> Nothing
|
||||||
|
@ -148,137 +70,3 @@ automaticEquivalent bisimlator aut hypo = case solve isEq of
|
||||||
where
|
where
|
||||||
bisimRes = bisimlator aut hypo
|
bisimRes = bisimlator aut hypo
|
||||||
isEq = isEmpty bisimRes
|
isEq = isEmpty bisimRes
|
||||||
automaticAlphabet aut = NLambda.alphabet aut
|
|
||||||
|
|
||||||
|
|
||||||
instance Conditional a => Conditional (Identity a) where
|
|
||||||
cond f x y = return (cond f (runIdentity x) (runIdentity y))
|
|
||||||
|
|
||||||
-- Checks bisimulation of initial states (only for DFAs)
|
|
||||||
-- returns some counter examples if not bisimilar
|
|
||||||
-- returns empty set iff bisimilar
|
|
||||||
bisim :: (NominalType i, NominalType q1, NominalType q2) => Automaton q1 i -> Automaton q2 i -> Set [i]
|
|
||||||
bisim aut1 aut2 = runIdentity $ go empty (pairsWith addEmptyWord (initialStates aut1) (initialStates aut2))
|
|
||||||
where
|
|
||||||
go rel todo = do
|
|
||||||
-- if elements are already in R, we can skip them
|
|
||||||
let todo2 = filter (\(_, x, y) -> (x, y) `notMember` rel) todo
|
|
||||||
-- split into correct pairs and wrong pairs
|
|
||||||
let (cont, ces) = partition (\(_, x, y) -> (x `member` (finalStates aut1)) <==> (y `member` (finalStates aut2))) todo2
|
|
||||||
let aa = NLambda.alphabet aut1
|
|
||||||
-- the good pairs should make one step
|
|
||||||
let dtodo = sum (pairsWith (\(w, x, y) a -> pairsWith (\x2 y2 -> (a:w, x2, y2)) (d aut1 a x) (d aut2 a y)) cont aa)
|
|
||||||
-- if there are wrong pairs
|
|
||||||
ite (isNotEmpty ces)
|
|
||||||
-- then return counter examples
|
|
||||||
(return $ map getRevWord ces)
|
|
||||||
-- else continue with good pairs
|
|
||||||
(ite (isEmpty dtodo)
|
|
||||||
(return empty)
|
|
||||||
(go (rel `union` map stripWord cont) (dtodo))
|
|
||||||
)
|
|
||||||
d aut a x = mapFilter (\(s, l, t) -> maybeIf (s `eq` x /\ l `eq` a) t) (delta aut)
|
|
||||||
stripWord (_, x, y) = (x, y)
|
|
||||||
getRevWord (w, _, _) = reverse w
|
|
||||||
addEmptyWord x y = ([], x, y)
|
|
||||||
|
|
||||||
-- Attempt at using a bisimlution up to to proof bisimulation between NFAs
|
|
||||||
-- Because why not? Inspired by the Hacking non-determinism paper
|
|
||||||
-- But they only consider finite sums (which is enough for finite sets)
|
|
||||||
-- Here I have to do a bit of trickery, which is hopefully correct.
|
|
||||||
-- I think it is correct, but not yet complete enough, we need more up-to.
|
|
||||||
bisimNonDet :: (Show i, Show q1, Show q2, NominalType i, NominalType q1, NominalType q2) => Int -> Automaton q1 i -> Automaton q2 i -> Set [i]
|
|
||||||
bisimNonDet n aut1 aut2 = runIdentity $ go empty (singleton ([], initialStates aut1, initialStates aut2))
|
|
||||||
where
|
|
||||||
go rel todo0 = do
|
|
||||||
-- if elements are too long, we ignore them
|
|
||||||
let todo0b = filter (\(w,_,_) -> fromBool (length w <= n)) todo0
|
|
||||||
-- if elements are already in R, we can skip them
|
|
||||||
let todo1 = filter (\(_, x, y) -> (x, y) `notMember` rel) todo0b
|
|
||||||
-- now we are going to do a up-to thingy
|
|
||||||
-- we look at all subsets x2 of x occuring in R (similarly for y)
|
|
||||||
let xbar x = mapFilter (\(x2, _) -> maybeIf (x2 `isSubsetOf` x) x2) rel
|
|
||||||
let ybar y = mapFilter (\(_, y2) -> maybeIf (y2 `isSubsetOf` y) y2) rel
|
|
||||||
-- and then the sums are expressed by these formulea kind of
|
|
||||||
let xform x y = x `eq` sum (xbar x) /\ forAll (\x2 -> exists (\y2 -> rel `contains` (x2, y2)) (ybar y)) (xbar x)
|
|
||||||
let yform x y = y `eq` sum (ybar y) /\ forAll (\y2 -> exists (\x2 -> rel `contains` (x2, y2)) (xbar x)) (ybar y)
|
|
||||||
let notSums x y = not (xform x y /\ yform x y)
|
|
||||||
-- filter out things expressed as sums
|
|
||||||
let todo2 = filter (\(_, x, y) -> notSums x y) todo1
|
|
||||||
-- split into correct pairs and wrong pairs
|
|
||||||
let (cont, ces) = partition (\(_, x, y) -> (x `intersect` (finalStates aut1)) <==> (y `intersect` (finalStates aut2))) todo2
|
|
||||||
let aa = NLambda.alphabet aut1
|
|
||||||
-- the good pairs should make one step
|
|
||||||
let dtodo = pairsWith (\(w, x, y) a -> (a:w, sumMap (d aut1 a) x, sumMap (d aut2 a) y)) cont aa
|
|
||||||
-- if there are wrong pairs
|
|
||||||
--trace "go" $ traceShow rel $ traceShow todo0 $ traceShow todo1 $ traceShow todo2 $ traceShow cont $
|
|
||||||
ite (isNotEmpty ces)
|
|
||||||
-- then return counter examples
|
|
||||||
(return $ map getRevWord ces)
|
|
||||||
-- else continue with good pairs
|
|
||||||
(ite (isEmpty dtodo)
|
|
||||||
(return empty)
|
|
||||||
(go (rel `union` map stripWord cont) (dtodo))
|
|
||||||
)
|
|
||||||
d aut a x = mapFilter (\(s, l, t) -> maybeIf (s `eq` x /\ l `eq` a) t) (delta aut)
|
|
||||||
stripWord (_, x, y) = (x, y)
|
|
||||||
getRevWord (w, _, _) = reverse w
|
|
||||||
addEmptyWord x y = ([], x, y)
|
|
||||||
sumMap f = sum . (map f)
|
|
||||||
|
|
||||||
-- Posing a membership query to the terminal and waits for used to input a formula
|
|
||||||
ioMembership :: (Show i, NominalType i, Contextual i) => Set [i] -> Set ([i], Formula)
|
|
||||||
ioMembership inputs = unsafePerformIO $ do
|
|
||||||
let representedInputs = fromVariant . fromJust <$> (toList $ setOrbitsRepresentatives inputs)
|
|
||||||
Prelude.putStrLn "\n# Membership Queries:"
|
|
||||||
Prelude.putStrLn "# Please answer each query with either \"True\" or \"False\""
|
|
||||||
answers <- forM representedInputs $ \input -> do
|
|
||||||
Prelude.putStr "Q: "
|
|
||||||
Prelude.print input
|
|
||||||
let loop = do
|
|
||||||
x <- getInputLine "A: "
|
|
||||||
case x of
|
|
||||||
Nothing -> error "Bye bye, have a good day!"
|
|
||||||
Just str -> do
|
|
||||||
case readMaybe str :: Maybe Bool of
|
|
||||||
Nothing -> do
|
|
||||||
outputStrLn $ "Unable to parse " ++ str ++ " :: Bool"
|
|
||||||
loop
|
|
||||||
Just f -> return f
|
|
||||||
answer <- runInputT defaultSettings loop
|
|
||||||
return $ orbit [] (input, fromBool answer)
|
|
||||||
let answersAsSet = simplify . sum . fromList $ answers
|
|
||||||
return answersAsSet
|
|
||||||
|
|
||||||
-- Poses a query to the terminal, waiting for the user to provide a counter example
|
|
||||||
-- TODO: extend to any alphabet type (hard because of parsing)
|
|
||||||
ioEquivalent :: (Show q, NominalType q) => Automaton q Atom -> Maybe (Set [Atom])
|
|
||||||
ioEquivalent hypothesis = unsafePerformIO $ do
|
|
||||||
Prelude.putStrLn "\n# Is the following automaton correct?"
|
|
||||||
Prelude.putStr "# "
|
|
||||||
Prelude.print hypothesis
|
|
||||||
Prelude.putStrLn "# \"Nothing\" for equivalent, \"Just [...]\" for a counter example (eg \"Just [0,1,0]\")"
|
|
||||||
answer <- runInputT defaultSettings loop
|
|
||||||
case answer of
|
|
||||||
Nothing -> return Nothing
|
|
||||||
Just input -> do
|
|
||||||
-- create sequences of same length
|
|
||||||
let n = length input
|
|
||||||
let sequence = replicateAtoms n
|
|
||||||
-- whenever two are identiacl in input, we will use eq, if not neq
|
|
||||||
let op i j = if (input !! i) == (input !! j) then eq else neq
|
|
||||||
-- copy the relations from input to sequence
|
|
||||||
let rels s = and [op i j (s !! i) (s !! j) | i <- [0..n - 1], j <- [0..n - 1], i < j]
|
|
||||||
let fseq = filter rels sequence
|
|
||||||
return $ Just fseq
|
|
||||||
where
|
|
||||||
loop = do
|
|
||||||
x <- getInputLine "> "
|
|
||||||
case x of
|
|
||||||
Nothing -> error "Bye bye, have a good day!"
|
|
||||||
Just str -> do
|
|
||||||
case readMaybe str :: Maybe (Maybe [Int]) of
|
|
||||||
Nothing -> do
|
|
||||||
outputStrLn $ "Unable to parse " ++ str ++ " :: Maybe [Int]"
|
|
||||||
loop
|
|
||||||
Just f -> return f
|
|
||||||
|
|
28
src/Teachers/Teacher.hs
Normal file
28
src/Teachers/Teacher.hs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
module Teachers.Teacher where
|
||||||
|
|
||||||
|
import NLambda
|
||||||
|
import Prelude hiding (map)
|
||||||
|
|
||||||
|
-- Abstract teacher type. Maybe this will be generalized to some monad, so that
|
||||||
|
-- the teacher can have state (such as a cache).
|
||||||
|
-- TODO: add a notion of state, so that Teachers can maintain a cache, or
|
||||||
|
-- a socket, or file, or whatever (as long as it stays deterministic).
|
||||||
|
data Teacher i = Teacher
|
||||||
|
-- A teacher provides a way to answer membership queries. You'd expect
|
||||||
|
-- a function of type [i] -> Bool. But in order to implement some teachers
|
||||||
|
-- more efficiently, we provide Set [i] as input, and the teacher is
|
||||||
|
-- supposed to answer each element in the set.
|
||||||
|
{ membership :: Set [i] -> Set ([i], Formula)
|
||||||
|
-- Given a hypothesis, returns Nothing when equivalence or a (equivariant)
|
||||||
|
-- set of counter examples. Needs to be quantified over q, because the
|
||||||
|
-- learner may choose the type of the state space.
|
||||||
|
, equivalent :: forall q. (Show q, NominalType q) => Automaton q i -> Maybe (Set [i])
|
||||||
|
-- Returns the alphabet to the learner
|
||||||
|
, alphabet :: Set i
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Often a membership query is defined by a function [i] -> Formula. This wraps
|
||||||
|
-- such a function to the required type for a membership query (see above).
|
||||||
|
foreachQuery :: NominalType i => ([i] -> Formula) -> Set[i] -> Set ([i], Formula)
|
||||||
|
foreachQuery f qs = map (\q -> (q, f q)) qs
|
74
src/Teachers/Terminal.hs
Normal file
74
src/Teachers/Terminal.hs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
module Teachers.Terminal where
|
||||||
|
|
||||||
|
import NLambda
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Data.IORef
|
||||||
|
import Prelude hiding (filter, map, and, sum)
|
||||||
|
import System.Console.Haskeline
|
||||||
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
|
import Text.Read (readMaybe)
|
||||||
|
|
||||||
|
-- Posing a membership query to the terminal and waits for used to input a formula
|
||||||
|
ioMembership :: Set [Atom] -> Set ([Atom], Formula)
|
||||||
|
ioMembership queries = unsafePerformIO $ do
|
||||||
|
cache <- readIORef mqCache
|
||||||
|
let cachedAnswers = filter (\(a, f) -> a `member` queries) cache
|
||||||
|
let newQueries = simplify $ queries \\ map fst cache
|
||||||
|
let representedInputs = fromVariant . fromJust <$> (toList $ setOrbitsRepresentatives newQueries)
|
||||||
|
putStrLn "\n# Membership Queries:"
|
||||||
|
putStrLn "# Please answer each query with \"True\" or \"False\" (\"^D\" for quit)"
|
||||||
|
answers <- forM representedInputs $ \input -> do
|
||||||
|
putStr "Q: "
|
||||||
|
print input
|
||||||
|
let loop = do
|
||||||
|
x <- fmap readMaybe <$> getInputLine "A: "
|
||||||
|
case x of
|
||||||
|
Nothing -> error "Bye bye, have a good day!"
|
||||||
|
Just Nothing -> do
|
||||||
|
outputStrLn $ "Unable to parse, try again"
|
||||||
|
loop
|
||||||
|
Just (Just f) -> return (f :: Bool)
|
||||||
|
answer <- runInputT defaultSettings loop
|
||||||
|
return $ orbit [] (input, fromBool answer)
|
||||||
|
let answersAsSet = simplify . sum . fromList $ answers
|
||||||
|
writeIORef mqCache (simplify $ cache `union` answersAsSet)
|
||||||
|
return (simplify $ cachedAnswers `union` answersAsSet)
|
||||||
|
|
||||||
|
-- We use a cache, so that questions will not be repeated.
|
||||||
|
-- It is a bit hacky, as the Teacher interface does not allow state...
|
||||||
|
mqCache :: IORef (Set ([Atom], Formula))
|
||||||
|
{-# NOINLINE mqCache #-}
|
||||||
|
mqCache = unsafePerformIO $ newIORef empty
|
||||||
|
|
||||||
|
-- Poses a query to the terminal, waiting for the user to provide a counter example
|
||||||
|
-- TODO: extend to any alphabet type (hard because of parsing)
|
||||||
|
ioEquivalent :: (Show q, NominalType q) => Automaton q Atom -> Maybe (Set [Atom])
|
||||||
|
ioEquivalent hypothesis = unsafePerformIO $ do
|
||||||
|
putStrLn "\n# Is the following automaton correct?"
|
||||||
|
putStr "# "
|
||||||
|
print hypothesis
|
||||||
|
putStrLn "# \"^D\" for equivalent, \"[...]\" for a counter example (eg \"[0,1,0]\")"
|
||||||
|
let loop = do
|
||||||
|
x <- fmap readMaybe <$> getInputLine "> "
|
||||||
|
case x of
|
||||||
|
Nothing -> do
|
||||||
|
outputStrLn $ "Ok, we're done"
|
||||||
|
return Nothing
|
||||||
|
Just Nothing -> do
|
||||||
|
outputStrLn $ "Unable to parse, try again"
|
||||||
|
loop
|
||||||
|
Just (Just f) -> return (Just f :: Maybe [Int])
|
||||||
|
answer <- runInputT defaultSettings loop
|
||||||
|
case answer of
|
||||||
|
Nothing -> return Nothing
|
||||||
|
Just input -> do
|
||||||
|
-- create sequences of same length
|
||||||
|
let n = length input
|
||||||
|
let sequence = replicateAtoms n
|
||||||
|
-- whenever two are identiacl in input, we will use eq, if not neq
|
||||||
|
let op i j = if (input !! i) == (input !! j) then eq else neq
|
||||||
|
-- copy the relations from input to sequence
|
||||||
|
let rels s = and [op i j (s !! i) (s !! j) | i <- [0..n - 1], j <- [0..n - 1], i < j]
|
||||||
|
let fseq = filter rels sequence
|
||||||
|
return $ Just fseq
|
84
src/Teachers/Whitebox.hs
Normal file
84
src/Teachers/Whitebox.hs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
module Teachers.Whitebox where
|
||||||
|
|
||||||
|
import NLambda
|
||||||
|
|
||||||
|
import Control.Monad.Identity
|
||||||
|
import Prelude hiding (map, sum, filter, not)
|
||||||
|
|
||||||
|
-- I found it a bit easier to write a do-block below. So I needed this
|
||||||
|
-- Conditional instance.
|
||||||
|
instance Conditional a => Conditional (Identity a) where
|
||||||
|
cond f x y = return (cond f (runIdentity x) (runIdentity y))
|
||||||
|
|
||||||
|
|
||||||
|
-- Checks bisimulation of initial states (only for DFAs)
|
||||||
|
-- returns some counter examples if not bisimilar
|
||||||
|
-- returns empty set iff bisimilar
|
||||||
|
bisim :: (NominalType i, NominalType q1, NominalType q2) => Automaton q1 i -> Automaton q2 i -> Set [i]
|
||||||
|
bisim aut1 aut2 = runIdentity $ go empty (pairsWith addEmptyWord (initialStates aut1) (initialStates aut2))
|
||||||
|
where
|
||||||
|
go rel todo = do
|
||||||
|
-- if elements are already in R, we can skip them
|
||||||
|
let todo2 = filter (\(_, x, y) -> (x, y) `notMember` rel) todo
|
||||||
|
-- split into correct pairs and wrong pairs
|
||||||
|
let (cont, ces) = partition (\(_, x, y) -> (x `member` (finalStates aut1)) <==> (y `member` (finalStates aut2))) todo2
|
||||||
|
let aa = NLambda.alphabet aut1
|
||||||
|
-- the good pairs should make one step
|
||||||
|
let dtodo = sum (pairsWith (\(w, x, y) a -> pairsWith (\x2 y2 -> (a:w, x2, y2)) (d aut1 a x) (d aut2 a y)) cont aa)
|
||||||
|
-- if there are wrong pairs
|
||||||
|
ite (isNotEmpty ces)
|
||||||
|
-- then return counter examples
|
||||||
|
(return $ map getRevWord ces)
|
||||||
|
-- else continue with good pairs
|
||||||
|
(ite (isEmpty dtodo)
|
||||||
|
(return empty)
|
||||||
|
(go (rel `union` map stripWord cont) (dtodo))
|
||||||
|
)
|
||||||
|
d aut a x = mapFilter (\(s, l, t) -> maybeIf (s `eq` x /\ l `eq` a) t) (delta aut)
|
||||||
|
stripWord (_, x, y) = (x, y)
|
||||||
|
getRevWord (w, _, _) = reverse w
|
||||||
|
addEmptyWord x y = ([], x, y)
|
||||||
|
|
||||||
|
-- Attempt at using a bisimlution up to to proof bisimulation between NFAs
|
||||||
|
-- Because why not? Inspired by the Hacking non-determinism paper
|
||||||
|
-- But they only consider finite sums (which is enough for finite sets)
|
||||||
|
-- Here I have to do a bit of trickery, which is hopefully correct.
|
||||||
|
-- I think it is correct, but not yet complete enough, we need more up-to.
|
||||||
|
bisimNonDet :: (Show i, Show q1, Show q2, NominalType i, NominalType q1, NominalType q2) => Int -> Automaton q1 i -> Automaton q2 i -> Set [i]
|
||||||
|
bisimNonDet n aut1 aut2 = runIdentity $ go empty (singleton ([], initialStates aut1, initialStates aut2))
|
||||||
|
where
|
||||||
|
go rel todo0 = do
|
||||||
|
-- if elements are too long, we ignore them
|
||||||
|
let todo0b = filter (\(w,_,_) -> fromBool (length w <= n)) todo0
|
||||||
|
-- if elements are already in R, we can skip them
|
||||||
|
let todo1 = filter (\(_, x, y) -> (x, y) `notMember` rel) todo0b
|
||||||
|
-- now we are going to do a up-to thingy
|
||||||
|
-- we look at all subsets x2 of x occuring in R (similarly for y)
|
||||||
|
let xbar x = mapFilter (\(x2, _) -> maybeIf (x2 `isSubsetOf` x) x2) rel
|
||||||
|
let ybar y = mapFilter (\(_, y2) -> maybeIf (y2 `isSubsetOf` y) y2) rel
|
||||||
|
-- and then the sums are expressed by these formulea kind of
|
||||||
|
let xform x y = x `eq` sum (xbar x) /\ forAll (\x2 -> exists (\y2 -> rel `contains` (x2, y2)) (ybar y)) (xbar x)
|
||||||
|
let yform x y = y `eq` sum (ybar y) /\ forAll (\y2 -> exists (\x2 -> rel `contains` (x2, y2)) (xbar x)) (ybar y)
|
||||||
|
let notSums x y = not (xform x y /\ yform x y)
|
||||||
|
-- filter out things expressed as sums
|
||||||
|
let todo2 = filter (\(_, x, y) -> notSums x y) todo1
|
||||||
|
-- split into correct pairs and wrong pairs
|
||||||
|
let (cont, ces) = partition (\(_, x, y) -> (x `intersect` (finalStates aut1)) <==> (y `intersect` (finalStates aut2))) todo2
|
||||||
|
let aa = NLambda.alphabet aut1
|
||||||
|
-- the good pairs should make one step
|
||||||
|
let dtodo = pairsWith (\(w, x, y) a -> (a:w, sumMap (d aut1 a) x, sumMap (d aut2 a) y)) cont aa
|
||||||
|
-- if there are wrong pairs
|
||||||
|
--trace "go" $ traceShow rel $ traceShow todo0 $ traceShow todo1 $ traceShow todo2 $ traceShow cont $
|
||||||
|
ite (isNotEmpty ces)
|
||||||
|
-- then return counter examples
|
||||||
|
(return $ map getRevWord ces)
|
||||||
|
-- else continue with good pairs
|
||||||
|
(ite (isEmpty dtodo)
|
||||||
|
(return empty)
|
||||||
|
(go (rel `union` map stripWord cont) (dtodo))
|
||||||
|
)
|
||||||
|
d aut a x = mapFilter (\(s, l, t) -> maybeIf (s `eq` x /\ l `eq` a) t) (delta aut)
|
||||||
|
stripWord (_, x, y) = (x, y)
|
||||||
|
getRevWord (w, _, _) = reverse w
|
||||||
|
addEmptyWord x y = ([], x, y)
|
||||||
|
sumMap f = sum . (map f)
|
Loading…
Add table
Reference in a new issue