This bug can be triggered by define-fun, quantifier macros or inferred substitutions whose RHS contain quantified formulas.
This corrects the issue by ensuring that bound variables introduced for prenexing are fresh for distinct quantified formula subterms that may share quantified variables.
This was reported by Geoff Sutcliffe via a TPTP run.
{
return NodeManager::currentNM()->mkNode(kind::SEXPR, cv1, cv2);
}
+Node BoundVarManager::getCacheValue(TNode cv1, TNode cv2, TNode cv3)
+{
+ return NodeManager::currentNM()->mkNode(kind::SEXPR, cv1, cv2, cv3);
+}
Node BoundVarManager::getCacheValue(TNode cv1, TNode cv2, size_t i)
{
//---------------------------------- utilities for computing Node hash
/** get cache value from two nodes, returns SEXPR */
static Node getCacheValue(TNode cv1, TNode cv2);
+ /** get cache value from three nodes, returns SEXPR */
+ static Node getCacheValue(TNode cv1, TNode cv2, TNode cv3);
/** get cache value from two nodes and a size_t, returns SEXPR */
static Node getCacheValue(TNode cv1, TNode cv2, size_t i);
/** get cache value, returns a constant rational node */
{
return Node::null();
}
- lit = lit[1];
- bool pol = lit.getKind() != NOT;
- Node n = pol ? lit : lit[0];
+ Node body = lit[1];
+ bool pol = body.getKind() != NOT;
+ Node n = pol ? body : body[0];
NodeManager* nm = NodeManager::currentNM();
if (n.getKind() == APPLY_UF)
{
Node n_def = nm->mkConst(pol);
Node fdef = solveEq(n, n_def);
Assert(!fdef.isNull());
- return fdef;
+ return returnMacro(fdef, lit);
}
}
else if (pol && n.getKind() == EQUAL)
<< "...does not contain bad (recursive) operator." << std::endl;
// must be ground UF term if mode is GROUND_UF
if (options::macrosQuantMode() != options::MacrosQuantMode::GROUND_UF
- || preservesTriggerVariables(lit, n_def))
+ || preservesTriggerVariables(body, n_def))
{
Trace("macros-debug")
<< "...respects ground-uf constraint." << std::endl;
Node fdef = solveEq(m, n_def);
if (!fdef.isNull())
{
- return fdef;
+ return returnMacro(fdef, lit);
}
}
}
return op.eqNode(fdef);
}
+Node QuantifiersMacros::returnMacro(Node fdef, Node lit) const
+{
+ Trace("macros") << "* Inferred macro " << fdef << " from " << lit
+ << std::endl;
+ return fdef;
+}
+
} // namespace quantifiers
} // namespace theory
} // namespace cvc5
* where n is of the form U(x1...xn).
*/
Node solveEq(Node n, Node ndef);
+ /**
+ * Returns the macro fdef, which originated from lit. This method is for
+ * debugging.
+ */
+ Node returnMacro(Node fdef, Node lit) const;
/** Reference to the quantifiers registry */
QuantifiersRegistry& d_qreg;
};
Node vv;
if (!q.isNull())
{
- Node cacheVal = BoundVarManager::getCacheValue(q, v);
+ // We cache based on the original quantified formula, the subformula
+ // that we are pulling variables from (body), and the variable v.
+ // The argument body is required since in rare cases, two subformulas
+ // may share the same variables. This is the case for define-fun
+ // or inferred substitutions that contain quantified formulas.
+ Node cacheVal = BoundVarManager::getCacheValue(q, body, v);
vv = bvm->mkBoundVar<QRewPrenexAttribute>(cacheVal, vt);
}
else
regress1/quantifiers/javafe.ast.StmtVec.009.smt2
regress1/quantifiers/lia-witness-div-pp.smt2
regress1/quantifiers/lra-vts-inf.smt2
+ regress1/quantifiers/macro-geo-small-3.smt2
regress1/quantifiers/min-ppgt-em-incomplete.smt2
regress1/quantifiers/min-ppgt-em-incomplete2.smt2
regress1/quantifiers/mix-coeff.smt2
--- /dev/null
+; COMMAND-LINE: --finite-model-find
+; EXPECT: sat
+(set-logic ALL)
+(set-info :status sat)
+(declare-sort U 0)
+(declare-fun T (U U) Bool)
+(define-fun P ((x1 U) (y1 U)) Bool (forall ((z1 U)) (or (not (T z1 x1)) (T z1 y1))))
+(define-fun E ((y2 U)) Bool (forall ((z2 U) (w2 U)) (or (not (P z2 y2)) (P z2 w2) (P w2 z2))))
+(assert (forall ((x3 U)) (exists ((y3 U)) (and (T y3 x3) (not (E x3))))))
+; benchmark triggered unsoundness due to reusing a bound variable for prenexing the lifting of P twice within the definition of E
+(check-sat)