-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement syntactic transitive closure routine
- Loading branch information
Showing
4 changed files
with
163 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
{-# LANGUAGE PatternSynonyms #-} | ||
|
||
{- | | ||
Copyright : (c) Runtime Verification, 2024 | ||
License : BSD-3-Clause | ||
-} | ||
module Booster.Pattern.PartialOrder (transitiveClosure) where | ||
|
||
import Control.Monad (guard) | ||
import Data.Maybe (mapMaybe) | ||
import Data.Set (Set) | ||
import Data.Set qualified as Set | ||
|
||
import Booster.Pattern.Base | ||
|
||
{- | Given a set of predicates @ps@, construct a syntactic transitive closure of relative to the symbol @sym@. | ||
This function only returns new predicates (if any). | ||
If the @ps@ contains application of symbols other than @sym@, they are ignored. | ||
-} | ||
transitiveClosure :: Symbol -> Set Predicate -> Set Predicate | ||
transitiveClosure sym ps = | ||
let attempts = map (\target -> Attempt target (Set.toList (Set.delete target ps))) . Set.toList $ ps | ||
in (Set.fromList . concatMap (makeTransitiveStep sym) $ attempts) | ||
|
||
-- | An @Attempt@ is a target clause and a list of assumption clauses | ||
data Attempt = Attempt | ||
{ target :: !Predicate | ||
-- ^ target predicate to eliminate, contains an existential variable | ||
, assumptions :: ![Predicate] | ||
-- ^ predicates that are assume "known", must contain the same existential that the target | ||
} | ||
|
||
{- | Validate a predicate clause. Checks: | ||
* the clause is a symbol application of @sym@ | ||
* the variables have distinct names, i.e. @sym@ is irreflexive | ||
-} | ||
validateClause :: Symbol -> Predicate -> Bool | ||
validateClause sym (Predicate p) = case p of | ||
(SymbolApplication clauseSym _ [(Var Variable{variableName = a}), (Var Variable{variableName = b})]) -> clauseSym == sym && a /= b | ||
_ -> False | ||
|
||
-- | Try solving, return an instantiated target if successful | ||
makeTransitiveStep :: Symbol -> Attempt -> [Predicate] | ||
makeTransitiveStep sym Attempt{target, assumptions} = do | ||
guard (all (validateClause sym) (target : assumptions)) | ||
mapMaybe (matchAndTransit sym target) assumptions | ||
|
||
{- | Try to make strengthen the @left@ predicate using the @right@ predicate, | ||
assuming both are the same *transitive* symbol | ||
-} | ||
matchAndTransit :: Symbol -> Predicate -> Predicate -> Maybe Predicate | ||
matchAndTransit sym (Predicate current) (Predicate next) = | ||
case (current, next) of | ||
(SymbolApplication symbol1 sorts [a, b], SymbolApplication symbol2 _ [c, d]) -> do | ||
guard (symbol1 == symbol2 && b == c) | ||
let newClause = Predicate $ SymbolApplication symbol1 sorts [a, d] | ||
guard (validateClause sym newClause) | ||
pure newClause | ||
_ -> Nothing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
module Test.Booster.Pattern.PartialOrder ( | ||
test_transitiveClosure, | ||
) where | ||
|
||
import Data.Set qualified as Set | ||
import Test.Tasty | ||
import Test.Tasty.HUnit | ||
|
||
import Booster.Pattern.Base | ||
import Booster.Pattern.Bool | ||
import Booster.Pattern.PartialOrder | ||
|
||
test_transitiveClosure :: TestTree | ||
test_transitiveClosure = | ||
testGroup | ||
"<Int --- strict partial order" | ||
[ test | ||
"No new items" | ||
ltIntSymbol | ||
( [ LtInt (var "A") (var "B") | ||
, LtInt (var "C") (var "D") | ||
] | ||
) | ||
( [] | ||
) | ||
, test | ||
"One new item" | ||
ltIntSymbol | ||
( [ LtInt (var "A") (var "B") | ||
, LtInt (var "B") (var "C") | ||
] | ||
) | ||
( [ LtInt (var "A") (var "C") | ||
] | ||
) | ||
, test | ||
"Two new items" | ||
ltIntSymbol | ||
( [ LtInt (var "A") (var "B") | ||
, LtInt (var "B") (var "C") | ||
, LtInt (var "B") (var "D") | ||
] | ||
) | ||
( [ LtInt (var "A") (var "C") | ||
, LtInt (var "A") (var "D") | ||
] | ||
) | ||
, test | ||
"Cycle, no new items" | ||
ltIntSymbol | ||
( [ LtInt (var "A") (var "B") | ||
, LtInt (var "B") (var "A") | ||
] | ||
) | ||
( [] | ||
) | ||
] | ||
|
||
---------------------------------------- | ||
-- Test fixture | ||
test :: String -> Symbol -> [Term] -> [Term] -> TestTree | ||
test name sym input expected = | ||
testCase name $ | ||
transitiveClosure sym (Set.fromList $ map Predicate input) | ||
@?= (Set.fromList $ map Predicate expected) | ||
|
||
ltIntSymbol :: Symbol | ||
ltIntSymbol = | ||
case LtInt (var "DUMMY") (var "DUMMY") of | ||
SymbolApplication ltInt _ _ -> ltInt | ||
_ -> error "Impossible" | ||
|
||
someSort :: Sort | ||
someSort = SortApp "SomeSort" [] | ||
|
||
var :: VarName -> Term | ||
var variableName = Var $ Variable{variableSort = someSort, variableName} |