haal-0.3.0.0: src/Haal/EquivalenceOracle/WpMethod.hs
{-# LANGUAGE ScopedTypeVariables #-}
-- | This module implements the WpMethod.
module Haal.EquivalenceOracle.WpMethod (
WpMethod,
WpMethodConfig (..),
RandomWpMethod,
RandomWpMethodConfig (..),
wpmethodSuiteSize,
mkWpMethod,
mkRandomWpMethod,
) where
import Control.Monad (replicateM)
import Control.Monad.Identity (Identity (runIdentity))
import Control.Monad.State (MonadState (state), State, runState)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Haal.BlackBox
import Haal.Experiment
import System.Random (Random (randomR), StdGen)
-- | The 'WpMethodConfig' type is used to configure the Wp-method equivalence oracle.
data WpMethodConfig = WpMethodConfig
{ wpmDepth :: Int
-- ^ The number of extra states beyond the hypothesis to account for.
}
deriving (Show, Eq)
-- | The 'WpMethod' type represents the Wp-method equivalence oracle.
newtype WpMethod = WpMethod WpMethodConfig deriving (Eq, Show)
-- | Constructor for a 'WpMethod' value.
mkWpMethod :: WpMethodConfig -> WpMethod
mkWpMethod = WpMethod
-- | The 'wpmethodSuiteSize' returns the number of test cases in the test suite of WpMethod.
wpmethodSuiteSize ::
( Automaton aut s
, FiniteOrd i
, FiniteOrd s
, Eq o
) =>
WpMethod ->
aut s i o ->
Int
wpmethodSuiteSize (WpMethod (WpMethodConfig d)) aut = firstPhaseSize + secondPhaseSize
where
alphabetList = Set.toList (inputs aut)
alphabetSize = length alphabetList
stateCover = accessSequences aut
localSufSizes =
Map.fromAscList
[ (st, Set.size (localCharacterizingSet aut st))
| st <- Set.toAscList (states aut)
]
globalSufSize = Set.size (globalCharacterizingSet aut)
transitionCover =
Set.fromList
[ acc ++ [a]
| acc <- Map.elems stateCover
, a <- alphabetList
]
difference = Set.fromList (Map.elems stateCover) `Set.difference` transitionCover
-- Closed form: |S| * |W| * (1 + |Σ| + ... + |Σ|^d)
firstPhaseSize =
Map.size stateCover
* globalSufSize
* sum [alphabetSize ^ k | k <- [0 .. d]]
-- Walk each acc once, then enumerate middles from that state; no test strings built.
secondPhaseSize =
sum
[ localSufSizes Map.! current (fst (runIdentity (walk afterAcc middle)))
| acc <- Set.toList difference
, let afterAcc = fst (runIdentity (walk aut acc))
, fixed <- [0 .. d]
, middle <- replicateM fixed alphabetList
]
-- | Returns the test suite for the WpMethod.
wpmethodSuite ::
forall aut i o s.
( Automaton aut s
, FiniteOrd i
, FiniteOrd s
, Eq o
) =>
WpMethod ->
aut s i o ->
(WpMethod, [[i]])
wpmethodSuite wpm@(WpMethod (WpMethodConfig d)) aut = (wpm, suite)
where
alphabet = inputs aut
stateCover = accessSequences aut
localSuf =
Map.fromAscList
[ (st, localCharacterizingSet aut st) | st <- Set.toAscList $ states aut
]
globalSuf = globalCharacterizingSet aut
transitionCover =
[ acc ++ [a]
| acc <- Map.elems stateCover
, a <- Set.toList alphabet
]
difference =
Set.fromList (Map.elems stateCover)
`Set.difference` Set.fromList transitionCover
firstPhase =
concat
[ [ acc ++ middle ++ suf
| acc <- Map.elems stateCover
, suf <- Set.toList globalSuf
]
| fixed <- [0 .. d]
, middle <- replicateM fixed $ Set.toList alphabet
]
secondPhase =
concat
[ [ acc ++ middle ++ suf
| acc <- Set.toList difference
, suf <- Set.toList $ localSuf Map.! current (fst (runIdentity (walk aut (acc ++ middle))))
]
| fixed <- [0 .. d]
, middle <- replicateM fixed $ Set.toList alphabet
]
suite = firstPhase ++ secondPhase
{- | The 'RandomWpMethodConfig' is a record data type that represents the configuration for an instance
of the Random WpMethod algorithm.
-}
data RandomWpMethodConfig = RandomWpMethodConfig
{ rwpGen :: StdGen
-- ^ Random generator.
, rwpExpected :: Int
-- ^ Expected depth of random walk.
, rwpMin :: Int
-- ^ Minimum depth of random walk.
, rwpLimit :: Int
-- ^ Maximum number of queries.
}
deriving (Show, Eq)
-- | The 'RandomWpMethod' type is just a wrapper around the config.
newtype RandomWpMethod = RandomWpMethod RandomWpMethodConfig deriving (Show, Eq)
-- | Constructor for a 'RandomWpMethod' value.
mkRandomWpMethod :: RandomWpMethodConfig -> RandomWpMethod
mkRandomWpMethod = RandomWpMethod
-- | Return the 'RandomWpMethod' test suite.
randomWpMethodSuite ::
forall aut i o s.
( Automaton aut s
, FiniteOrd i
, FiniteOrd s
, Eq o
) =>
RandomWpMethod ->
aut s i o ->
(RandomWpMethod, [[i]])
randomWpMethodSuite
( RandomWpMethod
conf@RandomWpMethodConfig
{ rwpGen = g
, rwpExpected = e
, rwpMin = mi
, rwpLimit = lim
}
)
aut = (RandomWpMethod (conf{rwpGen = genfinal}), suite)
where
alphabet = inputs aut
prefixes = accessSequences aut
localSuf =
Map.fromAscList
[ (st, localCharacterizingSet aut st) | st <- Set.toAscList $ states aut
]
globalSuf = globalCharacterizingSet aut
(suite, genfinal) = runState (replicateM lim genTestCase) g
genTestCase :: State StdGen [i]
genTestCase = do
prefixIdx <- state $ randomR (0, Map.size prefixes - 1)
let (_, prefix) = prefixIdx `Map.elemAt` prefixes
middle <- genExpectedLength
local <- state $ randomR (False, True)
if local
then do
let curr = current $ fst (runIdentity (walk aut (prefix ++ middle)))
suffixSet = localSuf Map.! curr
suffixIdx <- state $ randomR (0, Set.size suffixSet - 1)
let suffix = suffixIdx `Set.elemAt` suffixSet
return $ prefix ++ middle ++ suffix
else do
globalIdx <- state $ randomR (0, Set.size globalSuf - 1)
let suffix = globalIdx `Set.elemAt` globalSuf
return $ prefix ++ middle ++ suffix
genExpectedLength :: State StdGen [i]
genExpectedLength = state $ go [] mi
where
go :: [i] -> Int -> StdGen -> ([i], StdGen)
go acc minim gen =
let (continue, gen') = randomR (0.0 :: Double, 1.0) gen
in if minim > 0 || continue > 1 / (fromIntegral e + 1)
then
let (idx, gen'') = randomR (0, Set.size alphabet - 1) gen'
nextChar = idx `Set.elemAt` alphabet
in go (nextChar : acc) (minim - 1) gen''
else (acc, gen)
instance EquivalenceOracle WpMethod where
testSuite = wpmethodSuite
instance EquivalenceOracle RandomWpMethod where
testSuite = randomWpMethodSuite