diff --git a/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala b/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala index b7fefa6b243c653341976633b13779636ea81186..95a8cdd67722660f666a9256a0020fab9d93b5d2 100644 --- a/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala +++ b/src/main/scala/leon/solvers/combinators/UnrollingSolver.scala @@ -9,6 +9,7 @@ import purescala.Common._ import purescala.Definitions._ import purescala.Quantification._ import purescala.Constructors._ +import purescala.Extractors._ import purescala.Expressions._ import purescala.ExprOps._ import purescala.Types._ @@ -268,8 +269,67 @@ trait AbstractUnrollingSolver[T] private def getTotalModel: Model = { val wrapped = solverGetModel - templateGenerator.manager.quantifications.map { q => - q.holds + def checkForalls(quantified: Set[Identifier], body: Expr): Option[String] = { + val matchers = collect[(Expr, Seq[Expr])] { + case QuantificationMatcher(e, args) => Set(e -> args) + case _ => Set.empty + } (body) + + if (matchers.isEmpty) + return Some("No matchers found.") + + val matcherToQuants = matchers.foldLeft(Map.empty[Expr, Set[Identifier]]) { + case (acc, (m, args)) => acc + (m -> (acc.getOrElse(m, Set.empty) ++ args.flatMap { + case Variable(id) if quantified(id) => Set(id) + case _ => Set.empty[Identifier] + })) + } + + val bijectiveMappings = matcherToQuants.filter(_._2.nonEmpty).groupBy(_._2) + if (bijectiveMappings.size > 1) + return Some("Non-bijective mapping for symbol " + bijectiveMappings.head._2.head._1.asString) + + def quantifiedArg(e: Expr): Boolean = e match { + case Variable(id) => quantified(id) + case QuantificationMatcher(_, args) => args.forall(quantifiedArg) + case _ => false + } + + postTraversal(m => m match { + case QuantificationMatcher(_, args) => + val qArgs = args.filter(quantifiedArg) + + if (qArgs.nonEmpty && qArgs.size < args.size) + return Some("Mixed ground and quantified arguments in " + m.asString) + + case Operator(es, _) if es.collect { case Variable(id) if quantified(id) => id }.nonEmpty => + return Some("Invalid operation on quantifiers " + m.asString) + + case (_: Equals) | (_: And) | (_: Or) | (_: Implies) => // OK + + case Operator(es, _) if (es.flatMap(variablesOf).toSet & quantified).nonEmpty => + return Some("Unandled implications from operation " + m.asString) + + case _ => + }) (body) + + body match { + case Variable(id) if quantified(id) => + Some("Unexpected free quantifier " + id.asString) + case _ => None + } + } + + val issues: Iterable[(Seq[Identifier], Expr, String)] = for { + q <- templateGenerator.manager.quantifications.view + if wrapped.eval(q.holds, BooleanType) == Some(BooleanLiteral(true)) + msg <- checkForalls(q.quantifiers.map(_._1).toSet, q.body) + } yield (q.quantifiers.map(_._1), q.body, msg) + + if (issues.nonEmpty) { + val (quantifiers, body, msg) = issues.head + reporter.warning("Model soundness not guaranteed for \u2200" + + quantifiers.map(_.asString).mkString(",") + ". " + body.asString+" :\n => " + msg) } val typeInsts = templateGenerator.manager.typeInstantiations diff --git a/src/main/scala/leon/solvers/templates/LambdaManager.scala b/src/main/scala/leon/solvers/templates/LambdaManager.scala index 541888c8349214ef5190b3c4610ee278bcc73d01..362629388e533bcca567cd826fea9ea84e007b7c 100644 --- a/src/main/scala/leon/solvers/templates/LambdaManager.scala +++ b/src/main/scala/leon/solvers/templates/LambdaManager.scala @@ -75,7 +75,7 @@ object LambdaTemplate { matchers, quantifications, keyDeps, - key, + key -> structSubst, lambdaString ) } @@ -83,7 +83,7 @@ object LambdaTemplate { trait KeyedTemplate[T, E <: Expr] { val dependencies: Map[Identifier, T] - val structuralKey: E + val structure: E lazy val key: (E, Seq[T]) = { def rec(e: Expr): Seq[Identifier] = e match { @@ -99,7 +99,7 @@ trait KeyedTemplate[T, E <: Expr] { case _ => Seq.empty } - structuralKey -> rec(structuralKey).distinct.map(dependencies) + structure -> rec(structure).distinct.map(dependencies) } } @@ -119,12 +119,13 @@ class LambdaTemplate[T] private ( val matchers: Map[T, Set[Matcher[T]]], val quantifications: Seq[QuantificationTemplate[T]], val dependencies: Map[Identifier, T], - val structuralKey: Lambda, + val struct: (Lambda, Map[Identifier, Identifier]), stringRepr: () => String) extends Template[T] with KeyedTemplate[T, Lambda] { val args = arguments.map(_._2) val tpe = bestRealType(ids._1.getType).asInstanceOf[FunctionType] val functions: Set[(T, FunctionType, T)] = Set.empty + val (structure, structSubst) = struct def substitute(substituter: T => T, matcherSubst: Map[T, Matcher[T]]): LambdaTemplate[T] = { val newStart = substituter(start) @@ -171,7 +172,7 @@ class LambdaTemplate[T] private ( newMatchers, newQuantifications, newDependencies, - structuralKey, + struct, stringRepr ) } @@ -182,7 +183,7 @@ class LambdaTemplate[T] private ( ids._1 -> idT, encoder, manager, pathVar, arguments, condVars, exprVars, condTree, clauses map substituter, // make sure the body-defining clause is inlined! blockers, applications, lambdas, matchers, quantifications, - dependencies, structuralKey, stringRepr + dependencies, struct, stringRepr ) } diff --git a/src/main/scala/leon/solvers/templates/QuantificationManager.scala b/src/main/scala/leon/solvers/templates/QuantificationManager.scala index cd894b5f2953edf168b8fa936770b321f6d4193e..3b7d97a4ff28f5468a02566cbc196cfea54b7404 100644 --- a/src/main/scala/leon/solvers/templates/QuantificationManager.scala +++ b/src/main/scala/leon/solvers/templates/QuantificationManager.scala @@ -55,8 +55,9 @@ class QuantificationTemplate[T]( val matchers: Map[T, Set[Matcher[T]]], val lambdas: Seq[LambdaTemplate[T]], val dependencies: Map[Identifier, T], - val structuralKey: Forall) extends KeyedTemplate[T, Forall] { + val struct: (Forall, Map[Identifier, Identifier])) extends KeyedTemplate[T, Forall] { + val structure = struct._1 lazy val start = pathVar._2 def substitute(substituter: T => T, matcherSubst: Map[T, Matcher[T]]): QuantificationTemplate[T] = { @@ -88,7 +89,7 @@ class QuantificationTemplate[T]( }, lambdas.map(_.substitute(substituter, matcherSubst)), dependencies.map { case (id, value) => id -> substituter(value) }, - structuralKey + struct ) } } @@ -127,7 +128,7 @@ object QuantificationTemplate { new QuantificationTemplate[T](quantificationManager, pathVar, qs, q2s, insts, guards._2, quantifiers, condVars, exprVars, condTree, - clauses, blockers, applications, matchers, lambdas, keyDeps, key) + clauses, blockers, applications, matchers, lambdas, keyDeps, key -> structSubst) } } @@ -156,7 +157,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage private def matcherKey(caller: T, tpe: TypeTree): MatcherKey = tpe match { case ft: FunctionType if knownFree(ft)(caller) => CallerKey(caller, tpe) - case _: FunctionType if byID.isDefinedAt(caller) => LambdaKey(byID(caller).structuralKey, tpe) + case _: FunctionType if byID.isDefinedAt(caller) => LambdaKey(byID(caller).structure, tpe) case _ => TypeKey(tpe) } @@ -346,7 +347,6 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage } private[solvers] trait MatcherQuantification { - val holds: T val pathVar: (Identifier, T) val quantifiers: Seq[(Identifier, T)] val matchers: Set[Matcher[T]] @@ -359,6 +359,9 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage val applications: Map[T, Set[App[T]]] val lambdas: Seq[LambdaTemplate[T]] + val holds: T + val body: Expr + lazy val quantified: Set[T] = quantifiers.map(_._2).toSet lazy val start = pathVar._2 @@ -537,10 +540,16 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage val clauses: Seq[T], val blockers: Map[T, Set[TemplateCallInfo[T]]], val applications: Map[T, Set[App[T]]], - val lambdas: Seq[LambdaTemplate[T]]) extends MatcherQuantification { + val lambdas: Seq[LambdaTemplate[T]], + val template: QuantificationTemplate[T]) extends MatcherQuantification { var currentQ2Var: T = qs._2 val holds = qs._2 + val body = { + val quantified = quantifiers.map(_._1).toSet + val mapping = template.struct._2.map(p => p._2 -> p._1.toVariable) + replaceFromIDs(mapping, template.structure.body) + } protected def instanceSubst(enabler: T): Map[T, T] = { val nextQ2Var = encoder.encodeId(q2s._1) @@ -590,10 +599,17 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage val clauses: Seq[T], val blockers: Map[T, Set[TemplateCallInfo[T]]], val applications: Map[T, Set[App[T]]], - val lambdas: Seq[LambdaTemplate[T]]) extends MatcherQuantification { + val lambdas: Seq[LambdaTemplate[T]], + val template: LambdaTemplate[T]) extends MatcherQuantification { val holds = start + val body = { + val quantified = quantifiers.map(_._1).toSet + val mapping = template.structSubst.map(p => p._2 -> p._1.toVariable) + replaceFromIDs(mapping, template.structure) + } + protected def instanceSubst(enabler: T): Map[T, T] = { Map(guardVar -> start, blocker -> enabler) } @@ -734,75 +750,51 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage val appT = encoder.encodeExpr((template.arguments.map(_._1) zip encArgs.map(_.encoded)).toMap + template.ids)(app) val selfMatcher = Matcher(template.ids._2, template.tpe, encArgs, appT) + val instMatchers = allMatchers + (template.start -> (allMatchers.getOrElse(template.start, Set.empty) + selfMatcher)) + val enablingClause = encoder.mkImplies(guardT, blockerT) - instantiateAxiom( - template.pathVar._1 -> substituter(template.start), - blockerT, - guardT, - quantifiers, - qMatchers, - allMatchers + (template.start -> (allMatchers.getOrElse(template.start, Set.empty) + selfMatcher)), - template.condVars map { case (id, idT) => id -> substituter(idT) }, - template.exprVars map { case (id, idT) => id -> substituter(idT) }, - template.condTree, - (template.clauses map substituter) :+ enablingClause, - template.blockers map { case (b, fis) => - substituter(b) -> fis.map(fi => fi.copy( - args = fi.args.map(_.substitute(substituter, msubst)) - )) - }, - template.applications map { case (b, apps) => - substituter(b) -> apps.map(app => app.copy( - caller = substituter(app.caller), - args = app.args.map(_.substitute(substituter, msubst)) - )) - }, - template.lambdas map (_.substitute(substituter, msubst)) - ) - } - } + val condVars = template.condVars map { case (id, idT) => id -> substituter(idT) } + val exprVars = template.exprVars map { case (id, idT) => id -> substituter(idT) } + val clauses = (template.clauses map substituter) :+ enablingClause + val blockers = template.blockers map { case (b, fis) => + substituter(b) -> fis.map(fi => fi.copy(args = fi.args.map(_.substitute(substituter, msubst)))) + } - def instantiateAxiom( - pathVar: (Identifier, T), - blocker: T, - guardVar: T, - quantifiers: Seq[(Identifier, T)], - matchers: Set[Matcher[T]], - allMatchers: Map[T, Set[Matcher[T]]], - condVars: Map[Identifier, T], - exprVars: Map[Identifier, T], - condTree: Map[Identifier, Set[Identifier]], - clauses: Seq[T], - blockers: Map[T, Set[TemplateCallInfo[T]]], - applications: Map[T, Set[App[T]]], - lambdas: Seq[LambdaTemplate[T]] - ): Instantiation[T] = { - val quantified = quantifiers.map(_._2).toSet - val matchQuorums = extractQuorums(quantified, matchers, lambdas) + val applications = template.applications map { case (b, apps) => + substituter(b) -> apps.map(app => app.copy( + caller = substituter(app.caller), + args = app.args.map(_.substitute(substituter, msubst)) + )) + } - var instantiation = Instantiation.empty[T] + val lambdas = template.lambdas map (_.substitute(substituter, msubst)) - for (matchers <- matchQuorums) { - val axiom = new LambdaAxiom(pathVar, blocker, guardVar, quantifiers, - matchers, allMatchers, condVars, exprVars, condTree, - clauses, blockers, applications, lambdas - ) + val quantified = quantifiers.map(_._2).toSet + val matchQuorums = extractQuorums(quantified, qMatchers, lambdas) - quantifications += axiom - handledSubsts += axiom -> MutableSet.empty - ignoredSubsts += axiom -> MutableSet.empty + var instantiation = Instantiation.empty[T] - val newCtx = new InstantiationContext() - for ((b,m) <- instCtx.instantiated) { - instantiation ++= newCtx.instantiate(b, m)(axiom) + for (matchers <- matchQuorums) { + val axiom = new LambdaAxiom(template.pathVar._1 -> substituter(template.start), + blockerT, guardT, quantifiers, matchers, instMatchers, condVars, exprVars, template.condTree, + clauses, blockers, applications, lambdas, template) + + quantifications += axiom + handledSubsts += axiom -> MutableSet.empty + ignoredSubsts += axiom -> MutableSet.empty + + val newCtx = new InstantiationContext() + for ((b,m) <- instCtx.instantiated) { + instantiation ++= newCtx.instantiate(b, m)(axiom) + } + instCtx.merge(newCtx) } - instCtx.merge(newCtx) - } - instantiation ++= instantiateConstants(quantifiers, matchers) + instantiation ++= instantiateConstants(quantifiers, qMatchers) - instantiation + instantiation + } } def instantiateQuantification(template: QuantificationTemplate[T]): (T, Instantiation[T]) = { @@ -828,8 +820,7 @@ class QuantificationManager[T](encoder: TemplateEncoder[T]) extends LambdaManage template.quantifiers, matchers, template.matchers, template.condVars, template.exprVars, template.condTree, template.clauses map substituter, // one clause depends on 'q' (and therefore 'newQ') - template.blockers, template.applications, template.lambdas - ) + template.blockers, template.applications, template.lambdas, template) quantifications += quantification handledSubsts += quantification -> MutableSet.empty diff --git a/src/test/scala/leon/integration/solvers/QuantifierSolverSuite.scala b/src/test/scala/leon/integration/solvers/QuantifierSolverSuite.scala index 0e85b119bff11f1925d3a0177826701e0dd700d4..4ff5ccc71d159fd76ec1e48d99bce4be4dbfb125 100644 --- a/src/test/scala/leon/integration/solvers/QuantifierSolverSuite.scala +++ b/src/test/scala/leon/integration/solvers/QuantifierSolverSuite.scala @@ -108,6 +108,8 @@ class QuantifierSolverSuite extends LeonTestSuiteWithProgram { try { solver.assertCnstr(expr) solver.check match { + case Some(true) if sat && fix._1.reporter.warningCount > 0 => + fail(s"Solver $solver - Constraint ${expr.asString} doesn't guarantee model validity!?") case Some(true) if sat => // checkmodels ensures validity case Some(false) if !sat => // we were looking for unsat case res => fail(s"Solver $solver - Constraint ${expr.asString} has result $res!?")