Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make records as they are work in the arrays encoding #1419

Merged
merged 16 commits into from
Mar 8, 2022

Conversation

rodrigo7491
Copy link
Collaborator

@rodrigo7491 rodrigo7491 commented Feb 28, 2022

  • Tests added for any new code
  • Ran make fmt-fix (or had formatting run automatically on all files edited)
  • Documentation added for any new functionality
  • Entry added to UNRELEASED.md for any new functionality

This PR fixes three errors that were blocking the use of records in the arrays encoding, they are detailed below. Closes #1288.

  1. DomainRuleWithArrays was computing funEx twice due to call to super. This was solved by moving the remaining cases of apply from DomainRule to DomainRuleWithArrays.
  2. As pointed out by @konnov, when equating sets or functions we need to generate equalities for their elements using LazyEquality. This was solved by calling cacheEqConstraints with the sets'/functions' elements. For this fix EqRuleWithArrays was deleted and its logic moved to LazyEquality, making that class less lazy than before :-).
  3. Picking a record via CherryPick.pickRecord was generating unsatisfiable formulas in some cases. The problem was that when constructing the domain of the picked record we could lose the chain of SSA updates, due to a storeNotInSet operation being in the rhs of an implication. This was solved by changing the operation to not(selectInSet), which ensures absence from the set without updating the array, given that sets are initially empty.

@codecov-commenter
Copy link

codecov-commenter commented Mar 2, 2022

Codecov Report

Merging #1419 (93a5942) into unstable (bbdf123) will increase coverage by 0.05%.
The diff coverage is 87.83%.

Impacted file tree graph

@@             Coverage Diff              @@
##           unstable    #1419      +/-   ##
============================================
+ Coverage     74.52%   74.58%   +0.05%     
============================================
  Files           360      359       -1     
  Lines         11433    11442       +9     
  Branches        535      532       -3     
============================================
+ Hits           8521     8534      +13     
+ Misses         2912     2908       -4     
Impacted Files Coverage Δ
.../forsyte/apalache/tla/bmcmt/rules/DomainRule.scala 94.00% <ø> (ø)
...palache/tla/bmcmt/rules/DomainRuleWithArrays.scala 75.00% <60.00%> (ø)
...a/at/forsyte/apalache/tla/bmcmt/LazyEquality.scala 73.52% <86.20%> (+0.61%) ⬆️
...palache/tla/bmcmt/rules/FunAppRuleWithArrays.scala 94.82% <91.66%> (-5.18%) ⬇️
...he/tla/bmcmt/SymbStateRewriterImplWithArrays.scala 100.00% <100.00%> (ø)
...apalache/tla/bmcmt/rules/SetInRuleWithArrays.scala 92.06% <100.00%> (ø)
...syte/apalache/tla/bmcmt/rules/aux/CherryPick.scala 93.75% <100.00%> (+0.34%) ⬆️
...he/io/annotations/parser/CommentPreprocessor.scala 93.97% <0.00%> (+1.20%) ⬆️
.../forsyte/apalache/tla/bmcmt/SymbStateDecoder.scala 80.00% <0.00%> (+7.00%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bbdf123...93a5942. Read the comment docs.

@rodrigo7491
Copy link
Collaborator Author

rodrigo7491 commented Mar 2, 2022

The integration tests caught a bug that surfaced due to the changes made for point 2 (see PR description). When handling function application we were comparing cells directly, which is problematic in the presence of quantifiers, e.g., \A x \in S . f(x). The fix I made involved using part of the logic in FunAppRule.applyFun, using an oracle to avoid the direct cell comparison. In contrast to FunAppRule however, the constraints generated are for the domain and not the range. See the latest commit for these changes.

The integration tests are now passing, but the fix introduced a major efficiency loss. In #1169, the integration tests check for the arrays encoding took 4min, now it takes 1h. This should be addressed before merging the PR.

@rodrigo7491
Copy link
Collaborator Author

@konnov, this PR will hopefully help with #1381. If not, work on #1418 should do the trick.

Copy link
Collaborator

@konnov konnov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I have added a few comments.

Comment on lines +252 to +258
if (left.cellType == FinSetT(UnknownT()) && state.arena.getHas(left).isEmpty) {
// The statically empty set is a very special case, as its element type is unknown.
// Hence, we cannot use SMT equality, as it does not work with different sorts.
mkEmptySetEq(state, left, right)
} else if (right.cellType == FinSetT(UnknownT()) && state.arena.getHas(right).isEmpty) {
mkEmptySetEq(state, right, left) // same here
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very old piece of code. Let's replace it with an assertion that both left.cellType and right.cellType are FinSetT(_).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #1430.

Comment on lines 36 to 63
val (oracleState, oracle) = picker.oracleFactory.newDefaultOracle(nextState, nElems + 1)
nextState = picker.pickByOracle(oracleState, oracle, relationElems, oracleState.arena.cellTrue().toNameEx)
val pickedElem = nextState.asCell

assert(nextState.arena.getHas(pickedElem).size == 2)
val pickedArg = nextState.arena.getHas(pickedElem)(0)
val pickedRes = nextState.arena.getHas(pickedElem)(1)

// Cache the equality between the picked argument and the supplied argument, O(1) constraints
nextState = rewriter.lazyEq.cacheEqConstraints(nextState, Seq((pickedArg, argCell)))
// If oracle < N, then pickedArg = argCell
val pickedElemInDom = tla.not(oracle.whenEqualTo(nextState, nElems))
val argEq = tla.eql(pickedArg.toNameEx, argCell.toNameEx)
val argEqWhenNonEmpty = tla.impl(pickedElemInDom, argEq)
rewriter.solverContext.assertGroundExpr(argEqWhenNonEmpty)

// We require oracle = N iff there is no element equal to argCell, O(N) constraints
for ((elem, no) <- relationElems.zipWithIndex) {
val elemArg = nextState.arena.getHas(elem).head
nextState = rewriter.lazyEq.cacheEqConstraints(nextState, Seq((elemArg, argCell)))
val inDom = tla.apalacheSelectInSet(elemArg.toNameEx, domainCell.toNameEx)
val neqArg = tla.not(rewriter.lazyEq.safeEq(elemArg, argCell))
val found = tla.not(oracle.whenEqualTo(nextState, nElems))
// ~inDom \/ neqArg \/ found, or equivalently, (inDom /\ elemArg = argCell) => found
rewriter.solverContext.assertGroundExpr(tla.or(tla.not(inDom), neqArg, found))
// oracle = i => inDom. The equality pickedArg = argCell is enforced by argEqWhenNonEmpty
rewriter.solverContext.assertGroundExpr(tla.or(tla.not(oracle.whenEqualTo(nextState, no)), inDom))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have to do all of this to propagate lazy equality? If so, maybe we should limit this code to non-scalar types, that is, do not apply it Int, Bool, Str, and an uninterpreted type.

rodrigo7491 and others added 5 commits March 2, 2022 12:08
…ality.scala

Co-authored-by: Igor Konnov <igor@informal.systems>
…ality.scala

Co-authored-by: Igor Konnov <igor@informal.systems>
…omainRuleWithArrays.scala

Co-authored-by: Igor Konnov <igor@informal.systems>
@rodrigo7491
Copy link
Collaborator Author

The slowdown seen before was due to having to use an oracle to ascertain the argument of the function application. I made a fix in which, if the argument can be found in Scala by comparing cells, we generate a single SMT constraint querying the array that encodes the function. If we can't, e.g., in the presence of quantifiers, we use an oracle and need to have O(N) SMT constraints, with N being the cardinality of the domain of the function.

This change made the integration tests for the arrays encoding go back to their previous runtime, observed in #1169. Shall we merge the PR @konnov?

Copy link
Collaborator

@konnov konnov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Perhaps, we can simplify things when we add SMT-native support for records, tuples, etc.

@@ -21,14 +21,25 @@ class DomainRuleWithArrays(rewriter: SymbStateRewriter, intRangeCache: IntRangeC
val funState = rewriter.rewriteUntilDone(state.setRex(funEx))
val funCell = funState.asCell

// TODO: consider records, tuples, and sequences in the arrays encoding
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this TODO obsolete?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. It is there as a reminder of what was not touched for when we decide to encode these features differently.

@rodrigo7491 rodrigo7491 merged commit 6a3dd9f into unstable Mar 8, 2022
@rodrigo7491 rodrigo7491 deleted the enable-records-in-arrays-encoding branch March 8, 2022 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] Add support for TLA+ records in the arrays encoding
3 participants