-
Notifications
You must be signed in to change notification settings - Fork 3.1k
SI-6582 Use Tseitin's algorithm for CNF conversion in pattern matching analysis. #2796
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
Conversation
andFormula(eqAxioms, pure) | ||
def propToSolvable(p: Prop): Solvable = { | ||
val (eqAxiom, pure :: Nil) = removeVarEq(List(p), modelNull = false) | ||
eqFreePropToSolvable(simplify(And(eqAxiom, pure))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to oth 8000 ers. Learn more.
Note that I added a simplify call here compared to the original PR. Necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eqFreePropToSolvable
calls simplify
internally, so it can be omitted here
I cheated on a check file. We should figure out what changed before merging, if possible. |
I'm hopeful that checkfile issue will be caused by the non-determinism fixed in #2826 / SI-7020 |
Is #2826 the only hold back on this PR? |
The only issue with this PR is that I had to change test/files/neg/patmatexhaust.check, which should not have been necessary. I hope we'll know today whether #2826 solves that. |
I'll rebase on top of the other PR and see if it helps. |
Remove redundant UniqueSym class.
This resolves the `AnalysisBudgetException` problem for big pattern matches. Initial experiments indicate that the problem is even simpler than initially thought: all formulae I got from the exhaustiveness check could be transformed directly into CNF. Thus the framework introduced here might be overkill and a much simpler implementation could work as well. For Scala 2.11 I'd suggest to replace the DPLL procedure with Sat4j.
I think I found the issue. I probably made a mistake refactoring findAllModelsFor. Seems ok now. |
Here's the diff with my previous version of the PR: diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala b/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala
index 64128b82ac..1f2dc33f07 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/Solving.scala
@@ -201,31 +201,33 @@ trait Solving extends Logic {
// (i.e. the blocking clause - used for ALL-SAT)
def negateModel(m: TseitinModel) = m.map(lit => -lit)
- def findAllModels(clauses: Array[Clause], models: List[TseitinModel] = Nil, recursionDepthAllowed: Int = 10): List[TseitinModel] =
+ def findAllModels(clauses: Array[Clause], models: List[TseitinModel], recursionDepthAllowed: Int = 10): List[TseitinModel] =
if (recursionDepthAllowed == 0) models
else {
debug.patmat("find all models for\n" + cnfString(clauses))
- findTseitinModelFor(clauses, stoppingNanos) match {
- case NoTseitinModel => models
- case model =>
- // if we found a solution, conjunct the formula with the model's negation and recurse
- val unassigned = (allVars -- model).toList
- debug.patmat("unassigned " + unassigned + " in " + model)
-
- def force(lit: Lit) =
- withLit(findTseitinModelFor(dropUnit(clauses.toArray, lit), stoppingNanos), lit) match {
- case NoTseitinModel => Nil
- case _ => List(model)
- }
- val forced = unassigned flatMap (l => force(l) ++ force(-l))
- debug.patmat("forced " + forced)
-
- val negated = negateModel(model)
- findAllModels(clauses :+ negated, model :: (forced ++ models), recursionDepthAllowed - 1)
+ val model = findTseitinModelFor(clauses, stoppingNanos)
+ // if we found a solution, conjunct the formula with the model's negation and recurse
+ if (model ne NoTseitinModel) {
+ val unassigned = (allVars -- model.map(lit => Lit(lit.variable))).toList
+ debug.patmat(s"unassigned ${unassigned map (lit => solvable.symForVar(lit.variable))} in ${projectToModel(model, solvable.symForVar)}")
+
+ def force(lit: Lit) = {
+ val model = withLit(findTseitinModelFor(dropUnit(clauses.toArray, lit), stoppingNanos), lit)
+ if (model ne NoTseitinModel) List(model)
+ else Nil
+ }
+ val forced = unassigned flatMap { l =>
+ force(Lit(l.variable)) ++ force(Lit(-l.variable))
+ }
+ debug.patmat("forced " + forced)
+
+ val negated = negateModel(model)
+ findAllModels(clauses :+ negated, model :: (forced ++ models), recursionDepthAllowed - 1)
}
+ else models
}
- val tseitinModels = findAllModels(solvable.cnf.clauses)
+ val tseitinModels = findAllModels(solvable.cnf.clauses, Nil)
val models = tseitinModels.map(projectToModel(_, solvable.symForVar)) |
Darn.. Looks like it got a little less deterministic. I'm not sure we'll be able to make it deterministic, and maybe should look into more clever merging of counterexamples (going into the structure, say: the examples List(a) and List(b) are merged into the single example List(a | b)). I'm afraid this is going to be tricky for 2.10.3 :( |
While you're looking at that piece of code, could you explain/comment what That changes:
and
|
I stashed my wip in my branch patmat-tseitin-2.10-wip |
Forced assigns the unassigned variable to yes/no and checks for satisfiable to force these variables to be assigned. Gotta go now, sorry. |
It would probably be faster/read better as:
|
@adriaanm If there's a solution and there are unassigned variables left, then they do not influence the solution, so why would you need to assign a value to them? My assumption is that you can't handle unassigned variables when you map the solution back to a counter example. Would it not be easier to fix it there? If there are multiple counter examples corresponding to a solution you could just return the most precise one. |
Tracing out SI-7020: def f[T](x: Option[T]) = x match {
case Some(Some(5)) => true // It would fail on the following inputs: None, Some((x: T forSome x not in Some[?])), Some(Some((x: Any forSome x not in 5))), Some(_)
}
Taking the unassigned Is |
I'm very open to improvements to the counter example algorithm (though I'm On Wednesday, August 14, 2013, Jason Zaugg wrote:
|
Yeah, the whole logic of dependencies between variables' types is not encoded in the formula. That's why the counter examples need to be pruned (https://github.com/scala/scala/blob/master/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala#L352) |
@gbasler, how would you feel about having your work go to 2.11.0-M6 (September), instead of 2.10.3? 2.10 is in maintenance mode, and I'm not sure we have enough test coverage to change the algorithm this deeply without causing regressions. |
@adriaanm Sure. I really liked your improvements to the code (I totally missed to use a value class for Lits). I also don't have much time to look into this right now and it's better to clean up a bit before we merge these changes. I'll try to find the time and also look at the two other commits from Jason. Better be correct than fast and wrong! |
Thanks for the explanation. I've noted this over on SI-7746. |
Kicking to 2.10.4...but perhaps it's really 2.11 material? |
@gbasler, thanks! I really want this in, but I agree we should take our time to do it right. I feel 2.11 is the right target. |
This chops #2724 into smaller commits.
I made Lit into a value class and encapsulated a bit more.
Please double check my refactorings were correctness-preserving.
review by @gbasler