-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce the
Repeat
Apalache operator (#2927)
* Added the Repeat operator * integration tests + rule fix * Typo caught by @thpani * fmt fix * PR comments * fmt-fix --------- Co-authored-by: Igor Konnov <igor@konnov.phd>
- Loading branch information
Showing
20 changed files
with
349 additions
and
4 deletions.
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
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,66 @@ | ||
------------------------ MODULE Repeat ------------------------------------ | ||
EXTENDS Apalache, Integers | ||
|
||
Inv1 == | ||
LET Op(a, i) == a + 1 | ||
IN Repeat(Op, 5, 0) = 5 | ||
|
||
Inv2 == | ||
LET Op(a, i) == a + i | ||
IN Repeat(Op, 5, 0) = 15 | ||
|
||
\* Cyclical Op: \E k: Op^k = Id | ||
Inv3 == | ||
LET Op(a,i) == (a + i) % 3 | ||
IN LET | ||
v == 1 | ||
x0 == Repeat(Op, 0, v) | ||
x3 == Repeat(Op, 3, v) | ||
x6 == Repeat(Op, 6, v) | ||
IN | ||
/\ v = x0 | ||
/\ x0 = x3 | ||
/\ x3 = x6 | ||
|
||
\* Idempotent Op: Op^2 = Op | ||
Inv4 == | ||
LET | ||
\* @type: (Set(Set(Int)), Int) => Set(Set(Int)); | ||
Op(a, i) == {UNION a} | ||
IN LET | ||
v == {{1}, {2}, {3,4}} | ||
x1 == Repeat(Op, 1, v) | ||
x2 == Repeat(Op, 2, v) | ||
x3 == Repeat(Op, 3, v) | ||
IN | ||
/\ v /= x1 | ||
/\ x1 = x2 | ||
/\ x2 = x3 | ||
|
||
\* Nilpotent Op: \E k: Op^k = 0 | ||
Inv5 == | ||
LET | ||
\* @type: (Set(Int), Int) => Set(Int); | ||
Op(a, i) == a \ { x \in a: \A y \in a: x <= y } | ||
IN LET | ||
v == {1,2,3} | ||
x1 == Repeat(Op, 2, v) | ||
x2 == Repeat(Op, 3, v) | ||
x3 == Repeat(Op, 4, v) | ||
IN | ||
/\ x1 /= x2 | ||
/\ x2 = x3 | ||
/\ x3 = {} | ||
|
||
|
||
Init == TRUE | ||
Next == TRUE | ||
|
||
Inv == | ||
/\ Inv1 | ||
/\ Inv2 | ||
/\ Inv3 | ||
/\ Inv4 | ||
/\ Inv5 | ||
|
||
=============================================================================== |
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,11 @@ | ||
------------------------ MODULE RepeatBad ------------------------------------ | ||
EXTENDS Apalache, Integers | ||
|
||
Inv == | ||
LET Op(a, i) == a + 1 | ||
\* The 2nd argument to Repeat must be an integer literal | ||
IN \E x \in {5} : Repeat(Op, x, 0) = 5 | ||
|
||
Init == TRUE | ||
Next == TRUE | ||
=============================================================================== |
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
83 changes: 83 additions & 0 deletions
83
tla-bmcmt/src/main/scala/at/forsyte/apalache/tla/bmcmt/rules/RepeatRule.scala
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,83 @@ | ||
package at.forsyte.apalache.tla.bmcmt.rules | ||
|
||
import at.forsyte.apalache.tla.bmcmt._ | ||
import at.forsyte.apalache.tla.lir._ | ||
import at.forsyte.apalache.tla.lir.oper.ApalacheOper | ||
import at.forsyte.apalache.tla.lir.transformations.impl.IdleTracker | ||
import at.forsyte.apalache.tla.lir.transformations.standard.IncrementalRenaming | ||
import at.forsyte.apalache.tla.lir.values.TlaInt | ||
import at.forsyte.apalache.tla.pp.Inliner | ||
import at.forsyte.apalache.tla.types.tla | ||
|
||
/** | ||
* Rewriting rule for Repeat. This rule is similar to [[FoldSeqRule]]. | ||
* | ||
* This rule is more efficient than the fold- rules, because it does not require an underlying data structure (Seq or | ||
* Set). In particular, folding over 1..N, despite the overapproximation being tight by construction, still introduces | ||
* O(N*N) constraints, since the SMT solver must assert element uniqueness (i /= j for all i,j \in 1..N). OTOH, | ||
* RepeatRule consumes 1..N in the canonical order natively as a 1.to(N) in Scala. | ||
* | ||
* @author | ||
* Jure Kukovec | ||
*/ | ||
class RepeatRule(rewriter: SymbStateRewriter, renaming: IncrementalRenaming) extends RewritingRule { | ||
|
||
override def isApplicable(symbState: SymbState): Boolean = { | ||
symbState.ex match { | ||
case OperEx(ApalacheOper.repeat, LetInEx(NameEx(appName), TlaOperDecl(operName, params, _)), _, _) => | ||
appName == operName && params.size == 2 | ||
case _ => false | ||
} | ||
} | ||
|
||
override def apply(state: SymbState): SymbState = state.ex match { | ||
// assume isApplicable | ||
case ex @ OperEx(ApalacheOper.repeat, LetInEx(NameEx(_), opDecl), boundEx, baseEx) => | ||
boundEx match { | ||
case ValEx(TlaInt(n)) if n.isValidInt && n.toInt >= 0 => | ||
// rewrite baseEx to its final cell form | ||
val baseState = rewriter.rewriteUntilDone(state.setRex(baseEx)) | ||
|
||
// We need the type signature. The signature of Repeat is | ||
// \A a : ((a,Int) => a, Int, a) => a | ||
// so the operator type must be (a,Int) => a | ||
val a = TlaType1.fromTypeTag(baseEx.typeTag) | ||
val opT = OperT1(Seq(a, IntT1), a) | ||
// sanity check | ||
TlaType1.fromTypeTag(opDecl.typeTag) match { | ||
case `opT` => // all good | ||
case badType => | ||
val msg = s"FoldSet argument ${opDecl.name} should have the tag $opT, found $badType." | ||
throw new TypingException(msg, opDecl.ID) | ||
} | ||
|
||
// expressions are transient, we don't need tracking | ||
val inliner = new Inliner(new IdleTracker, renaming) | ||
// We can make the scope directly, since InlinePass already ensures all is well. | ||
val seededScope: Inliner.Scope = Map(opDecl.name -> opDecl) | ||
|
||
// To implement the Repeat rule, we generate a sequence of cells. | ||
// At each step, we perform one application of the operator | ||
// defined by `opDecl` to the previous partial result, | ||
// and pass the iteration index as the second parameter | ||
(1 to n.toInt).foldLeft(baseState) { case (partialState, i) => | ||
// partialState currently holds the cell representing the previous application step | ||
val oldResultCell = partialState.asCell | ||
|
||
// First, we inline the operator application, with cell names as parameters | ||
val appEx = tla.appOp( | ||
tla.name(opDecl.name, opT), | ||
oldResultCell.toBuilder, | ||
tla.int(i), | ||
) | ||
|
||
val inlinedEx = inliner.transform(seededScope)(appEx) | ||
rewriter.rewriteUntilDone(partialState.setRex(inlinedEx)) | ||
} | ||
case _ => | ||
throw new RewriterException("Apalache!Repeat expects a constant positive integer. Found: " + boundEx, ex) | ||
} | ||
case _ => | ||
throw new RewriterException("%s is not applicable".format(getClass.getSimpleName), state.ex) | ||
} | ||
} |
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
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
45 changes: 45 additions & 0 deletions
45
tla-bmcmt/src/test/scala/at/forsyte/apalache/tla/bmcmt/TestSymbStateRewriterRepeat.scala
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,45 @@ | ||
package at.forsyte.apalache.tla.bmcmt | ||
|
||
import at.forsyte.apalache.infra.passes.options.SMTEncoding | ||
import at.forsyte.apalache.tla.lir._ | ||
import at.forsyte.apalache.tla.types.tla | ||
|
||
trait TestSymbStateRewriterRepeat extends RewriterBase { | ||
test("""Repeat(LET Op(a,i) == a + 1 IN Op, 5, 0) = 5""") { rewriterType: SMTEncoding => | ||
// Op(x, i) == x + 1 | ||
val opT = OperT1(Seq(IntT1, IntT1), IntT1) | ||
|
||
val plusOneEx = tla.plus(tla.name("x", IntT1), tla.int(1)) | ||
val plusOneOper = tla.decl("Op", plusOneEx, tla.param("x", IntT1), tla.param("i", IntT1)) | ||
val letEx = tla.letIn(tla.name(plusOneOper.name, opT), plusOneOper) | ||
val repeatEx = tla.repeat(letEx, 5, tla.int(0)) | ||
|
||
val rewriter = create(rewriterType) | ||
var state = new SymbState(repeatEx, arena, Binding()) | ||
state = rewriter.rewriteUntilDone(state) | ||
val asCell = state.asCell | ||
|
||
// compare the value | ||
val eqn = tla.eql(asCell.toBuilder, tla.int(5)) | ||
assertTlaExAndRestore(rewriter, state.setRex(eqn)) | ||
} | ||
|
||
test("""Repeat(LET Op(a,i) == a + i IN Op, 5, 0) = 15""") { rewriterType: SMTEncoding => | ||
// Op(x, i) == x + i | ||
val opT = OperT1(Seq(IntT1, IntT1), IntT1) | ||
|
||
val plusiEx = tla.plus(tla.name("x", IntT1), tla.name("i", IntT1)) | ||
val plusiOper = tla.decl("Op", plusiEx, tla.param("x", IntT1), tla.param("i", IntT1)) | ||
val letEx = tla.letIn(tla.name(plusiOper.name, opT), plusiOper) | ||
val repeatEx = tla.repeat(letEx, 5, tla.int(0)) | ||
|
||
val rewriter = create(rewriterType) | ||
var state = new SymbState(repeatEx, arena, Binding()) | ||
state = rewriter.rewriteUntilDone(state) | ||
val asCell = state.asCell | ||
|
||
// compare the value | ||
val eqn = tla.eql(asCell.toBuilder, tla.int(15)) | ||
assertTlaExAndRestore(rewriter, state.setRex(eqn)) | ||
} | ||
} |
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
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
Oops, something went wrong.