mirror of
https://github.com/Jaxan/nominal-lstar.git
synced 2025-04-26 22:27:46 +02:00
Code: Angluin's algorithm with NLambda
This commit is contained in:
commit
d52f991a74
8 changed files with 700 additions and 0 deletions
28
NominalAngluin.cabal
Normal file
28
NominalAngluin.cabal
Normal file
|
@ -0,0 +1,28 @@
|
|||
-- Initial NominalAngluin.cabal generated by cabal init. For further
|
||||
-- documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: NominalAngluin
|
||||
version: 0.1.0.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
-- license:
|
||||
license-file: LICENSE
|
||||
author: Joshua Moerman
|
||||
maintainer: lakseru@gmail.com
|
||||
-- copyright:
|
||||
-- category:
|
||||
build-type: Simple
|
||||
-- extra-source-files:
|
||||
cabal-version: >=1.10
|
||||
|
||||
executable NominalAngluin
|
||||
main-is: Main.hs
|
||||
-- other-modules:
|
||||
-- other-extensions:
|
||||
build-depends:
|
||||
base >=4.8 && <4.9,
|
||||
containers,
|
||||
NLambda,
|
||||
readline
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
2
Setup.hs
Normal file
2
Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Distribution.Simple
|
||||
main = defaultMain
|
16
src/Examples.hs
Normal file
16
src/Examples.hs
Normal file
|
@ -0,0 +1,16 @@
|
|||
module Examples
|
||||
( module Examples
|
||||
, module Examples.Contrived
|
||||
, module Examples.Fifo
|
||||
, module Examples.Stack
|
||||
) where
|
||||
|
||||
import Examples.Contrived
|
||||
import Examples.Fifo
|
||||
import Examples.Stack
|
||||
import NLambda (Atom)
|
||||
import Teacher (TeacherWithTarget (..))
|
||||
|
||||
-- Wrapping it in a teacher
|
||||
exampleTeacher :: TeacherWithTarget Atom
|
||||
exampleTeacher = TeacherWithTarget example4
|
123
src/Examples/Contrived.hs
Normal file
123
src/Examples/Contrived.hs
Normal file
|
@ -0,0 +1,123 @@
|
|||
{-# LANGUAGE DeriveGeneric #-}
|
||||
module Examples.Contrived where
|
||||
|
||||
import Teacher
|
||||
|
||||
import NLambda hiding (a, b, c, d, e, f, g, h, i, j, k, l, m, n,
|
||||
o, p, q, r, s, t, u, v, w, x, y, z)
|
||||
|
||||
-- Explicit Prelude, as NLambda has quite some clashes
|
||||
import Data.Either (Either (..))
|
||||
import Data.Maybe (Maybe (..))
|
||||
import Prelude (Bool (..), Eq, Ord, Show, ($), (.))
|
||||
import qualified Prelude
|
||||
|
||||
import GHC.Generics (Generic)
|
||||
|
||||
-- Example automaton from the whiteboard. Three orbits with 0, 1 and 2
|
||||
-- registers. The third orbit has a local symmetry (S2).
|
||||
data Example1 = Initial | S1 Atom | S2 (Atom, Atom)
|
||||
deriving (Show, Eq, Ord, Generic)
|
||||
instance BareNominalType Example1
|
||||
example1 = automaton
|
||||
-- states, 4 orbits (of which one unreachable)
|
||||
(singleton Initial
|
||||
`union` map S1 atoms
|
||||
`union` map S2 atomsPairs)
|
||||
-- alphabet
|
||||
atoms
|
||||
-- transitions
|
||||
(map (\a -> (Initial, a, S1 a)) atoms
|
||||
`union` map (\a -> (S1 a, a, Initial)) atoms
|
||||
`union` mapFilter (\(a, b) -> maybeIf (neq a b) (S1 a, b, S2 (a, b))) atomsPairs
|
||||
`union` mapFilter (\(a, b, c) -> maybeIf (eq a c \/ eq b c) (S2 (a, b), c, Initial)) atomsTriples
|
||||
`union` mapFilter (\(a, b, c) -> maybeIf (neq a c /\ neq b c) (S2 (a, b), c, S1 c)) atomsTriples)
|
||||
-- initial states
|
||||
(singleton Initial)
|
||||
-- final states
|
||||
(map S2 atomsPairs)
|
||||
|
||||
|
||||
-- Accepts all even words (ignores the alphabet). Two orbits, with a
|
||||
-- trivial action. No registers.
|
||||
data Aut2 = Even | Odd deriving (Eq, Ord, Show, Generic)
|
||||
instance BareNominalType Aut2
|
||||
example2 = automaton
|
||||
-- states, two orbits
|
||||
(fromList [Even, Odd])
|
||||
-- alphabet
|
||||
atoms
|
||||
-- transitions
|
||||
(map (\a -> (Even, a, Odd)) atoms
|
||||
`union` map (\a -> (Odd, a, Even)) atoms)
|
||||
-- initial states
|
||||
(singleton Even)
|
||||
-- final states
|
||||
(singleton Even)
|
||||
|
||||
|
||||
-- Accepts all non-empty words with the same symbol. Three orbits: the initial
|
||||
-- state, a state with a register and a sink state.
|
||||
data Aut3 = Empty | Stored Atom | Sink deriving (Eq, Ord, Show, Generic)
|
||||
instance BareNominalType Aut3
|
||||
example3 = automaton
|
||||
-- states, three orbits
|
||||
(fromList [Empty, Sink]
|
||||
`union` map Stored atoms)
|
||||
-- alphabet
|
||||
atoms
|
||||
-- transitions
|
||||
(map (\a -> (Empty, a, Stored a)) atoms
|
||||
`union` map (\a -> (Stored a, a, Stored a)) atoms
|
||||
`union` map (\(a,b) -> (Stored a, b, Sink)) differentAtomsPairs
|
||||
`union` map (\a -> (Sink, a, Sink)) atoms)
|
||||
-- initial states
|
||||
(singleton Empty)
|
||||
-- final states
|
||||
(map Stored atoms)
|
||||
|
||||
|
||||
-- Example showing that a local symmetry is not always the full symmetric group
|
||||
-- or trivial. Five (reachable) orbits. The state Symm a b c has a C3 symmetry,
|
||||
-- i.e. we can shift: Symm a b c ~ Symm b c a, but not swap: Symm a b c !~
|
||||
-- Symm a c b (here ~ means bisimilar).
|
||||
data Aut4 = Aut4Init -- Initial state
|
||||
| First Atom -- State after reading 1 symbol
|
||||
| Second Atom Atom -- After reading two different symbols
|
||||
| Symm Atom Atom Atom -- Accepting state with C3 symmetry
|
||||
| Sorted Atom Atom Atom -- State without symmetry
|
||||
deriving (Eq, Ord, Show, Generic)
|
||||
instance BareNominalType Aut4
|
||||
|
||||
example4 = automaton
|
||||
-- states
|
||||
(singleton Aut4Init
|
||||
`union` map First atoms
|
||||
`union` map (unc2 Second) atomsPairs
|
||||
`union` map (unc3 Symm) atomsTriples
|
||||
`union` map (unc3 Sorted) atomsTriples)
|
||||
-- alphabet
|
||||
atoms
|
||||
-- transitions
|
||||
(map (\a -> (Aut4Init, a, First a)) atoms
|
||||
`union` map (\a -> (First a, a, Aut4Init)) atoms
|
||||
`union` map (\(a, b) -> (First a, b, Second a b)) differentAtomsPairs
|
||||
`union` map (\(a, b) -> (Second a b, a, Aut4Init)) atomsPairs
|
||||
`union` map (\(a, b) -> (Second a b, b, Aut4Init)) atomsPairs
|
||||
`union` mapFilter (\(a, b, c) -> maybeIf (c `neq` a /\ c `neq` b) (Second a b, c, Symm a b c)) atomsTriples
|
||||
`union` mapFilter (\(a, b, c, d) -> maybeIf (d `neq` a /\ d `neq` b /\ d `neq` c) (Symm a b c, d, Aut4Init)) atomsQuadruples
|
||||
`union` map (\(a, b, c) -> (Symm a b c, a, Sorted a b c)) atomsTriples
|
||||
`union` map (\(a, b, c) -> (Symm a b c, b, Sorted b c a)) atomsTriples
|
||||
`union` map (\(a, b, c) -> (Symm a b c, c, Sorted c a b)) atomsTriples
|
||||
`union` mapFilter (\(a, b, c, d) -> maybeIf (d `neq` a /\ d `neq` b /\ d `neq` c) (Sorted a b c, d, First d)) atomsQuadruples
|
||||
`union` map (\(a, b, c) -> (Sorted a b c, a, Sorted a b c)) atomsTriples
|
||||
`union` map (\(a, b, c) -> (Sorted a b c, b, Symm a b c)) atomsTriples
|
||||
`union` map (\(a, b, c) -> (Sorted a b c, c, Aut4Init)) atomsTriples)
|
||||
-- initial states
|
||||
(singleton Aut4Init)
|
||||
-- final states
|
||||
(map (unc3 Symm) atomsTriples)
|
||||
where
|
||||
atomsQuadruples = map (\[a,b,c,d] -> (a,b,c,d)) $ replicateAtoms 4
|
||||
unc2 f (a,b) = f a b
|
||||
unc3 f (a,b,c) = f a b c
|
75
src/Examples/Fifo.hs
Normal file
75
src/Examples/Fifo.hs
Normal file
|
@ -0,0 +1,75 @@
|
|||
{-# LANGUAGE DeriveGeneric #-}
|
||||
module Examples.Fifo (DataInput(..), fifoExample) where
|
||||
|
||||
import GHC.Generics (Generic)
|
||||
import NLambda
|
||||
import Prelude (Bool (..), Eq, Int, Maybe (..), Ord, Show,
|
||||
length, reverse, ($), (+), (-), (.), (>=))
|
||||
import qualified Prelude
|
||||
|
||||
|
||||
-- Functional queue data type. First list is for push stuff onto, the
|
||||
-- second list is to pop. If the second list is empty, it will reverse
|
||||
-- the first.
|
||||
data Fifo a = Fifo [a] [a] deriving (Eq, Ord, Show, Generic)
|
||||
|
||||
push :: a -> Fifo a -> Fifo a
|
||||
push x (Fifo l1 l2) = Fifo (x:l1) l2
|
||||
|
||||
pop :: Fifo a -> Maybe (a, Fifo a)
|
||||
pop (Fifo [] []) = Nothing
|
||||
pop (Fifo l1 []) = pop (Fifo [] (reverse l1))
|
||||
pop (Fifo l1 (x:l2)) = Just (x, Fifo l1 l2)
|
||||
|
||||
isEmptyFifo :: Fifo a -> Bool
|
||||
isEmptyFifo (Fifo [] []) = True
|
||||
isEmptyFifo _ = False
|
||||
|
||||
emptyFifo :: Fifo a
|
||||
emptyFifo = Fifo [] []
|
||||
|
||||
sizeFifo :: Fifo a -> Int
|
||||
sizeFifo (Fifo l1 l2) = length l1 + length l2
|
||||
|
||||
|
||||
-- Our automaton accepts valid traces for this data structure. For
|
||||
-- example Put(a) Get(a) is valid, but Put(a) Get(b) is only valid if
|
||||
-- a = b. We will bound the number of states to make it a finite orbit
|
||||
-- nominal automaton.
|
||||
|
||||
-- The alphabet:
|
||||
data DataInput = Put Atom | Get Atom deriving (Eq, Ord, Show, Generic)
|
||||
instance BareNominalType DataInput
|
||||
instance Contextual DataInput where
|
||||
when f (Put a) = Put (when f a)
|
||||
when f (Get a) = Get (when f a)
|
||||
|
||||
-- The automaton: States consist of fifo queues and a sink state.
|
||||
-- This representation is not minimal at all, but that's OK, since the
|
||||
-- learner will learn a minimal anyways. The parameter n is the bound.
|
||||
instance BareNominalType a => BareNominalType (Fifo a)
|
||||
fifoExample n = automaton
|
||||
-- states
|
||||
(singleton Nothing
|
||||
`union` map Just allStates)
|
||||
-- alphabet
|
||||
(map Put atoms `union` map Get atoms)
|
||||
-- transitions
|
||||
(map (\a -> (Nothing, Put a, Nothing)) atoms
|
||||
`union` map (\a -> (Nothing, Get a, Nothing)) atoms
|
||||
`union` pairsWith (\s a -> (Just s, Put a, nextState s a)) allStates atoms
|
||||
`union` sum (map prevStates allStates))
|
||||
-- initial states
|
||||
(singleton (Just emptyFifo))
|
||||
-- final states
|
||||
(map Just allStates)
|
||||
where
|
||||
allStates = sum . fromList $ [states i | i <- [0..n]]
|
||||
states i = sum . fromList $ [ pairsWith Fifo (replicateAtoms j) (replicateAtoms (i - j)) | j <- [0 .. i]]
|
||||
nextState fifo a = if sizeFifo fifo >= n
|
||||
then Nothing
|
||||
else Just (push a fifo)
|
||||
prevStates fifo = case pop fifo of
|
||||
Nothing -> map (\a -> (Just fifo, Get a, Nothing)) atoms
|
||||
Just (b, fifo2) -> singleton (Just fifo, Get b, Just fifo2)
|
||||
`union` mapFilter (\a -> maybeIf (a `neq` b) (Just fifo, Get a, Nothing)) atoms
|
67
src/Examples/Stack.hs
Normal file
67
src/Examples/Stack.hs
Normal file
|
@ -0,0 +1,67 @@
|
|||
{-# LANGUAGE DeriveGeneric #-}
|
||||
module Examples.Stack (DataInput(..), stackExample) where
|
||||
|
||||
import Examples.Fifo (DataInput (..))
|
||||
import GHC.Generics (Generic)
|
||||
import NLambda
|
||||
import Prelude (Bool (..), Eq, Int, Maybe (..), Ord, Show,
|
||||
length, ($), (.), (>=))
|
||||
import qualified Prelude
|
||||
|
||||
|
||||
-- Functional stack data type is simply a list.
|
||||
data Stack a = Stack [a] deriving (Eq, Ord, Show, Generic)
|
||||
|
||||
push :: a -> Stack a -> Stack a
|
||||
push x (Stack l1) = Stack (x:l1)
|
||||
|
||||
pop :: Stack a -> Maybe (a, Stack a)
|
||||
pop (Stack []) = Nothing
|
||||
pop (Stack (x:l)) = Just (x, Stack l)
|
||||
|
||||
isEmptyStack :: Stack a -> Bool
|
||||
isEmptyStack (Stack []) = True
|
||||
isEmptyStack _ = False
|
||||
|
||||
emptyStack :: Stack a
|
||||
emptyStack = Stack []
|
||||
|
||||
sizeStack :: Stack a -> Int
|
||||
sizeStack (Stack l1) = length l1
|
||||
|
||||
|
||||
-- Our automaton accepts valid traces for this data structure. For
|
||||
-- example Put(a) Get(a) is valid, but Put(a) Get(b) is only valid if
|
||||
-- a = b. We will bound the number of states to make it a finite orbit
|
||||
-- nominal automaton.
|
||||
|
||||
-- The alphabet is defined in Examples.Fifo
|
||||
|
||||
-- The automaton: States consist of stacks and a sink state.
|
||||
-- The parameter n is the bound.
|
||||
instance BareNominalType a => BareNominalType (Stack a)
|
||||
stackExample n = automaton
|
||||
-- states
|
||||
(singleton Nothing
|
||||
`union` map Just allStates)
|
||||
-- alphabet
|
||||
(map Put atoms `union` map Get atoms)
|
||||
-- transitions
|
||||
(map (\a -> (Nothing, Put a, Nothing)) atoms
|
||||
`union` map (\a -> (Nothing, Get a, Nothing)) atoms
|
||||
`union` pairsWith (\s a -> (Just s, Put a, nextState s a)) allStates atoms
|
||||
`union` sum (map prevStates allStates))
|
||||
-- initial states
|
||||
(singleton (Just emptyStack))
|
||||
-- final states
|
||||
(map Just allStates)
|
||||
where
|
||||
allStates = sum . fromList $ [states i | i <- [0..n]]
|
||||
states i = map Stack (replicateAtoms i)
|
||||
nextState stack a = if sizeStack stack >= n
|
||||
then Nothing
|
||||
else Just (push a stack)
|
||||
prevStates stack = case pop stack of
|
||||
Nothing -> map (\a -> (Just stack, Get a, Nothing)) atoms
|
||||
Just (b, stack2) -> singleton (Just stack, Get b, Just stack2)
|
||||
`union` mapFilter (\a -> maybeIf (a `neq` b) (Just stack, Get a, Nothing)) atoms
|
243
src/Main.hs
Normal file
243
src/Main.hs
Normal file
|
@ -0,0 +1,243 @@
|
|||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
|
||||
import Examples
|
||||
import Teacher
|
||||
|
||||
import NLambda hiding (a, b, c, d, e, f, g, h, i, j, k, l, m,
|
||||
n, o, p, q, r, s, t, u, v, w, x, y, z)
|
||||
|
||||
import Data.List (inits)
|
||||
import Data.Maybe (fromJust)
|
||||
import Prelude hiding (and, curry, filter, lookup, map, not,
|
||||
sum, uncurry)
|
||||
|
||||
import GHC.Generics (Generic)
|
||||
|
||||
-- We represent functions as their graphs
|
||||
type Fun a b = Set (a, b)
|
||||
|
||||
-- Basic manipulations on functions
|
||||
-- Note that this returns a set, rather than an element
|
||||
-- because we cannot extract a value from a singleton set
|
||||
apply :: (NominalType a, NominalType b) => Fun a b -> a -> Set b
|
||||
apply f a1 = mapFilter (\(a2, b) -> maybeIf (eq a1 a2) b) f
|
||||
|
||||
-- AxB -> c is adjoint to A -> C^B
|
||||
-- curry and uncurry witnesses both ways of the adjunction
|
||||
curry :: (NominalType a, NominalType b, NominalType c) => Fun (a, b) c -> Fun a (Fun b c)
|
||||
curry f = map (\a1 -> (a1, mapFilter (\((a2,b),c) -> maybeIf (eq a1 a2) (b,c)) f)) as
|
||||
where as = map (\((a,b),c) -> a) f
|
||||
|
||||
uncurry :: (NominalType a, NominalType b, NominalType c) => Fun a (Fun b c) -> Fun (a, b) c
|
||||
uncurry f = sum $ map (\(a,s) -> map (\(b,c) -> ((a, b), c)) s) f
|
||||
|
||||
-- Returns the subset (of the domain) which exhibits
|
||||
-- different return values for the two functions
|
||||
-- I am not sure about its correctness...
|
||||
discrepancy :: (NominalType a, NominalType b) => Fun a b -> Fun a b -> Set a
|
||||
discrepancy f1 f2 =
|
||||
pairsWithFilter (
|
||||
\(a1,b1) (a2,b2) -> maybeIf (eq a1 a2 /\ neq b1 b2) a1
|
||||
) f1 f2
|
||||
|
||||
|
||||
-- An observation table is a function S x E -> O
|
||||
-- (Also includes SA x E -> O)
|
||||
type Table i o = Fun ([i], [i]) o
|
||||
|
||||
-- `row is` denotes the data of a single row
|
||||
-- that is, the function E -> O
|
||||
row :: (NominalType i, NominalType o) => Table i o -> [i] -> Fun [i] o
|
||||
row t is = sum (apply (curry t) is)
|
||||
|
||||
-- `rowa is a` is the row for the one letter extensions
|
||||
rowa :: (NominalType i, NominalType o) => Table i o -> [i] -> i -> Fun [i] o
|
||||
rowa t is a = row t (is ++ [a])
|
||||
|
||||
-- Our context
|
||||
type Output = Bool
|
||||
|
||||
-- fills part of the table. First parameter is the rows (with extension),
|
||||
-- second is columns. Although the teacher provides us formulas instead of
|
||||
-- booleans, we can partition the answers to obtain actual booleans.
|
||||
fillTable :: (Contextual i, NominalType i, Teacher t i) => t -> Set [i] -> Set [i] -> Table i Output
|
||||
fillTable teacher sssa ee = sum2 . map2 (map slv) . map2 simplify . partition (\(s, e, f) -> f) $ base
|
||||
where
|
||||
base = pairsWith (\s e -> (s, e, membership teacher (s++e))) sssa ee
|
||||
map2 f (a, b) = (f a, f b)
|
||||
slv (a,b,f) = ((a,b), Data.Maybe.fromJust . solve $ f)
|
||||
sum2 (a,b) = a `union` b
|
||||
|
||||
|
||||
-- Data structure representing the state of the learning algorithm (NOT a
|
||||
-- state in the automaton)
|
||||
data State i = State
|
||||
{ t :: Table i Output -- the table
|
||||
, ss :: Set [i] -- state sequences
|
||||
, ssa :: Set [i] -- their one letter extensions
|
||||
, ee :: Set [i] -- suffixes
|
||||
, aa :: Set i -- alphabet (remains constant)
|
||||
}
|
||||
deriving (Show, Ord, Eq, Generic)
|
||||
|
||||
instance NominalType i => BareNominalType (State i)
|
||||
|
||||
instance NominalType i => Conditional (State i) where
|
||||
cond f s1 s2 = fromTup (cond f (toTup s1) (toTup s2)) where
|
||||
toTup State{..} = (t,ss,ssa,ee,aa)
|
||||
fromTup (t,ss,ssa,ee,aa) = State{..}
|
||||
|
||||
-- We can determine its completeness with the following
|
||||
-- It returns all witnesses (of the form sa) for incompleteness
|
||||
incompleteness :: NominalType i => State i -> Set [i]
|
||||
incompleteness State{..} = filter (not . hasEqRow) ssa
|
||||
where
|
||||
-- true if the sequence sa has an equivalent row in ss
|
||||
hasEqRow sa = exists (\s2 -> eq (row t sa) (row t s2)) ss
|
||||
|
||||
-- We can determine its consistency with the following
|
||||
-- Returns equivalent rows (fst) with all inequivalent extensions (snd)
|
||||
inconsistency :: NominalType i => State i -> Set (([i], [i], i), Set [i])
|
||||
inconsistency State{..} =
|
||||
triplesWithFilter (
|
||||
\s1 s2 a -> maybeIf (candidate s1 s2 a) ((s1, s2, a), discrepancy (rowa t s1 a) (rowa t s2 a))
|
||||
) ss ss aa
|
||||
where
|
||||
-- true for equal rows, but unequal extensions
|
||||
candidate s1 s2 a = row t s1 `eq` row t s2
|
||||
/\ rowa t s1 a `neq` rowa t s2 a
|
||||
|
||||
-- This can be written for all monads. Unfortunately (a,) is also a monad and
|
||||
-- this gives rise to overlapping instances, so I only do it for IO here.
|
||||
-- Note that it is not really well defined, but it kinda works.
|
||||
instance (Conditional a) => Conditional (IO a) where
|
||||
cond f a b = case solve f of
|
||||
Just True -> a
|
||||
Just False -> b
|
||||
Nothing -> fail "### Unresolved branch ###"
|
||||
-- NOTE: another implementation would be to evaluate both a and b
|
||||
-- and apply ite to their results. This however would runs both side
|
||||
-- effects of a and b.
|
||||
|
||||
-- This function will (recursively) make the table complete and consistent.
|
||||
-- This is in the IO monad purely because I want some debugging information.
|
||||
-- (Same holds for many other functions here)
|
||||
makeCompleteConsistent :: (Show i, Contextual i, NominalType i, Teacher t i) => t -> State i -> IO (State i)
|
||||
makeCompleteConsistent teacher state@State{..} = do
|
||||
-- inc is the set of rows witnessing incompleteness, that is the sequences
|
||||
-- 's1 a' which do not have their equivalents of the form 's2'.
|
||||
let inc = incompleteness state
|
||||
ite (isNotEmpty inc)
|
||||
(do
|
||||
-- If that set is non-empty, we should add new rows
|
||||
putStrLn "Incomplete!"
|
||||
-- These will be the new rows, ...
|
||||
let ds = inc
|
||||
putStr " -> Adding rows: "
|
||||
print ds
|
||||
-- ... and their extensions
|
||||
let dsa = pairsWith (\s a -> s ++ [a]) ds aa
|
||||
-- For the new rows, we fill the table
|
||||
-- note that `ds ee` is already filled
|
||||
let dt = fillTable teacher dsa ee
|
||||
putStr " -> delta table: "
|
||||
print dt
|
||||
-- And we repeat
|
||||
let state2 = state {t = t `union` dt, ss = ss `union` ds, ssa = ssa `union` dsa}
|
||||
makeCompleteConsistent teacher state2
|
||||
)
|
||||
(do
|
||||
-- inc2 is the set of inconsistencies.
|
||||
let inc2 = inconsistency state
|
||||
ite (isNotEmpty inc2)
|
||||
(do
|
||||
-- If that set is non-empty, we should add new columns
|
||||
putStrLn "Inconsistent!"
|
||||
-- The extensions are in the second component
|
||||
let de = sum $ map (\((s1,s2,a),es) -> map (a:) es) inc2
|
||||
putStr " -> Adding columns: "
|
||||
print de
|
||||
-- Fill that part of the table
|
||||
let dt = fillTable teacher (ss `union` ssa) de
|
||||
putStr " -> delta table: "
|
||||
print dt
|
||||
-- And we continue
|
||||
let state2 = state {t = t `union` dt, ee = ee `union` de}
|
||||
makeCompleteConsistent teacher state2
|
||||
)
|
||||
(do
|
||||
-- If both sets are empty, the table is complete and
|
||||
-- consistent, so we are done.
|
||||
putStrLn " => Complete + Consistent :D!"
|
||||
return state
|
||||
)
|
||||
)
|
||||
|
||||
-- Given a C&C table, constructs an automaton. The states are given by 2^E (not
|
||||
-- necessarily equivariant functions)
|
||||
constructHypothesis :: NominalType i => State i -> Automaton (Fun [i] Output) i
|
||||
constructHypothesis State{..} = automaton q a d i f
|
||||
where
|
||||
q = map (row t) ss
|
||||
a = aa
|
||||
d = pairsWith (\s a -> (row t s, a, rowa t s a)) ss aa
|
||||
i = singleton $ row t []
|
||||
f = mapFilter (\s -> maybeIf (toform $ apply t (s, [])) (row t s)) ss
|
||||
toform s = forAll id . map fromBool $ s
|
||||
|
||||
-- Extends the table with all prefixes of a set of counter examples.
|
||||
useCounterExample :: (Show i, Contextual i, NominalType i, Teacher t i) => t -> State i -> Set [i] -> IO (State i)
|
||||
useCounterExample teacher state@State{..} ces = do
|
||||
putStr "Using ce: "
|
||||
print ces
|
||||
let ds = sum . map (fromList . inits) $ ces
|
||||
putStr " -> Adding rows: "
|
||||
print ds
|
||||
-- as above, adding rows
|
||||
let dsa = pairsWith (\s a -> s ++ [a]) ds aa
|
||||
let dt = fillTable teacher dsa ee
|
||||
let state2 = state {t = t `union` dt, ss = ss `union` ds, ssa = ssa `union` dsa}
|
||||
return state2
|
||||
|
||||
-- The main loop, which results in an automaton. Will stop if the hypothesis
|
||||
-- exactly accepts the language we are learning.
|
||||
loop :: (Show i, Contextual i, NominalType i, Teacher t i) => t -> State i -> IO (Automaton (Fun [i] Output) i)
|
||||
loop teacher s = do
|
||||
putStrLn "##################"
|
||||
putStrLn "1. Making it complete and consistent"
|
||||
s <- makeCompleteConsistent teacher s
|
||||
putStrLn "2. Constructing hypothesis"
|
||||
let h = constructHypothesis s
|
||||
print h
|
||||
putStr "3. Equivalent? "
|
||||
let eq = equivalent teacher h
|
||||
print eq
|
||||
case eq of
|
||||
Nothing -> return h
|
||||
Just ce -> do
|
||||
s <- useCounterExample teacher s ce
|
||||
loop teacher s
|
||||
|
||||
constructEmptyState :: (Contextual i, NominalType i, Teacher t i) => t -> State i
|
||||
constructEmptyState teacher =
|
||||
let aa = Teacher.alphabet teacher in
|
||||
let ss = singleton [] in
|
||||
let ssa = pairsWith (\s a -> s ++ [a]) ss aa in
|
||||
let ee = singleton [] in
|
||||
let t = fillTable teacher (ss `union` ssa) ee in
|
||||
State{..}
|
||||
|
||||
learn :: (Show i, Contextual i, NominalType i, Teacher t i) => t -> IO (Automaton (Fun [i] Output) i)
|
||||
learn teacher = do
|
||||
let s = constructEmptyState teacher
|
||||
loop teacher s
|
||||
|
||||
-- Initializes the table and runs the algorithm.
|
||||
main :: IO ()
|
||||
main = do
|
||||
h <- learn exampleTeacher
|
||||
putStrLn "Finished! Final hypothesis ="
|
||||
print h
|
146
src/Teacher.hs
Normal file
146
src/Teacher.hs
Normal file
|
@ -0,0 +1,146 @@
|
|||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE FunctionalDependencies #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
|
||||
module Teacher where
|
||||
|
||||
import NLambda hiding (a, b, c, d, e, f, g, h, i, j,
|
||||
k, l, m, n, o, p, q, r, s, t, u, v, w,
|
||||
x, y, z)
|
||||
|
||||
-- Explicit Prelude, as NLambda has quite some clashes
|
||||
import Data.Function (fix)
|
||||
import Data.List (zip, (!!))
|
||||
import Data.Maybe (Maybe (..))
|
||||
import Prelude (Bool (..), IO, Int, Read, Show, error,
|
||||
fmap, length, return, ($), (++), (-),
|
||||
(<), (==))
|
||||
import qualified Prelude
|
||||
|
||||
-- Used in the IO teacher
|
||||
import System.Console.Readline
|
||||
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)
|
||||
-- NOTE: Maybe neater when implemented as record?
|
||||
class Teacher t i | t -> i where
|
||||
-- Given a sequence, check whether it is in the language
|
||||
-- Assumed to be equivariant
|
||||
membership :: t -> [i] -> Formula
|
||||
-- Given a hypothesis, returns Nothing when equivalence or a (equivariant)
|
||||
-- set of counter examples.
|
||||
equivalent :: (Show q, NominalType q) => t -> Automaton q i -> Maybe (Set [i])
|
||||
-- Returns the alphabet to the learner
|
||||
alphabet :: t -> Set i
|
||||
|
||||
|
||||
-- Type for a teacher with an automaton as target
|
||||
-- The state type is abstracted away
|
||||
data TeacherWithTarget i = forall q . NominalType q => TeacherWithTarget (Automaton q i)
|
||||
|
||||
-- Implements the teacher
|
||||
instance (NominalType i) => Teacher (TeacherWithTarget i) i where
|
||||
membership (TeacherWithTarget aut) input = accepts aut input
|
||||
equivalent (TeacherWithTarget aut) hypo = case solve isEq of
|
||||
Nothing -> error "should be solved"
|
||||
Just True -> Nothing
|
||||
Just False -> Just ce
|
||||
where
|
||||
isEq = equivalentDA aut hypo
|
||||
lDiff = aut `differenceDA` hypo
|
||||
rDiff = hypo `differenceDA` aut
|
||||
symmDiff = lDiff `unionDA` rDiff
|
||||
ce = searchWord symmDiff
|
||||
alphabet (TeacherWithTarget aut) = NLambda.alphabet aut
|
||||
|
||||
-- Searching a word in the difference automaton
|
||||
-- Will find all shortest ones
|
||||
searchWord :: (NominalType i, NominalType q) => Automaton q i -> Set [i]
|
||||
searchWord d = go (singleton [])
|
||||
where
|
||||
go aa = let candidates = filter (accepts d) aa in
|
||||
ite (isEmpty candidates)
|
||||
(go $ pairsWith (\as a -> a:as) aa (NLambda.alphabet d))
|
||||
candidates
|
||||
|
||||
|
||||
-- Will ask everything to someone reading the terminal
|
||||
data TeacherWithIO = TeacherWithIO
|
||||
|
||||
-- For the moment only Atom as input type
|
||||
-- Note that parsing is very unforgiving, one mistake, and there is no way back
|
||||
-- Atoms are referenced by Ints. When the user provides a counter example, we
|
||||
-- consider the whole orbit generated by it.
|
||||
instance Teacher TeacherWithIO Atom where
|
||||
membership _ input = unsafePerformIO $ do
|
||||
let supp = leastSupport input
|
||||
Prelude.putStrLn "\n# Is the following word accepted?"
|
||||
Prelude.putStr "# "
|
||||
Prelude.print input
|
||||
Prelude.putStrLn "# You can answer with a formula (EQ, NEQ, AND, OR, TRUE, FALSE)"
|
||||
Prelude.putStrLn "# You may use the following atoms:"
|
||||
Prelude.putStr "# "
|
||||
Prelude.print $ zip supp [0..]
|
||||
answer <- fix (\m -> do
|
||||
x <- readline "> "
|
||||
case x of
|
||||
Nothing -> error "Quit"
|
||||
Just str -> do
|
||||
case readMaybe str :: Maybe Form of
|
||||
Nothing -> do
|
||||
Prelude.putStrLn $ "Unable to parse " ++ str ++ " :: Form"
|
||||
m
|
||||
Just f -> return f
|
||||
)
|
||||
return $ interpret supp answer
|
||||
equivalent _ hypothesis = unsafePerformIO $ do
|
||||
Prelude.putStrLn "\n# Is the following automaton correct?"
|
||||
Prelude.putStr "# "
|
||||
Prelude.print hypothesis
|
||||
Prelude.putStrLn "# Nothing for Yes, Just [...] for a counter example"
|
||||
answer <- fix (\m -> do
|
||||
x <- readline "> "
|
||||
case x of
|
||||
Nothing -> error "Quit"
|
||||
Just str -> do
|
||||
case readMaybe str :: Maybe (Maybe [Prelude.String]) of
|
||||
Nothing -> do
|
||||
Prelude.putStrLn $ "Unable to parse " ++ str ++ " :: Maybe [String]"
|
||||
m
|
||||
Just f -> return f
|
||||
)
|
||||
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
|
||||
alphabet _ = atoms
|
||||
|
||||
-- Data structure for reading formulas (with the derived Read instance)
|
||||
data Form
|
||||
= EQ Int Int
|
||||
| NEQ Int Int
|
||||
| AND Form Form
|
||||
| OR Form Form
|
||||
| TRUE
|
||||
| FALSE
|
||||
deriving (Read)
|
||||
|
||||
interpret :: [Atom] -> Form -> Formula
|
||||
interpret support (EQ i j) = eq (support !! i) (support !! j)
|
||||
interpret support (NEQ i j) = neq (support !! i) (support !! j)
|
||||
interpret support (AND f1 f2) = interpret support f1 /\ interpret support f2
|
||||
interpret support (OR f1 f2) = interpret support f1 \/ interpret support f1
|
||||
interpret _ TRUE = true
|
||||
interpret _ FALSE = false
|
Loading…
Add table
Reference in a new issue