module Detection.GeneralizedReactivity
( GRFormula(..)
, Refusal
, detectGR
, checkGR
) where
import Simplify
( simplify
)
import Config
( Configuration(..)
, defaultCfg
)
import Data.Specification
( Specification(..)
)
import Data.Either
( partitionEithers
)
import Data.List
( sortBy
, partition
, sort
, find
)
import Data.Types
( Semantics(..)
)
import Data.Function
( on
)
import Control.Monad
( unless
)
import Utils
( strictSort
, bucketSort
)
import Data.LTL
( Atomic(..)
, Formula(..)
, fmlOutputs
, subFormulas
, isBooleanFormula
, isBooleanNextFormula
, simplePrint
, applySub
, fFinally
, fGlobally
, fNot
, fAnd
, fOr
)
import Data.Error
( Error
)
import Writer.Eval
( eval
)
import Writer.Utils
( merge
)
import Control.Exception
( assert
)
import qualified Data.Map.Strict as M
type Refusal = String
data GRFormula =
GRFormula
{ level :: Int
, initEnv :: [Formula]
, initSys :: [Formula]
, assertEnv :: [Formula]
, assertSys :: [Formula]
, liveness :: [([Formula],[Formula])]
} deriving (Show)
data TFml =
TTTrue
| TFFalse
| TAtomic Int
| TAnd [TFml]
| TOr [TFml]
deriving (Ord, Eq, Show)
checkGR
:: Specification -> Either Error Int
checkGR s = case detectGR defaultCfg s of
Right fml -> return $ level fml
Left x -> case x of
Left err -> Left err
Right _ -> return (1)
detectGR
:: Configuration -> Specification -> Either (Either Error Refusal) GRFormula
detectGR c s = do
let
c' = c {
simplifyStrong = False,
noDerived = False,
noWeak = True,
noFinally = False,
noGlobally = False,
pushGlobally = False,
pushFinally = False,
pushNext = True,
pullFinally = True,
pullGlobally = True,
pullNext = False,
noRelease = True,
owSemantics = case semantics s of
SemanticsStrictMoore -> Just SemanticsStrictMealy
SemanticsMoore -> Just SemanticsMealy
_ -> Nothing
}
strict = semantics s `elem` [SemanticsStrictMealy, SemanticsStrictMoore]
case eval' c' s of
Left x -> Left $ Left x
Right y -> case quickCheckGR strict y of
Just f -> return f
Nothing ->
let
fml = do
let c'' = c' {
negNormalForm = True,
simplifyWeak = True
}
(es,ss,rs,as,is,gs) <- eval c'' s
fml' <- merge es ss rs as is gs
simplify c'' $ noImplication $ noEquivalence fml'
in case fml of
Left x -> Left $ Left x
Right x -> case transformToGR strict x of
Left z -> Left $ Right z
Right z -> return z
where
eval' x y = do
(es,ss,rs,as,is,gs) <- eval x y
es' <- mapM (simplify x) es
ss' <- mapM (simplify x) ss
rs' <- mapM (simplify x) rs
as' <- mapM (simplify x) as
is' <- mapM (simplify x) is
gs' <- mapM (simplify x) gs
return (es',ss',rs',as',is',gs')
noImplication fml = case fml of
Implies x y -> let
x' = noImplication x
y' = noImplication y
in
Or [Not x', y']
_ ->
applySub noImplication fml
noEquivalence fml = case fml of
Equiv x y -> let
x' = noEquivalence x
y' = noEquivalence y
in
And [Implies x' y', Implies y' x']
_ ->
applySub noEquivalence fml
quickCheckGR
:: Bool -> ([Formula],[Formula],[Formula],[Formula],[Formula],[Formula])
-> Maybe GRFormula
quickCheckGR strict (es,ss,rs,as,is,gs) = do
mapM_ boolIn es
mapM_ boolFml ss
let
rs'' = case pullG $ fAnd $ concatMap pullG rs of
Globally (And xs) : ys -> xs ++ ys
Globally x : ys -> x : ys
zs -> zs
(rs',as') = case pullG $ fAnd $ concatMap pullG as of
Globally (And xs) : ys ->
let (ls,os) = partitionEithers $ map boolNextInE xs
in (rs'' ++ ls, map Globally os ++ ys)
Globally x : ys -> case boolNextIn x of
Just () -> (x:rs'',ys)
Nothing -> (rs'', Globally x : ys)
zs -> (rs'',zs)
case as' of
[] -> do
mapM_ boolNextIn rs'
let
is'' = case pullG $ fAnd $ concatMap pullG is of
Globally (And xs) : ys -> xs ++ ys
Globally x : ys -> x : ys
zs -> zs
(is',gs') = case pullG $ fAnd $ concatMap pullG gs of
Globally (And xs) : ys ->
let (ls,os) = partitionEithers $ map boolNextFmlE xs
in (is'' ++ ls, map Globally os ++ ys)
Globally x : ys -> case boolNextFml x of
Just () -> (x:is'',ys)
Nothing -> (is'', Globally x : ys)
zs -> (is'',zs)
mapM_ boolNextFml is'
case separateStandardLiveness $
noImplEquiv $ fAnd gs' of
Right (ls,[]) -> return
GRFormula
{ level = length ls
, initEnv = es
, initSys = ss
, assertEnv = rs'
, assertSys = is'
, liveness = ls
}
_ -> Nothing
_ -> do
unless strict Nothing
mapM_ boolNextIn rs'
let is' = case pullG $ fAnd $ concatMap pullG is of
Globally (And xs) : ys -> xs ++ ys
Globally x : ys -> x : ys
zs -> zs
mapM_ boolNextFml is'
case separateStandardLiveness $
noImplEquiv $ Implies (fAnd as') (fAnd gs) of
Right ([x],[]) -> return
GRFormula
{ level = 1
, initEnv = es
, initSys = ss
, assertEnv = rs'
, assertSys = is'
, liveness = [x]
}
_ -> Nothing
where
boolIn fml = case fml of
TTrue -> return ()
FFalse -> return ()
Atomic (Input _) -> return ()
Not x -> boolIn x
And xs -> mapM_ boolIn xs
Or xs -> mapM_ boolIn xs
Implies x y -> mapM_ boolIn [x,y]
Equiv x y -> mapM_ boolIn [x,y]
_ -> Nothing
boolFml fml = case fml of
TTrue -> return ()
FFalse -> return ()
Atomic _ -> return ()
Not x -> boolFml x
And xs -> mapM_ boolFml xs
Or xs -> mapM_ boolFml xs
Implies x y -> mapM_ boolFml [x,y]
Equiv x y -> mapM_ boolFml [x,y]
_ -> Nothing
boolNextIn fml = case fml of
TTrue -> return ()
FFalse -> return ()
Atomic _ -> return ()
Not x -> boolNextIn x
And xs -> mapM_ boolNextIn xs
Or xs -> mapM_ boolNextIn xs
Implies x y -> mapM_ boolNextIn [x,y]
Equiv x y -> mapM_ boolNextIn [x,y]
Next x -> boolIn x
_ -> Nothing
boolNextInE fml = case boolNextIn fml of
Just () -> Left fml
Nothing -> Right fml
boolNextFml fml = case fml of
TTrue -> return ()
FFalse -> return ()
Atomic _ -> return ()
Not x -> boolNextFml x
And xs -> mapM_ boolNextFml xs
Or xs -> mapM_ boolNextFml xs
Implies x y -> mapM_ boolNextFml [x,y]
Equiv x y -> mapM_ boolNextFml [x,y]
Next x -> boolFml x
_ -> Nothing
noImplEquiv fml = case fml of
TTrue -> fml
FFalse -> fml
Atomic _ -> fml
And xs -> fAnd $ map noImplEquiv xs
Or xs -> fOr $ map noImplEquiv xs
Implies x y -> fOr [fNot (noImplEquiv x), noImplEquiv y]
Equiv x y ->
let x' = noImplEquiv x
y' = noImplEquiv y
in fOr [fAnd [x',y'], fAnd [fNot x', fNot y']]
_ -> fml
boolNextFmlE fml = case boolNextFml fml of
Just () -> Left fml
Nothing -> Right fml
pullG f = case f of
And xs -> let
ys = concatMap pullG xs
(zs,os) = partitionEithers $ map isG ys
in
Globally (And zs) : os
_ -> [f]
isG f = case f of
Globally x -> case isG x of
Left y -> Left y
Right y -> Left y
_ -> Right f
transformToGR
:: Bool -> Formula -> Either Refusal GRFormula
transformToGR strict fml = do
noUntil fml
noDirectNext fml
let xs = firstLevelCNF $ pullTogether fml
(is,ps,ys) <- separateInitials xs
(fs,fr) <- separateRequirements is $ fAnd $ map fOr ys
(gs,ls,rs) <-
if strict
then case separateStandard fr of
Right (a,b,[]) -> return (a,b,[])
_ -> separateStrict fr
else separateStandard fr
case rs of
[] -> return
GRFormula
{ level = length ls
, initEnv = is
, initSys = ps
, assertEnv = fs
, assertSys = gs
, liveness = ls
}
_ -> errIncompatible $ map fOr rs
separateStrict
:: Formula
-> Either Refusal ([Formula],[([Formula],[Formula])],[[Formula]])
separateStrict fml = let
xs = firstLevelDNF $ pullTogether fml
ys = map (pullG . fAnd) xs
(c1,c2,c3,xr) = foldl classify ([],[],[],[]) ys
in do
(gs,ff) <- case c3 of
[] -> case c2 of
[] -> return ([],fOr (map (Finally . Globally) c1 ++ map fAnd xr))
_ -> errIncompatible $
map (\(x,y) -> fAnd [Finally (Globally x), Globally y]) c2
[x] -> let
cs = firstLevelCNF $ fOr $ pullF x
(fs,zs) = partitionEithers $ map isFL cs
in if null xr then
case find ((sort zs /=) . sort . firstLevelCNF . snd) c2 of
Nothing ->
return (map fOr zs,
fOr (map (Finally . Globally) (c1 ++ map fst c2) ++
map (Globally . Finally) fs))
Just r -> errIncompatible [
fAnd $ map fOr zs,
fAnd $ map fOr $ firstLevelCNF $ snd r
]
else
errIncompatible $ map fOr xr
_ -> assert False undefined
(ls,rs) <- separateStandardLiveness ff
return (gs,ls,rs)
where
pullG f = case f of
And xs -> let
ys = concatMap pullG xs
(gs,rs) = partitionEithers $ map isG ys
in
Globally (And gs) : rs
_ -> [f]
isG f = case f of
Globally x -> Left x
_ -> Right f
pullF f = case f of
Or xs -> let
ys = concatMap pullF xs
(fs,rs) = partitionEithers $ map isF ys
in
Finally (Or fs) : rs
_ -> [f]
isF f = case f of
Finally x -> Left x
_ -> Right f
isFL fs = case fs of
[Finally x] -> Left x
_ -> Right fs
classify (a,b,c,d) xs = case xs of
[Finally (Globally x)] -> (x:a,b,c,d)
[Finally (Globally x), Globally y] -> (a,(x,y):b,c,d)
[Globally y, Finally (Globally x)] -> (a,(x,y):b,c,d)
[Globally x] -> (a,b,x:c,d)
_ -> (a,b,c,xs:d)
separateStandard
:: Formula
-> Either Refusal ([Formula],[([Formula],[Formula])],[[Formula]])
separateStandard fml = do
let xs = firstLevelCNF $ pullTogether fml
(cG,ys) <- separateGlobally xs
let (gs,lg) = partition isBooleanNextFormula cG
(ls,rs) <- separateStandardLiveness $ fAnd $ map fOr $
ys ++ map ((: []) . fGlobally) lg
return (gs,ls,rs)
noUntil
:: Formula -> Either Refusal ()
noUntil fml = case fml of
TTrue -> return ()
FFalse -> return ()
Atomic {} -> return ()
Until {} -> Left $
"Generalized Reactivity does not allow until sub-formulas:\n"
++ " " ++ simplePrint fml
Release {} -> assert False undefined
Weak {} -> assert False undefined
_ -> mapM_ noUntil $ subFormulas fml
noDirectNext
:: Formula -> Either Refusal ()
noDirectNext fml = case fml of
TTrue -> return ()
FFalse -> return ()
Atomic {} -> return ()
Globally {} -> return ()
Finally {} -> return ()
Next {} -> Left $
"GeneralizedReactivity does not allow next sub-formulas on the initial level:\n"
++ " " ++ simplePrint fml
_ -> mapM_ noDirectNext $ subFormulas fml
separateBoolean
:: [[Formula]] -> Either Refusal ([[Formula]],[[Formula]])
separateBoolean = return . partition (all isBooleanFormula)
separateGlobally
:: [[Formula]] -> Either Refusal ([Formula],[[Formula]])
separateGlobally xs = do
let (a,b) = partitionEithers $ map separateFormula xs
return (concat a, b)
where
separateFormula ys = case ys of
[Globally (And zs)] -> Left zs
[Globally x] -> Left [x]
_ -> Right ys
separateFinally
:: [[Formula]] -> Either Refusal ([Formula],[[Formula]])
separateFinally xs = do
let (a,b) = partitionEithers $ map separateFml xs
return (concat a, b)
where
separateFml ys = case ys of
[Finally (Or zs)] -> Left zs
[Finally x] -> Left [x]
_ -> Right ys
separateInitials
:: [[Formula]] -> Either Refusal ([Formula],[Formula],[[Formula]])
separateInitials xs = do
(bs,rs) <- separateBoolean xs
let ys = firstLevelDNF $ pullTogether $ fAnd $ map fOr bs
let (is,ps) = partitionEithers $ map pureInputFml ys
return (map fNot is, ps, rs)
where
pureInputFml ys =
if null $ fmlOutputs $ fOr ys
then Left $ fAnd ys
else Right $ fAnd ys
separateRequirements
:: [Formula] -> Formula
-> Either Refusal ([Formula], Formula)
separateRequirements is fml = do
let ys = firstLevelDNF $ pullTogether fml
(bs,ns) <- separateBoolean ys
unless (map (fNot . fAnd) bs == is) $ Left $
"The initial constraints cannot be refined to fit into the "
++ "Generalized Reactivity format."
(cF,fr) <- separateFinally ns
let (fs,lf) = partition isBooleanNextFormula cF
mapM_ checkInputsUnderNext fs
return (map fNot fs, fOr $ map fAnd fr ++ map fFinally lf)
where
checkInputsUnderNext f = case f of
Next x ->
unless (null $ fmlOutputs x) $ Left $
"GeneralizedReactivity does not allow to constraint "
++ "outputs under a transition target of "
++ "the environment:\n"
++ " " ++ simplePrint f
_ -> mapM_ checkInputsUnderNext $ subFormulas f
separateStandardLiveness
:: Formula -> Either Refusal ([([Formula],[Formula])],[[Formula]])
separateStandardLiveness fml =
let
ys = firstLevelCNF $ pullTogether fml
(zs,rs) = partitionEithers $ map classify ys
zs' = sortBy (compare `on` fst) zs
ls = foldl join [] zs'
in
return (ls, rs)
where
join [] (fs,gs) = [(fs,gs)]
join ((fs,gs):rs) (fs',gs')
| fs == fs' = (fs,gs ++ gs'):rs
| otherwise = (fs',gs'):(fs,gs):rs
classify ys
| not (all isFGB ys) = Right ys
| otherwise =
let (a,b) = partitionEithers $ map sepFG ys
in Left (strictSort a, strictSort b)
isFGB formula = case formula of
Finally (Globally f) -> isBooleanFormula f
Globally (Finally f) -> isBooleanFormula f
_ -> False
sepFG formula = case formula of
Finally (Globally f) -> Left $ fNot f
Globally (Finally f) -> Right f
_ -> assert False undefined
pullTogether
:: Formula -> Formula
pullTogether formula = case formula of
Globally (And ys) -> fAnd $ map (pullTogether . fGlobally) ys
Globally f -> fGlobally $ pullUp f
Finally (Or ys) -> fOr $ map (pullTogether . fFinally) ys
Finally f -> fFinally $ pullUp f
_ -> applySub pullTogether formula
where
pullUp fml = case fml of
And [] -> TTrue
And [y] -> pullUp y
And ys ->
let zs = map pullUp ys
in if all isGlobally zs
then fGlobally $ fAnd $ map rmG zs
else fAnd zs
Or [] -> FFalse
Or [y] -> pullUp y
Or ys ->
let zs = map pullUp ys
in if all isFinally zs
then fFinally $ fOr $ map rmF zs
else fOr zs
_ -> applySub pullUp fml
isGlobally fml = case fml of
Globally _ -> True
_ -> False
isFinally fml = case fml of
Finally _ -> True
_ -> False
rmG fml = case fml of
Globally f -> f
_ -> fml
rmF fml = case fml of
Finally f -> f
_ -> fml
firstLevelDNF
:: Formula -> [[Formula]]
firstLevelDNF = firstLevelCNF . swapAndOr
where
swapAndOr f = case f of
And xs -> Or $ map swapAndOr xs
Or xs -> And $ map swapAndOr xs
_ -> f
firstLevelCNF
:: Formula -> [[Formula]]
firstLevelCNF formula =
let
ys = levelOneAtoms [] formula
(mm,bb,_) = foldl f (M.empty, M.empty, 0 :: Int) ys
f (m,b,i) x = case M.lookup x m of
Just _ -> (m,b,i)
Nothing -> case M.lookup (fNot x) m of
Just i' -> (M.insert x (i'+1) m,M.insert (i'+1) x b, i)
Nothing -> (M.insert x (2*i) m, M.insert (2*i) x b, i+2)
fml' = replaceLevelOneAtoms mm formula
in
map (map (bb M.!)) $ fCNF fml'
where
replaceLevelOneAtoms mm fml = case fml of
TTrue -> TTTrue
FFalse -> TFFalse
Atomic {} -> TAtomic $ mm M.! fml
Not (Atomic {}) -> TAtomic $ mm M.! fml
Next {} -> TAtomic $ mm M.! fml
Finally {} -> TAtomic $ mm M.! fml
Globally {} -> TAtomic $ mm M.! fml
And xs -> TAnd $ map (replaceLevelOneAtoms mm) xs
Or xs -> TOr $ map (replaceLevelOneAtoms mm) xs
_ -> assert False undefined
levelOneAtoms a fml = case fml of
TTrue -> a
FFalse -> a
Atomic {} -> fml : a
Not (Atomic {}) -> fml : a
Next {} -> fml : a
Finally {} -> fml : a
Globally {} -> fml : a
And xs -> foldl levelOneAtoms a xs
Or xs -> foldl levelOneAtoms a xs
_ -> assert False undefined
fCNF fml =
strictSort $ map strictSort $
case alreadyCNF $ warp fml of
Left (us,ps) -> ps ++ concatMap toCNF us
Right x -> x
toCNF (us,ds) = swap $ map (map (ds ++) . fCNF) us
swap (x:xr) = foldl joinFml x xr
swap [] = assert False undefined
joinFml b = simpleFml . concatMap (\zs -> map (zs ++) b)
simpleFml = filterSupSets . map filternegations
filternegations = filternegations' [] . bucketSort
filternegations' a xs = case xs of
(x : y : xr)
| x + 1 == y -> []
| otherwise -> filternegations' (x : a) (y : xr)
(x : xr) -> filternegations' (x : a) xr
[] -> reverse a
alreadyCNF fml = case fml of
TTTrue -> Right []
TFFalse -> Right [[]]
TAtomic x -> Right [[x]]
TAnd xs -> case partitionEithers $ map pureOr xs of
([],zs) -> Right zs
(ys,zs) -> Left (ys,zs)
TOr {} -> alreadyCNF $ TAnd [fml]
pureOr fml = case fml of
TOr xs -> case partitionEithers $ map pure xs of
([],zs) -> Right zs
(ys,zs) -> Left (ys,zs)
TAtomic x -> Right [x]
_ -> assert False undefined
pure fml = case fml of
TAnd {} -> Left fml
TAtomic x -> Right x
_ -> assert False undefined
warp fml = case fml of
TAnd [] -> TTTrue
TAnd [x] -> warp x
TAnd xs
| TFFalse `elem` xs -> TFFalse
| otherwise -> case filter (/= TTTrue) xs of
[] -> TTTrue
[x] -> warp x
ys -> TAnd $ warpAnd $ map warp ys
TOr [] -> TFFalse
TOr [x] -> warp x
TOr xs
| TTTrue `elem` xs -> TTTrue
| otherwise -> case filter (/= TFFalse) xs of
[] -> TFFalse
[x] -> warp x
ys -> TOr $ warpOr $ map warp ys
_ -> fml
warpAnd = concatMap wAnd
wAnd fml = case fml of
TAnd x -> x
_ -> [fml]
warpOr = concatMap wOr
wOr fml = case fml of
TOr x -> x
_ -> [fml]
filterSupSets
:: [[Int]] -> [[Int]]
filterSupSets xs =
let ys = sortBy (compare `on` length) xs
in filtersets [] ys
where
filtersets a [] = a
filtersets a ([]:xr) =
filtersets a xr
filtersets a (x:xr) =
filtersets (x:a) (rmsup x [] xr)
rmsup _ a [] = reverse a
rmsup x a (y:yr)
| subset x y = rmsup x a yr
| otherwise = rmsup x (y:a) yr
subset x y = null $ foldl eatup x y
eatup [] _ = []
eatup (x:xr) y
| x == y = xr
| otherwise = x:xr
errIncompatible
:: [Formula] -> Either Refusal a
errIncompatible xs =
Left $ "The following sub-formulas cannot be refined "
++ "to fit the GeneralizedReactivity requirements:"
++ concatMap (\x -> "\n * " ++ simplePrint x) xs