mirror of
https://github.com/Jaxan/nominal-lstar.git
synced 2025-04-27 06:37:45 +02:00
A little bit more on Mealy machines and updates the README
This commit is contained in:
parent
91d4c6d539
commit
f8c7b8d766
2 changed files with 89 additions and 51 deletions
41
README.md
41
README.md
|
@ -1,19 +1,13 @@
|
||||||
Learning Nominal Automata
|
Learning Nominal Automata
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
*NOTE*: Please download the archive `popl-artifact.zip`. This contains the
|
|
||||||
specific versions (of this and nlambda) used for the POPL submission.
|
|
||||||
This archive should contain a similar README, with simpler instructions.
|
|
||||||
If you want the newest version of the software, then don't use that
|
|
||||||
archive, but use the code in this repository.
|
|
||||||
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
This artifact was tested on a Debian system. During development both Mac and
|
This artifact was tested on a Debian system. During development both Mac and
|
||||||
Windows have been used, so it should work on these operating systems too. Note
|
Windows have been used, so it should work on these operating systems too. Note
|
||||||
that you will need the Z3 solver (as executable). The algorithms are
|
that you will need the Z3 solver (as executable). The algorithms are
|
||||||
implemented in Haskell and you will need a recent GHC (at least 7.10).
|
implemented in Haskell and you will need a recent GHC (at least 7.10).
|
||||||
|
Currently, GHC 9.10 is used in the development.
|
||||||
|
|
||||||
We use the library [nlambda](https://github.com/szynwelski/nlambda). It
|
We use the library [nlambda](https://github.com/szynwelski/nlambda). It
|
||||||
is recommended to use the most recent version. Just grab the source and
|
is recommended to use the most recent version. Just grab the source and
|
||||||
|
@ -26,24 +20,28 @@ Follow the build guide on their website.
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
You can use the stack tool. Make sure to include nlambda as a package.
|
The `cabal` build tool should suffice. Please put both this repository and
|
||||||
It should be a matter of `stack build`, if not, stack will probably
|
the nlambda repository somehwere, and add a `cabal.project` file with the
|
||||||
tell you what to do. (If you need any help, send me a message.)
|
following contents:
|
||||||
|
```
|
||||||
|
packages: nominal-lstar nlambda
|
||||||
|
```
|
||||||
|
Then a simple `cabal build all` should do it. To test whether everything
|
||||||
|
works, run `cabal test nominal-lstar`. (Please get in touch if you have
|
||||||
|
trouble setting it up.)
|
||||||
|
|
||||||
|
|
||||||
# Running
|
# Running
|
||||||
|
|
||||||
Stack will produce a binary in the `.stack-works` directory, which can
|
Run `cabal run nominal-lstar` within the `nominal-lstar` directory.
|
||||||
be invoked directly. Alternatively one can run `stack exec nominal-lstar`.
|
|
||||||
There is two modes of operation: Running the examples, or running it
|
|
||||||
interactively.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
The executable expects three arguments:
|
The executable expects one or three arguments:
|
||||||
|
|
||||||
```
|
```
|
||||||
stack exec NominalAngluin -- <Learner> <Oracle> <Example>
|
cabal run nominal-lstar <Learner>
|
||||||
|
cabal run nominal-lstar <Learner> <Oracle> <Example>
|
||||||
```
|
```
|
||||||
|
|
||||||
There are three learners:
|
There are three learners:
|
||||||
|
@ -75,7 +73,7 @@ stack data structure):
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
```
|
```
|
||||||
stack exec nominal-lstar -- NomLStar EqDFA "Fifo 2"
|
cabal run nominal-lstar NomLStar EqDFA "Fifo 2"
|
||||||
```
|
```
|
||||||
|
|
||||||
The program will output all the intermediate hypotheses. And will terminate
|
The program will output all the intermediate hypotheses. And will terminate
|
||||||
|
@ -95,7 +93,7 @@ We proved by hand that the learnt model did indeed accept the language.
|
||||||
|
|
||||||
Run the tool like so:
|
Run the tool like so:
|
||||||
```
|
```
|
||||||
stack exec nominal-lstar -- <Leaner>
|
cabal run nominal-lstar <Leaner>
|
||||||
```
|
```
|
||||||
(So similar to the above case, but without specifying the equivalence
|
(So similar to the above case, but without specifying the equivalence
|
||||||
checker and example.) The tool will ask you membership queries and
|
checker and example.) The tool will ask you membership queries and
|
||||||
|
@ -122,7 +120,7 @@ A: True
|
||||||
Q: [0]
|
Q: [0]
|
||||||
A: True
|
A: True
|
||||||
Automaton {states = {{([],True)}}, alphabet = {a₁ : for a₁ ∊ 𝔸}, delta = {({([],True)},a₁,{([],True)}) : for a₁ ∊ 𝔸}, initialStates = {{([],True)}}, finalStates = {{([],True)}}}
|
Automaton {states = {{([],True)}}, alphabet = {a₁ : for a₁ ∊ 𝔸}, delta = {({([],True)},a₁,{([],True)}) : for a₁ ∊ 𝔸}, initialStates = {{([],True)}}, finalStates = {{([],True)}}}
|
||||||
3. Equivalent?
|
3. Equivalent?
|
||||||
|
|
||||||
# Is the following automaton correct?
|
# Is the following automaton correct?
|
||||||
# Automaton {states = {{([],True)}}, alphabet = {a₁ : for a₁ ∊ 𝔸}, delta = {({([],True)},a₁,{([],True)}) : for a₁ ∊ 𝔸}, initialStates = {{([],True)}}, finalStates = {{([],True)}}}
|
# Automaton {states = {{([],True)}}, alphabet = {a₁ : for a₁ ∊ 𝔸}, delta = {({([],True)},a₁,{([],True)}) : for a₁ ∊ 𝔸}, initialStates = {{([],True)}}, finalStates = {{([],True)}}}
|
||||||
|
@ -142,11 +140,14 @@ A: True
|
||||||
Q: [1,0]
|
Q: [1,0]
|
||||||
A: False
|
A: False
|
||||||
Q: [1,0,1]
|
Q: [1,0,1]
|
||||||
A:
|
A:
|
||||||
```
|
```
|
||||||
|
|
||||||
# Changes since first release
|
# Changes since first release
|
||||||
|
|
||||||
|
The original version of the tool, presented at POPL, is commit e1b00e1 (from
|
||||||
|
2016). Since then, some new features are implemented:
|
||||||
|
|
||||||
* Better support for interactive communication.
|
* Better support for interactive communication.
|
||||||
* Optimisation: add only one row/column to fix closedness/consistency
|
* Optimisation: add only one row/column to fix closedness/consistency
|
||||||
* Simpler observation table
|
* Simpler observation table
|
||||||
|
|
99
test/Spec.hs
99
test/Spec.hs
|
@ -14,25 +14,38 @@ import Test.Tasty
|
||||||
import Test.Tasty.HUnit
|
import Test.Tasty.HUnit
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = defaultMain (testGroup "Unit tests" unitTests)
|
main = defaultMain (testGroup "Unit tests" [dfaLearning, mealyBasics])
|
||||||
where
|
where
|
||||||
unitTests =
|
dfaLearning =
|
||||||
[ testCase "Learning DFA DW1" $ learnAndCompare (runningExample atoms 1) @?= True
|
testGroup
|
||||||
, testCase "Learning DFA DW2" $ learnAndCompare (runningExample atoms 2) @?= True
|
"DFA Learning"
|
||||||
, testCase "Mealy deterministic echo" $ isTrue (mealyIsDeterministic echoMachine) @?= True
|
[ testCase "Learning DFA DW1" $ learnDFAAndCompare (runningExample atoms 1) @?= True
|
||||||
, testCase "Mealy deterministic memory True" $ isTrue (mealyIsDeterministic (memoryMachine True)) @?= True
|
, testCase "Learning DFA DW2" $ learnDFAAndCompare (runningExample atoms 2) @?= True
|
||||||
, testCase "Mealy deterministic memory False" $ isTrue (mealyIsDeterministic (memoryMachine False)) @?= True
|
]
|
||||||
, testCase "Mealy enabled echo" $ isTrue (mealyIsEnabled echoMachine) @?= True
|
mealyBasics =
|
||||||
, testCase "Mealy enabled memory True" $ isTrue (mealyIsEnabled (memoryMachine True)) @?= True
|
testGroup
|
||||||
, testCase "Mealy enabled memory False" $ isTrue (mealyIsEnabled (memoryMachine False)) @?= True
|
"Mealy Basics"
|
||||||
]
|
[ testCase "Mealy deterministic echo" $ isTrue (mealyIsDeterministic echoMachine) @?= True
|
||||||
|
, testCase "Mealy deterministic memory True" $ isTrue (mealyIsDeterministic (memoryMachine True)) @?= True
|
||||||
|
, testCase "Mealy deterministic memory False" $ isTrue (mealyIsDeterministic (memoryMachine False)) @?= True
|
||||||
|
, testCase "Mealy enabled echo" $ isTrue (mealyIsEnabled echoMachine) @?= True
|
||||||
|
, testCase "Mealy enabled memory True" $ isTrue (mealyIsEnabled (memoryMachine True)) @?= True
|
||||||
|
, testCase "Mealy enabled memory False" $ isTrue (mealyIsEnabled (memoryMachine False)) @?= True
|
||||||
|
, testCase "Mealy reachable echo" $ isTrue (mealyReachable echoMachine `eq` singleton ()) @?= True
|
||||||
|
, testCase "Mealy reachable memory True" $ isTrue (mealyReachable (memoryMachine True) `eq` (singleton Nothing `union` NLambda.map Just atoms)) @?= True
|
||||||
|
, testCase "Mealy reachable memory False" $ isTrue (mealyReachable (memoryMachine False) `eq` (singleton Nothing `union` NLambda.map Just atoms)) @?= True
|
||||||
|
]
|
||||||
|
|
||||||
learnAndCompare :: (Contextual i, Show i, Nominal i, Nominal q1) => Automaton q1 i -> Bool
|
learnDFAAndCompare :: (Contextual i, Show i, Nominal i, Nominal q1) => Automaton q1 i -> Bool
|
||||||
learnAndCompare target = equivalenceCheck target learnedModel
|
learnDFAAndCompare target = equivalenceCheck target learnedModel
|
||||||
where
|
where
|
||||||
learnedModel = learnAngluin (teacherWithTarget target)
|
learnedModel = learnAngluin (teacherWithTarget target)
|
||||||
equivalenceCheck m1 m2 = isTrue . isEmpty $ bisim m1 m2
|
equivalenceCheck m1 m2 = isTrue . isEmpty $ bisim m1 m2
|
||||||
|
|
||||||
|
{- *******************************************************************
|
||||||
|
Basic data structure for Nominal Mealy machines and basic functions to
|
||||||
|
check whether they are deterministic and input-enabled.
|
||||||
|
-}
|
||||||
data MealyMachine s i o = MealyMachine
|
data MealyMachine s i o = MealyMachine
|
||||||
{ mealyInitialState :: s
|
{ mealyInitialState :: s
|
||||||
, mealyStates :: Set s
|
, mealyStates :: Set s
|
||||||
|
@ -47,27 +60,51 @@ mealyIsDeterministic m = forAll (\s -> forAll (\i -> let ot = mapFilter ((\(s2,
|
||||||
mealyIsEnabled :: _ => MealyMachine s i o -> Formula
|
mealyIsEnabled :: _ => MealyMachine s i o -> Formula
|
||||||
mealyIsEnabled m = forAll (\s -> forAll (\i -> let ot = mapFilter ((\(s2, i2, o, t) -> maybeIf ((s, i) `eq` (s2, i2)) (o, t))) (mealyTransitions m) in isNotEmpty ot) (mealyInputs m)) (mealyStates m)
|
mealyIsEnabled m = forAll (\s -> forAll (\i -> let ot = mapFilter ((\(s2, i2, o, t) -> maybeIf ((s, i) `eq` (s2, i2)) (o, t))) (mealyTransitions m) in isNotEmpty ot) (mealyInputs m)) (mealyStates m)
|
||||||
|
|
||||||
echoMachine :: MealyMachine () Atom Atom
|
-- Computes all reachable states
|
||||||
echoMachine = MealyMachine
|
mealyReachable :: _ => MealyMachine s i o -> Set s
|
||||||
{ mealyInitialState = ()
|
mealyReachable m = go (singleton (mealyInitialState m)) empty
|
||||||
, mealyStates = singleton ()
|
where
|
||||||
, mealyInputs = atoms
|
go todo visited =
|
||||||
, mealyTransitions = NLambda.map (\a -> ((), a, a, ())) atoms
|
ite (isEmpty todo)
|
||||||
}
|
{- then -} visited
|
||||||
|
{- else -} (let v = visited `union` todo
|
||||||
|
s = successors todo
|
||||||
|
in go (s \\ v) v)
|
||||||
|
successors todo = NLambda.sum (pairsWith (\s i -> mapFilter ((\(s2, i2, _, t) -> maybeIf ((s, i) `eq` (s2, i2)) t)) (mealyTransitions m)) todo (mealyInputs m))
|
||||||
|
|
||||||
|
{- *******************************************************************
|
||||||
|
EXAMPLE MEALY MACHINES
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- Immediately echoes the input (of type Atom)
|
||||||
|
echoMachine :: MealyMachine () Atom Atom
|
||||||
|
echoMachine =
|
||||||
|
MealyMachine
|
||||||
|
{ mealyInitialState = ()
|
||||||
|
, mealyStates = singleton ()
|
||||||
|
, mealyInputs = atoms
|
||||||
|
, mealyTransitions = NLambda.map (\a -> ((), a, a, ())) atoms
|
||||||
|
}
|
||||||
|
|
||||||
|
-- The next example is a memory cell to store a single atom. You can Put
|
||||||
|
-- or Get the values. Depending on the mode, the machine can forget its
|
||||||
|
-- value after a Get operation.
|
||||||
data MIn a = MPut a | MGet
|
data MIn a = MPut a | MGet
|
||||||
deriving (Eq, Ord, Show, Generic, Nominal, Contextual)
|
deriving (Eq, Ord, Show, Generic, Nominal, Contextual)
|
||||||
|
|
||||||
data MOut a = MNok | MOk | MVal a
|
data MOut a = MNok | MOk | MVal a
|
||||||
deriving (Eq, Ord, Show, Generic, Nominal, Contextual)
|
deriving (Eq, Ord, Show, Generic, Nominal, Contextual)
|
||||||
|
|
||||||
memoryMachine :: Bool -> MealyMachine (Maybe Atom) (MIn Atom) (MOut Atom)
|
memoryMachine :: Bool -> MealyMachine (Maybe Atom) (MIn Atom) (MOut Atom)
|
||||||
memoryMachine forget = MealyMachine
|
memoryMachine forget =
|
||||||
{ mealyInitialState = Nothing
|
MealyMachine
|
||||||
, mealyStates = allStates
|
{ mealyInitialState = Nothing
|
||||||
, mealyInputs = singleton MGet `union` NLambda.map MPut atoms
|
, mealyStates = allStates
|
||||||
, mealyTransitions = NLambda.pairsWith (\q a -> (q, MPut a, MOk, Just a)) allStates atoms `union`
|
, mealyInputs = singleton MGet `union` NLambda.map MPut atoms
|
||||||
NLambda.map (\a -> (Just a, MGet, MVal a, if forget then Nothing else Just a)) atoms `union`
|
, mealyTransitions =
|
||||||
NLambda.singleton (Nothing, MGet, MNok, Nothing)
|
NLambda.pairsWith (\q a -> (q, MPut a, MOk, Just a)) allStates atoms
|
||||||
}
|
`union` NLambda.map (\a -> (Just a, MGet, MVal a, if forget then Nothing else Just a)) atoms
|
||||||
where allStates = singleton Nothing `union` (NLambda.map Just atoms)
|
`union` NLambda.singleton (Nothing, MGet, MNok, Nothing)
|
||||||
|
}
|
||||||
|
where
|
||||||
|
allStates = singleton Nothing `union` (NLambda.map Just atoms)
|
||||||
|
|
Loading…
Add table
Reference in a new issue