From f7dcb4875bba33b7712732a874581639681926f8 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 13 Apr 2021 15:38:36 -0500 Subject: Refactor quantifiers macros (#6348) This does some refactoring of quantifiers macros preprocessing pass to use up-to-date utility methods, including lambdas, substitutions, methods for getting free variables. This is work towards adding proofs for macros. --- src/preprocessing/passes/quantifier_macros.cpp | 401 +++++++------------------ src/preprocessing/passes/quantifier_macros.h | 43 +-- 2 files changed, 129 insertions(+), 315 deletions(-) (limited to 'src/preprocessing/passes') diff --git a/src/preprocessing/passes/quantifier_macros.cpp b/src/preprocessing/passes/quantifier_macros.cpp index b3a80de2c..66837267a 100644 --- a/src/preprocessing/passes/quantifier_macros.cpp +++ b/src/preprocessing/passes/quantifier_macros.cpp @@ -19,6 +19,7 @@ #include +#include "expr/node_algorithm.h" #include "expr/skolem_manager.h" #include "options/quantifiers_options.h" #include "options/smt_options.h" @@ -57,7 +58,7 @@ PreprocessingPassResult QuantifierMacros::applyInternal( bool success; do { - success = simplify(assertionsToPreprocess, true); + success = simplify(assertionsToPreprocess); } while (success); finalizeDefinitions(); clearMaps(); @@ -66,16 +67,13 @@ PreprocessingPassResult QuantifierMacros::applyInternal( void QuantifierMacros::clearMaps() { - d_macro_basis.clear(); - d_macro_defs.clear(); - d_macro_defs_new.clear(); - d_macro_def_contains.clear(); - d_simplify_cache.clear(); + d_macroDefs.clear(); + d_macroDefsNew.clear(); d_quant_macros.clear(); d_ground_macros = false; } -bool QuantifierMacros::simplify(AssertionPipeline* ap, bool doRewrite) +bool QuantifierMacros::simplify(AssertionPipeline* ap) { const std::vector& assertions = ap->ref(); unsigned rmax = @@ -100,13 +98,17 @@ bool QuantifierMacros::simplify(AssertionPipeline* ap, bool doRewrite) i--; } } - Trace("macros") << "...finished process, #new def = " << d_macro_defs_new.size() << std::endl; - if( doRewrite && !d_macro_defs_new.empty() ){ + Trace("macros") << "...finished process, #new def = " + << d_macroDefsNew.size() << std::endl; + if (!d_macroDefsNew.empty()) + { bool retVal = false; Trace("macros") << "Do simplifications..." << std::endl; //now, rewrite based on macro definitions - for( unsigned i=0; i args; - for( size_t j=0; j args(n[0].begin(), n[0].end()); Node nproc = n[1]; - if( !d_macro_defs_new.empty() ){ - nproc = simplify( nproc ); - if( nproc!=n[1] ){ - nproc = Rewriter::rewrite( nproc ); - } + if (!d_macroDefsNew.empty()) + { + nproc = nproc.substitute(d_macroDefsNew.begin(), d_macroDefsNew.end()); + nproc = Rewriter::rewrite(nproc); } //look at the body of the quantifier for macro definition if( process( nproc, true, args, n ) ){ @@ -177,7 +170,8 @@ bool QuantifierMacros::containsBadOp( Node n, Node op, std::vector< Node >& opc, visited[n] = true; if( n.getKind()==APPLY_UF ){ Node nop = n.getOperator(); - if( nop==op || d_macro_defs.find( nop )!=d_macro_defs.end() ){ + if (nop == op || d_macroDefs.find(nop) != d_macroDefs.end()) + { return true; } if( std::find( opc.begin(), opc.end(), nop )==opc.end() ){ @@ -195,10 +189,6 @@ bool QuantifierMacros::containsBadOp( Node n, Node op, std::vector< Node >& opc, return false; } -bool QuantifierMacros::isMacroLiteral( Node n, bool pol ){ - return pol && n.getKind()==EQUAL; -} - bool QuantifierMacros::isGroundUfTerm(Node q, Node n) { Node icn = d_preprocContext->getTheoryEngine() @@ -221,7 +211,8 @@ bool QuantifierMacros::isBoundVarApplyUf( Node n ) { TypeNode tno = n.getOperator().getType(); std::map< Node, bool > vars; // allow if a vector of unique variables of the same type as UF arguments - for( unsigned i=0; i& v_quant, std::vector< Node >& vars, bool retOnly, std::map< Node, bool >& visited ){ - if( visited.find( n )==visited.end() ){ - visited[n] = true; - if( std::find( v_quant.begin(), v_quant.end(), n )!=v_quant.end() ){ - if( std::find( vars.begin(), vars.end(), n )==vars.end() ){ - if( retOnly ){ - return true; - }else{ - vars.push_back( n ); - } - } - } - for( size_t i=0; i& v_quant, std::map< Node, Node >& solved, - std::vector< Node >& vars, std::vector< Node >& subs, bool reqComplete ){ - bool success = true; - for( size_t a=0; a& args, Node f ){ Trace("macros-debug") << " process " << n << std::endl; NodeManager* nm = NodeManager::currentNM(); - SkolemManager* sm = nm->getSkolemManager(); if( n.getKind()==NOT ){ return process( n[0], !pol, args, f ); - }else if( n.getKind()==AND || n.getKind()==OR ){ - //bool favorPol = (n.getKind()==AND)==pol; - //conditional? - }else if( n.getKind()==ITE ){ - //can not do anything }else if( n.getKind()==APPLY_UF ){ //predicate case if( isBoundVarApplyUf( n ) ){ Node op = n.getOperator(); - if( d_macro_defs.find( op )==d_macro_defs.end() ){ + if (d_macroDefs.find(op) == d_macroDefs.end()) + { Node n_def = nm->mkConst(pol); - for( unsigned i=0; imkDummySkolem(ss.str(), - n[i].getType(), - "created during macro definition recognition"); - d_macro_basis[op].push_back( v ); - } - //contains no ops - std::vector< Node > op_contains; //add the macro - addMacro( op, n_def, op_contains ); - return true; + return addMacroEq(n, n_def); } } - }else{ + } + else if (pol && n.getKind() == EQUAL) + { //literal case - if( isMacroLiteral( n, pol ) ){ - Trace("macros-debug") << "Check macro literal : " << n << std::endl; - std::map< Node, bool > visited; - std::vector< Node > candidates; - for( size_t i=0; i fvs; - visited.clear(); - getFreeVariables( m, args, fvs, false, visited ); - //get definition and condition - Node n_def = solveInEquality( m, n ); //definition for the macro - if( !n_def.isNull() ){ - Trace("macros-debug") << m << " is possible macro in " << f << std::endl; - Trace("macros-debug") << " corresponding definition is : " << n_def << std::endl; - visited.clear(); - //definition must exist and not contain any free variables apart from fvs - if( !getFreeVariables( n_def, args, fvs, true, visited ) ){ - Trace("macros-debug") << "...free variables are contained." << std::endl; - visited.clear(); - //cannot contain a defined operator, opc is list of functions it contains - std::vector< Node > opc; - if( !containsBadOp( n_def, op, opc, visited ) ){ - Trace("macros-debug") << "...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 - || isGroundUfTerm(f, n_def)) - { - Trace("macros-debug") << "...respects ground-uf constraint." << std::endl; - //now we must rewrite candidates[i] to a term of form g( x1, ..., xn ) where - // x1 ... xn are distinct variables - if( d_macro_basis[op].empty() ){ - for( size_t a=0; amkDummySkolem( - ss.str(), - m[a].getType(), - "created during macro definition recognition"); - d_macro_basis[op].push_back( v ); - } - } - std::map< Node, Node > solved; - for( size_t a=0; a vars; - std::vector< Node > subs; - if( getSubstitution( fvs, solved, vars, subs, true ) ){ - n_def = n_def.substitute( vars.begin(), vars.end(), subs.begin(), subs.end() ); - addMacro( op, n_def, opc ); - return true; - } - } - } - } - } - } - } + Trace("macros-debug") << "Check macro literal : " << n << std::endl; + std::map visited; + std::vector candidates; + for (size_t i = 0; i < n.getNumChildren(); i++) + { + getMacroCandidates(n[i], candidates, visited); } - } - return false; -} - -Node QuantifierMacros::simplify( Node n ){ - if( n.getNumChildren()==0 ){ - return n; - }else{ - std::map< Node, Node >::iterator itn = d_simplify_cache.find( n ); - if( itn!=d_simplify_cache.end() ){ - return itn->second; - }else{ - Node ret = n; - Trace("macros-debug") << " simplify " << n << std::endl; - std::vector< Node > children; - bool childChanged = false; - for( size_t i=0; i::iterator it = d_macro_defs.find( op ); - if( it!=d_macro_defs.end() && !it->second.isNull() ){ - //only apply if children are subtypes of arguments - bool success = true; - // FIXME : this can be eliminated when we have proper typing rules - std::vector< Node > cond; - TypeNode tno = op.getType(); - for( unsigned i=0; isecond; - std::map< Node, std::vector< Node > >::iterator itb = d_macro_basis.find( op ); - if( itb!=d_macro_basis.end() ){ - ret = ret.substitute( itb->second.begin(), itb->second.end(), children.begin(), children.end() ); - } - if( !cond.empty() ){ - Node cc = cond.size()==1 ? cond[0] : NodeManager::currentNM()->mkNode( kind::AND, cond ); - ret = NodeManager::currentNM()->mkNode( kind::ITE, cc, ret, n ); - } - retSet = true; - } - } + // get definition and condition + Node n_def = solveInEquality(m, n); // definition for the macro + if (n_def.isNull()) + { + continue; } - if( !retSet && childChanged ){ - if( n.getMetaKind() == kind::metakind::PARAMETERIZED ){ - children.insert( children.begin(), n.getOperator() ); + Trace("macros-debug") << m << " is possible macro in " << f << std::endl; + Trace("macros-debug") + << " corresponding definition is : " << n_def << std::endl; + visited.clear(); + // cannot contain a defined operator, opc is list of functions it contains + std::vector opc; + if (!containsBadOp(n_def, op, opc, visited)) + { + Trace("macros-debug") + << "...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 + || isGroundUfTerm(f, n_def)) + { + Trace("macros-debug") + << "...respects ground-uf constraint." << std::endl; + if (addMacroEq(m, n_def)) + { + return true; + } } - ret = NodeManager::currentNM()->mkNode( n.getKind(), children ); - } - d_simplify_cache[n] = ret; - return ret; - } - } -} - -void QuantifierMacros::debugMacroDefinition( Node oo, Node n ) { - //for debugging, ensure that all previous definitions have been eliminated - if( n.getKind()==APPLY_UF ){ - Node op = n.getOperator(); - if( d_macro_defs.find( op )!=d_macro_defs.end() ){ - if( d_macro_defs.find( oo )!=d_macro_defs.end() ){ - Trace("macros-warn") << "BAD DEFINITION for macro " << oo << " : " << d_macro_defs[oo] << std::endl; - }else{ - Trace("macros-warn") << "BAD ASSERTION " << oo << std::endl; } - Trace("macros-warn") << " contains defined function " << op << "!!!" << std::endl; } } - for( unsigned i=0; igetSmt(); - for( std::map< Node, Node >::iterator it = d_macro_defs.begin(); it != d_macro_defs.end(); ++it ){ - Trace("macros-def") << "Macro definition for " << it->first << " : " << it->second << std::endl; + for (const std::pair& m : d_macroDefs) + { + Trace("macros-def") << "Macro definition for " << m.first << " : " + << m.second << std::endl; Trace("macros-def") << " basis is : "; - std::vector< Node > nargs; - std::vector args; - for( unsigned i=0; ifirst].size(); i++ ){ - Node bv = NodeManager::currentNM()->mkBoundVar( d_macro_basis[it->first][i].getType() ); - Trace("macros-def") << d_macro_basis[it->first][i] << " "; - nargs.push_back( bv ); - args.push_back(bv); - } - Trace("macros-def") << std::endl; - Node sbody = it->second.substitute( d_macro_basis[it->first].begin(), d_macro_basis[it->first].end(), nargs.begin(), nargs.end() ); - smt->defineFunction(it->first, args, sbody); - - if( Trace.isOn("macros-warn") ){ - debugMacroDefinition( it->first, sbody ); - } + std::vector args(m.second[0].begin(), m.second[0].end()); + Node sbody = m.second[1]; + smt->defineFunction(m.first, args, sbody); } - Trace("macros") << "done." << std::endl; + Trace("macros-def") << "done." << std::endl; } } -void QuantifierMacros::addMacro( Node op, Node n, std::vector< Node >& opc ) { - Trace("macros") << "* " << n << " is a macro for " << op << ", #op contain = " << opc.size() << std::endl; - d_simplify_cache.clear(); - d_macro_defs[op] = n; - d_macro_defs_new[op] = n; - //substitute into all previous - std::vector< Node > dep_ops; - dep_ops.push_back( op ); - Trace("macros-debug") << "...substitute into " << d_macro_def_contains[op].size() << " previous definitions." << std::endl; - for( unsigned i=0; i vars; + std::vector fvars; + for (const Node& nc : n) + { + vars.push_back(nc); + Node v = nm->mkBoundVar(nc.getType()); + fvars.push_back(v); } - //store the contains op information - for( unsigned i=0; imkNode(LAMBDA, nm->mkNode(BOUND_VAR_LIST, fvars), fdef); + // If the definition has a free variable, it is malformed. This can happen + // if the right hand side of a macro definition contains a variable not + // contained in the left hand side + if (expr::hasFreeVar(fdef)) + { + return false; + } + TNode op = n.getOperator(); + TNode fdeft = fdef; + for (std::pair& prev : d_macroDefsNew) + { + d_macroDefsNew[prev.first] = prev.second.substitute(op, fdeft); } + Assert(op.getType().isComparableTo(fdef.getType())); + d_macroDefs[op] = fdef; + d_macroDefsNew[op] = fdef; + Trace("macros") << "(macro " << op << " " << fdef[0] << " " << fdef[1] << ")" + << std::endl; + return true; } - } // passes } // preprocessing } // namespace cvc5 diff --git a/src/preprocessing/passes/quantifier_macros.h b/src/preprocessing/passes/quantifier_macros.h index 8ee878c29..54215b620 100644 --- a/src/preprocessing/passes/quantifier_macros.h +++ b/src/preprocessing/passes/quantifier_macros.h @@ -44,49 +44,38 @@ class QuantifierMacros : public PreprocessingPass Node op, std::vector& opc, std::map& visited); - bool isMacroLiteral(Node n, bool pol); bool isGroundUfTerm(Node f, Node n); void getMacroCandidates(Node n, std::vector& candidates, std::map& visited); Node solveInEquality(Node n, Node lit); - bool getFreeVariables(Node n, - std::vector& v_quant, - std::vector& vars, - bool retOnly, - std::map& visited); - bool getSubstitution(std::vector& v_quant, - std::map& solved, - std::vector& vars, - std::vector& subs, - bool reqComplete); - void addMacro(Node op, Node n, std::vector& opc); - void debugMacroDefinition(Node oo, Node n); + /** + * Called when we have inferred a quantified formula is of the form + * forall x1 ... xn. n = ndef + * where n is of the form U(x1...xn). Returns true if this is a legal + * macro definition for U. + */ + bool addMacroEq(Node n, Node ndef); /** * This applies macro elimination to the given pipeline, which discovers - * whether there are any quantified formulas corresponding to macros. + * whether there are any quantified formulas corresponding to macros, + * and rewrites the given assertions pipeline. * * @param ap The pipeline to apply macros to. - * @param doRewrite Whether we also wish to rewrite the assertions based on - * the discovered macro definitions. * @return Whether new definitions were inferred and we rewrote the assertions * based on them. */ - bool simplify(AssertionPipeline* ap, bool doRewrite = false); - Node simplify(Node n); + bool simplify(AssertionPipeline* ap); void finalizeDefinitions(); void clearMaps(); - // map from operators to macro basis terms - std::map > d_macro_basis; - // map from operators to macro definition - std::map d_macro_defs; - std::map d_macro_defs_new; - // operators to macro ops that contain them - std::map > d_macro_def_contains; - // simplify caches - std::map d_simplify_cache; + /** All macros inferred by this class */ + std::map d_macroDefs; + /** The current list of macros inferring during a call to simplify */ + std::map d_macroDefsNew; + /** Map from quantified formulas to whether they are macro definitions */ std::map d_quant_macros; + /** Whether we are currently limited to inferring ground macros */ bool d_ground_macros; }; -- cgit v1.2.3