diff options
Diffstat (limited to 'src/theory/quantifiers/sygus')
24 files changed, 13862 insertions, 0 deletions
diff --git a/src/theory/quantifiers/sygus/ce_guided_conjecture.cpp b/src/theory/quantifiers/sygus/ce_guided_conjecture.cpp new file mode 100644 index 000000000..7bcaa0cba --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_conjecture.cpp @@ -0,0 +1,894 @@ +/********************* */ +/*! \file ce_guided_conjecture.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief implementation of class that encapsulates counterexample-guided instantiation + ** techniques for a single SyGuS synthesis conjecture + **/ +#include "theory/quantifiers/sygus/ce_guided_conjecture.h" + +#include "expr/datatype.h" +#include "options/base_options.h" +#include "options/quantifiers_options.h" +#include "printer/printer.h" +#include "prop/prop_engine.h" +#include "smt/smt_statistics_registry.h" +#include "theory/quantifiers/sygus/ce_guided_instantiation.h" +#include "theory/quantifiers/first_order_model.h" +#include "theory/quantifiers/instantiate.h" +#include "theory/quantifiers/quantifiers_attributes.h" +#include "theory/quantifiers/skolemize.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_util.h" +#include "theory/theory_engine.h" + +using namespace CVC4::kind; +using namespace std; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +// recursion is not an issue since OR nodes are flattened by the (quantifiers) rewriter +// this function is for sanity since solution correctness in SyGuS depends on fully miniscoping based on this function +void collectDisjuncts( Node n, std::vector< Node >& d ) { + if( n.getKind()==OR ){ + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + collectDisjuncts( n[i], d ); + } + }else{ + d.push_back( n ); + } +} + +CegConjecture::CegConjecture(QuantifiersEngine* qe) + : d_qe(qe), + d_ceg_si(new CegConjectureSingleInv(qe, this)), + d_ceg_pbe(new CegConjecturePbe(qe, this)), + d_ceg_proc(new CegConjectureProcess(qe)), + d_ceg_gc(new CegGrammarConstructor(qe, this)), + d_refine_count(0), + d_syntax_guided(false) {} + +CegConjecture::~CegConjecture() {} + +void CegConjecture::assign( Node q ) { + Assert( d_embed_quant.isNull() ); + Assert( q.getKind()==FORALL ); + Trace("cegqi") << "CegConjecture : assign : " << q << std::endl; + d_quant = q; + + // pre-simplify the quantified formula based on the process utility + d_simp_quant = d_ceg_proc->preSimplify(d_quant); + + std::map< Node, Node > templates; + std::map< Node, Node > templates_arg; + //register with single invocation if applicable + if (d_qe->getQuantAttributes()->isSygus(q)) + { + d_ceg_si->initialize(d_simp_quant); + d_simp_quant = d_ceg_si->getSimplifiedConjecture(); + // carry the templates + for( unsigned i=0; i<q[0].getNumChildren(); i++ ){ + Node v = q[0][i]; + Node templ = d_ceg_si->getTemplate(v); + if( !templ.isNull() ){ + templates[v] = templ; + templates_arg[v] = d_ceg_si->getTemplateArg(v); + } + } + } + + // post-simplify the quantified formula based on the process utility + d_simp_quant = d_ceg_proc->postSimplify(d_simp_quant); + + // finished simplifying the quantified formula at this point + + // convert to deep embedding and finalize single invocation here + d_embed_quant = d_ceg_gc->process(d_simp_quant, templates, templates_arg); + Trace("cegqi") << "CegConjecture : converted to embedding : " << d_embed_quant << std::endl; + + // we now finalize the single invocation module, based on the syntax restrictions + if (d_qe->getQuantAttributes()->isSygus(q)) + { + d_ceg_si->finishInit( d_ceg_gc->isSyntaxRestricted(), d_ceg_gc->hasSyntaxITE() ); + } + + Assert( d_candidates.empty() ); + std::vector< Node > vars; + for( unsigned i=0; i<d_embed_quant[0].getNumChildren(); i++ ){ + vars.push_back( d_embed_quant[0][i] ); + Node e = NodeManager::currentNM()->mkSkolem( "e", d_embed_quant[0][i].getType() ); + d_candidates.push_back( e ); + } + Trace("cegqi") << "Base quantified formula is : " << d_embed_quant << std::endl; + //construct base instantiation + d_base_inst = Rewriter::rewrite(d_qe->getInstantiate()->getInstantiation( + d_embed_quant, vars, d_candidates)); + Trace("cegqi") << "Base instantiation is : " << d_base_inst << std::endl; + d_base_body = d_base_inst; + if (d_base_body.getKind() == NOT && d_base_body[0].getKind() == FORALL) + { + for (const Node& v : d_base_body[0][0]) + { + d_base_vars.push_back(v); + } + d_base_body = d_base_body[0][1]; + } + + // register this term with sygus database and other utilities that impact + // the enumerative sygus search + std::vector< Node > guarded_lemmas; + if( !isSingleInvocation() ){ + d_ceg_proc->initialize(d_base_inst, d_candidates); + if( options::sygusPbe() ){ + d_ceg_pbe->initialize(d_base_inst, d_candidates, guarded_lemmas); + } else { + for (unsigned i = 0; i < d_candidates.size(); i++) { + Node e = d_candidates[i]; + d_qe->getTermDatabaseSygus()->registerEnumerator(e, e, this); + } + } + } + + if (d_qe->getQuantAttributes()->isSygus(q)) + { + collectDisjuncts( d_base_inst, d_base_disj ); + Trace("cegqi") << "Conjecture has " << d_base_disj.size() << " disjuncts." << std::endl; + //store the inner variables for each disjunct + for( unsigned j=0; j<d_base_disj.size(); j++ ){ + Trace("cegqi") << " " << j << " : " << d_base_disj[j] << std::endl; + d_inner_vars_disj.push_back( std::vector< Node >() ); + //if the disjunct is an existential, store it + if( d_base_disj[j].getKind()==NOT && d_base_disj[j][0].getKind()==FORALL ){ + for( unsigned k=0; k<d_base_disj[j][0][0].getNumChildren(); k++ ){ + d_inner_vars.push_back( d_base_disj[j][0][0][k] ); + d_inner_vars_disj[j].push_back( d_base_disj[j][0][0][k] ); + } + } + } + d_syntax_guided = true; + } + else if (d_qe->getQuantAttributes()->isSynthesis(q)) + { + d_syntax_guided = false; + }else{ + Assert( false ); + } + + // initialize the guard + if( !d_syntax_guided ){ + if( d_nsg_guard.isNull() ){ + d_nsg_guard = Rewriter::rewrite( NodeManager::currentNM()->mkSkolem( "G", NodeManager::currentNM()->booleanType() ) ); + d_nsg_guard = d_qe->getValuation().ensureLiteral( d_nsg_guard ); + AlwaysAssert( !d_nsg_guard.isNull() ); + d_qe->getOutputChannel().requirePhase( d_nsg_guard, true ); + // negated base as a guarded lemma + guarded_lemmas.push_back( d_base_inst.negate() ); + } + }else if( d_ceg_si->getGuard().isNull() ){ + std::vector< Node > lems; + d_ceg_si->getInitialSingleInvLemma( lems ); + for( unsigned i=0; i<lems.size(); i++ ){ + Trace("cegqi-lemma") << "Cegqi::Lemma : single invocation " << i << " : " << lems[i] << std::endl; + d_qe->getOutputChannel().lemma( lems[i] ); + if( Trace.isOn("cegqi-debug") ){ + Node rlem = Rewriter::rewrite( lems[i] ); + Trace("cegqi-debug") << "...rewritten : " << rlem << std::endl; + } + } + } + Assert( !getGuard().isNull() ); + Node gneg = getGuard().negate(); + for( unsigned i=0; i<guarded_lemmas.size(); i++ ){ + Node lem = NodeManager::currentNM()->mkNode( OR, gneg, guarded_lemmas[i] ); + Trace("cegqi-lemma") << "Cegqi::Lemma : initial (guarded) lemma : " << lem << std::endl; + d_qe->getOutputChannel().lemma( lem ); + } + + // assign the cegis sampler if applicable + if (options::cegisSample() != CEGIS_SAMPLE_NONE) + { + Trace("cegis-sample") << "Initialize sampler for " << d_base_body << "..." + << std::endl; + TypeNode bt = d_base_body.getType(); + d_cegis_sampler.initialize(bt, d_base_vars, options::sygusSamples()); + } + + Trace("cegqi") << "...finished, single invocation = " << isSingleInvocation() << std::endl; +} + +Node CegConjecture::getGuard() { + return !d_syntax_guided ? d_nsg_guard : d_ceg_si->getGuard(); +} + +bool CegConjecture::isSingleInvocation() const { + return d_ceg_si->isSingleInvocation(); +} + +bool CegConjecture::needsCheck( std::vector< Node >& lem ) { + if( isSingleInvocation() && !d_ceg_si->needsCheck() ){ + return false; + }else{ + bool value; + Assert( !getGuard().isNull() ); + // non or fully single invocation : look at guard only + if( d_qe->getValuation().hasSatValue( getGuard(), value ) ) { + if( !value ){ + Trace("cegqi-engine-debug") << "Conjecture is infeasible." << std::endl; + return false; + } + }else{ + Assert( false ); + } + return true; + } +} + + +void CegConjecture::doSingleInvCheck(std::vector< Node >& lems) { + if( d_ceg_si!=NULL ){ + d_ceg_si->check(lems); + } +} + +void CegConjecture::doBasicCheck(std::vector< Node >& lems) { + std::vector< Node > model_terms; + std::vector< Node > clist; + getCandidateList( clist, true ); + Assert( clist.size()==d_quant[0].getNumChildren() ); + getModelValues( clist, model_terms ); + if (d_qe->getInstantiate()->addInstantiation(d_quant, model_terms)) + { + //record the instantiation + recordInstantiation( model_terms ); + }else{ + Assert( false ); + } +} + +bool CegConjecture::needsRefinement() { + return !d_ce_sk.empty(); +} + +void CegConjecture::getCandidateList( std::vector< Node >& clist, bool forceOrig ) { + if( d_ceg_pbe->isPbe() && !forceOrig ){ + d_ceg_pbe->getCandidateList( d_candidates, clist ); + }else{ + clist.insert( clist.end(), d_candidates.begin(), d_candidates.end() ); + } +} + +bool CegConjecture::constructCandidates( std::vector< Node >& clist, std::vector< Node >& model_values, std::vector< Node >& candidate_values, + std::vector< Node >& lems ) { + Assert( clist.size()==model_values.size() ); + if( d_ceg_pbe->isPbe() ){ + return d_ceg_pbe->constructCandidates( clist, model_values, d_candidates, candidate_values, lems ); + }else{ + Assert( model_values.size()==d_candidates.size() ); + candidate_values.insert( candidate_values.end(), model_values.begin(), model_values.end() ); + } + return true; +} + +void CegConjecture::doCheck(std::vector< Node >& lems, std::vector< Node >& model_values) { + std::vector< Node > clist; + getCandidateList( clist ); + std::vector< Node > c_model_values; + Trace("cegqi-check") << "CegConjuncture : check, build candidates..." << std::endl; + bool constructed_cand = constructCandidates( clist, model_values, c_model_values, lems ); + + //must get a counterexample to the value of the current candidate + Node inst; + if( constructed_cand ){ + if( Trace.isOn("cegqi-check") ){ + Trace("cegqi-check") << "CegConjuncture : check candidate : " << std::endl; + for( unsigned i=0; i<c_model_values.size(); i++ ){ + Trace("cegqi-check") << " " << i << " : " << d_candidates[i] << " -> " << c_model_values[i] << std::endl; + } + } + Assert( c_model_values.size()==d_candidates.size() ); + inst = d_base_inst.substitute( d_candidates.begin(), d_candidates.end(), c_model_values.begin(), c_model_values.end() ); + }else{ + inst = d_base_inst; + } + + //check whether we will run CEGIS on inner skolem variables + bool sk_refine = ( !isGround() || d_refine_count==0 ) && ( !d_ceg_pbe->isPbe() || constructed_cand ); + if( sk_refine ){ + if (options::cegisSample() == CEGIS_SAMPLE_TRUST) + { + // we have that the current candidate passed a sample test + // since we trust sampling in this mode, we assert there is no + // counterexample to the conjecture here. + NodeManager* nm = NodeManager::currentNM(); + Node lem = nm->mkNode(OR, d_quant.negate(), nm->mkConst(false)); + lem = getStreamGuardedLemma(lem); + lems.push_back(lem); + recordInstantiation(c_model_values); + return; + } + Assert( d_ce_sk.empty() ); + d_ce_sk.push_back( std::vector< Node >() ); + }else{ + if( !constructed_cand ){ + return; + } + } + + std::vector< Node > ic; + ic.push_back( d_quant.negate() ); + std::vector< Node > d; + collectDisjuncts( inst, d ); + Assert( d.size()==d_base_disj.size() ); + //immediately skolemize inner existentials + for( unsigned i=0; i<d.size(); i++ ){ + Node dr = Rewriter::rewrite( d[i] ); + if( dr.getKind()==NOT && dr[0].getKind()==FORALL ){ + if( constructed_cand ){ + ic.push_back(d_qe->getSkolemize()->getSkolemizedBody(dr[0]).negate()); + } + if( sk_refine ){ + Assert( !isGround() ); + d_ce_sk.back().push_back( dr[0] ); + } + }else{ + if( constructed_cand ){ + ic.push_back( dr ); + if( !d_inner_vars_disj[i].empty() ){ + Trace("cegqi-debug") << "*** quantified disjunct : " << d[i] << " simplifies to " << dr << std::endl; + } + } + if( sk_refine ){ + d_ce_sk.back().push_back( Node::null() ); + } + } + } + if( constructed_cand ){ + Node lem = NodeManager::currentNM()->mkNode( OR, ic ); + lem = Rewriter::rewrite( lem ); + //eagerly unfold applications of evaluation function + if( options::sygusDirectEval() ){ + Trace("cegqi-debug") << "pre-unfold counterexample : " << lem << std::endl; + std::map< Node, Node > visited_n; + lem = d_qe->getTermDatabaseSygus()->getEagerUnfold( lem, visited_n ); + } + lem = getStreamGuardedLemma(lem); + lems.push_back( lem ); + recordInstantiation( c_model_values ); + } +} + +void CegConjecture::doRefine( std::vector< Node >& lems ){ + Assert( lems.empty() ); + Assert( d_ce_sk.size()==1 ); + + //first, make skolem substitution + Trace("cegqi-refine") << "doRefine : construct skolem substitution..." << std::endl; + std::vector< Node > sk_vars; + std::vector< Node > sk_subs; + //collect the substitution over all disjuncts + for( unsigned k=0; k<d_ce_sk[0].size(); k++ ){ + Node ce_q = d_ce_sk[0][k]; + if( !ce_q.isNull() ){ + Assert( !d_inner_vars_disj[k].empty() ); + std::vector<Node> skolems; + d_qe->getSkolemize()->getSkolemConstants(ce_q, skolems); + Assert(d_inner_vars_disj[k].size() == skolems.size()); + std::vector< Node > model_values; + getModelValues(skolems, model_values); + sk_vars.insert( sk_vars.end(), d_inner_vars_disj[k].begin(), d_inner_vars_disj[k].end() ); + sk_subs.insert( sk_subs.end(), model_values.begin(), model_values.end() ); + }else{ + if( !d_inner_vars_disj[k].empty() ){ + //denegrate case : quantified disjunct was trivially true and does not need to be refined + //add trivial substitution (in case we need substitution for previous cex's) + for( unsigned i=0; i<d_inner_vars_disj[k].size(); i++ ){ + sk_vars.push_back( d_inner_vars_disj[k][i] ); + sk_subs.push_back( getModelValue( d_inner_vars_disj[k][i] ) ); // will return dummy value + } + } + } + } + + //for conditional evaluation + std::vector< Node > lem_c; + Assert( d_ce_sk[0].size()==d_base_disj.size() ); + std::vector< Node > inst_cond_c; + Trace("cegqi-refine") << "doRefine : Construct refinement lemma..." << std::endl; + for( unsigned k=0; k<d_ce_sk[0].size(); k++ ){ + Node ce_q = d_ce_sk[0][k]; + Trace("cegqi-refine-debug") << " For counterexample point, disjunct " << k << " : " << ce_q << " " << d_base_disj[k] << std::endl; + Node c_disj; + if( !ce_q.isNull() ){ + Assert( d_base_disj[k].getKind()==kind::NOT && d_base_disj[k][0].getKind()==kind::FORALL ); + c_disj = d_base_disj[k][0][1]; + }else{ + if( d_inner_vars_disj[k].empty() ){ + c_disj = d_base_disj[k].negate(); + }else{ + //denegrate case : quantified disjunct was trivially true and does not need to be refined + Trace("cegqi-refine-debug") << "*** skip " << d_base_disj[k] << std::endl; + } + } + if( !c_disj.isNull() ){ + //compute the body, inst_cond + //standard CEGIS refinement : plug in values, assert that d_candidates must satisfy entire specification + lem_c.push_back( c_disj ); + } + } + Assert( sk_vars.size()==sk_subs.size() ); + + Node base_lem = lem_c.size()==1 ? lem_c[0] : NodeManager::currentNM()->mkNode( AND, lem_c ); + + Trace("cegqi-refine") << "doRefine : construct and finalize lemmas..." << std::endl; + + + base_lem = base_lem.substitute( sk_vars.begin(), sk_vars.end(), sk_subs.begin(), sk_subs.end() ); + base_lem = Rewriter::rewrite( base_lem ); + d_refinement_lemmas.push_back(base_lem); + + Node lem = + NodeManager::currentNM()->mkNode(OR, getGuard().negate(), base_lem); + lems.push_back( lem ); + + d_ce_sk.clear(); +} + +void CegConjecture::preregisterConjecture( Node q ) { + d_ceg_si->preregisterConjecture( q ); +} + +void CegConjecture::getModelValues( std::vector< Node >& n, std::vector< Node >& v ) { + Trace("cegqi-engine") << " * Value is : "; + for( unsigned i=0; i<n.size(); i++ ){ + Node nv = getModelValue( n[i] ); + v.push_back( nv ); + if( Trace.isOn("cegqi-engine") ){ + TypeNode tn = nv.getType(); + Trace("cegqi-engine") << n[i] << " -> "; + std::stringstream ss; + Printer::getPrinter(options::outputLanguage())->toStreamSygus(ss, nv); + Trace("cegqi-engine") << ss.str() << " "; + if (Trace.isOn("cegqi-engine-rr")) + { + Node bv = d_qe->getTermDatabaseSygus()->sygusToBuiltin(nv, tn); + bv = Rewriter::rewrite(bv); + Trace("cegqi-engine-rr") << " -> " << bv << std::endl; + } + } + Assert( !nv.isNull() ); + } + Trace("cegqi-engine") << std::endl; +} + +Node CegConjecture::getModelValue( Node n ) { + Trace("cegqi-mv") << "getModelValue for : " << n << std::endl; + return d_qe->getModel()->getValue( n ); +} + +void CegConjecture::debugPrint( const char * c ) { + Trace(c) << "Synthesis conjecture : " << d_embed_quant << std::endl; + Trace(c) << " * Candidate program/output symbol : "; + for( unsigned i=0; i<d_candidates.size(); i++ ){ + Trace(c) << d_candidates[i] << " "; + } + Trace(c) << std::endl; + Trace(c) << " * Candidate ce skolems : "; + for( unsigned i=0; i<d_ce_sk.size(); i++ ){ + Trace(c) << d_ce_sk[i] << " "; + } +} + +Node CegConjecture::getCurrentStreamGuard() const { + if( d_stream_guards.empty() ){ + return Node::null(); + }else{ + return d_stream_guards.back(); + } +} + +Node CegConjecture::getStreamGuardedLemma(Node n) const +{ + if (options::sygusStream()) + { + // if we are in streaming mode, we guard with the current stream guard + Node csg = getCurrentStreamGuard(); + Assert(!csg.isNull()); + return NodeManager::currentNM()->mkNode(kind::OR, csg.negate(), n); + } + return n; +} + +Node CegConjecture::getNextDecisionRequest( unsigned& priority ) { + // first, must try the guard + // which denotes "this conjecture is feasible" + Node feasible_guard = getGuard(); + bool value; + if( !d_qe->getValuation().hasSatValue( feasible_guard, value ) ) { + priority = 0; + return feasible_guard; + }else{ + if( value ){ + // the conjecture is feasible + if( options::sygusStream() ){ + Assert( !isSingleInvocation() ); + // if we are in sygus streaming mode, then get the "next guard" + // which denotes "we have not yet generated the next solution to the conjecture" + Node curr_stream_guard = getCurrentStreamGuard(); + bool needs_new_stream_guard = false; + if( curr_stream_guard.isNull() ){ + needs_new_stream_guard = true; + }else{ + // check the polarity of the guard + if( !d_qe->getValuation().hasSatValue( curr_stream_guard, value ) ) { + priority = 0; + return curr_stream_guard; + }else{ + if( !value ){ + Trace("cegqi-debug") << "getNextDecision : we have a new solution since stream guard was propagated false: " << curr_stream_guard << std::endl; + // we have generated a solution, print it + // get the current output stream + // this output stream should coincide with wherever --dump-synth is output on + Options& nodeManagerOptions = NodeManager::currentNM()->getOptions(); + printSynthSolution( *nodeManagerOptions.getOut(), false ); + // need to make the next stream guard + needs_new_stream_guard = true; + + // We will not refine the current candidate solution since it is a solution + // thus, we clear information regarding the current refinement + d_ce_sk.clear(); + // However, we need to exclude the current solution using an explicit refinement + // so that we proceed to the next solution. + std::vector< Node > clist; + getCandidateList( clist ); + Trace("cegqi-debug") << "getNextDecision : solution was : " << std::endl; + std::vector< Node > exp; + for( unsigned i=0; i<clist.size(); i++ ){ + Node cprog = clist[i]; + Node sol = cprog; + if( !d_cinfo[cprog].d_inst.empty() ){ + sol = d_cinfo[cprog].d_inst.back(); + // add to explanation of exclusion + d_qe->getTermDatabaseSygus() + ->getExplain() + ->getExplanationForConstantEquality(cprog, sol, exp); + } + Trace("cegqi-debug") << " " << cprog << " -> " << sol << std::endl; + } + Assert( !exp.empty() ); + Node exc_lem = exp.size()==1 ? exp[0] : NodeManager::currentNM()->mkNode( kind::AND, exp ); + exc_lem = exc_lem.negate(); + Trace("cegqi-lemma") << "Cegqi::Lemma : stream exclude current solution : " << exc_lem << std::endl; + d_qe->getOutputChannel().lemma( exc_lem ); + } + } + } + if( needs_new_stream_guard ){ + // generate a new stream guard + curr_stream_guard = Rewriter::rewrite( NodeManager::currentNM()->mkSkolem( "G_Stream", NodeManager::currentNM()->booleanType() ) ); + curr_stream_guard = d_qe->getValuation().ensureLiteral( curr_stream_guard ); + AlwaysAssert( !curr_stream_guard.isNull() ); + d_qe->getOutputChannel().requirePhase( curr_stream_guard, true ); + d_stream_guards.push_back( curr_stream_guard ); + Trace("cegqi-debug") << "getNextDecision : allocate new stream guard : " << curr_stream_guard << std::endl; + // return it as a decision + priority = 0; + return curr_stream_guard; + } + } + }else{ + Trace("cegqi-debug") << "getNextDecision : conjecture is infeasible." << std::endl; + } + } + return Node::null(); +} + +void CegConjecture::printSynthSolution( std::ostream& out, bool singleInvocation ) { + Trace("cegqi-debug") << "Printing synth solution..." << std::endl; + Assert( d_quant[0].getNumChildren()==d_embed_quant[0].getNumChildren() ); + std::vector<Node> sols; + std::vector<int> statuses; + getSynthSolutionsInternal(sols, statuses, singleInvocation); + for (unsigned i = 0, size = d_embed_quant[0].getNumChildren(); i < size; i++) + { + Node sol = sols[i]; + if (!sol.isNull()) + { + Node prog = d_embed_quant[0][i]; + int status = statuses[i]; + TypeNode tn = prog.getType(); + const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype(); + std::stringstream ss; + ss << prog; + std::string f(ss.str()); + f.erase(f.begin()); + out << "(define-fun " << f << " "; + if( dt.getSygusVarList().isNull() ){ + out << "() "; + }else{ + out << dt.getSygusVarList() << " "; + } + out << dt.getSygusType() << " "; + if( status==0 ){ + out << sol; + }else{ + Printer::getPrinter(options::outputLanguage())->toStreamSygus(out, sol); + } + out << ")" << std::endl; + CegInstantiation* cei = d_qe->getCegInstantiation(); + ++(cei->d_statistics.d_solutions); + + if (status != 0 && options::sygusRewSynth()) + { + TermDbSygus* sygusDb = d_qe->getTermDatabaseSygus(); + std::map<Node, SygusSamplerExt>::iterator its = d_sampler.find(prog); + if (its == d_sampler.end()) + { + d_sampler[prog].initializeSygusExt( + d_qe, prog, options::sygusSamples()); + its = d_sampler.find(prog); + } + Node solb = sygusDb->sygusToBuiltin(sol, prog.getType()); + Node eq_sol = its->second.registerTerm(solb); + // eq_sol is a candidate solution that is equivalent to sol + if (eq_sol != solb) + { + ++(cei->d_statistics.d_candidate_rewrites); + if (!eq_sol.isNull()) + { + // Terms solb and eq_sol are equivalent under sample points but do + // not rewrite to the same term. Hence, this indicates a candidate + // rewrite. + out << "(candidate-rewrite " << solb << " " << eq_sol << ")" + << std::endl; + ++(cei->d_statistics.d_candidate_rewrites_print); + // debugging information + if (Trace.isOn("sygus-rr-debug")) + { + ExtendedRewriter* er = sygusDb->getExtRewriter(); + Node solbr = er->extendedRewrite(solb); + Node eq_solr = er->extendedRewrite(eq_sol); + Trace("sygus-rr-debug") + << "; candidate #1 ext-rewrites to: " << solbr << std::endl; + Trace("sygus-rr-debug") + << "; candidate #2 ext-rewrites to: " << eq_solr << std::endl; + } + } + } + } + } + } +} + +void CegConjecture::getSynthSolutions(std::map<Node, Node>& sol_map, + bool singleInvocation) +{ + NodeManager* nm = NodeManager::currentNM(); + TermDbSygus* sygusDb = d_qe->getTermDatabaseSygus(); + std::vector<Node> sols; + std::vector<int> statuses; + getSynthSolutionsInternal(sols, statuses, singleInvocation); + for (unsigned i = 0, size = d_embed_quant[0].getNumChildren(); i < size; i++) + { + Node sol = sols[i]; + int status = statuses[i]; + // get the builtin solution + Node bsol = sol; + if (status != 0) + { + // convert sygus to builtin here + bsol = sygusDb->sygusToBuiltin(sol, sol.getType()); + } + // convert to lambda + TypeNode tn = d_embed_quant[0][i].getType(); + const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype(); + Node bvl = Node::fromExpr(dt.getSygusVarList()); + if (!bvl.isNull()) + { + bsol = nm->mkNode(LAMBDA, bvl, bsol); + } + // store in map + Node fvar = d_quant[0][i]; + Assert(fvar.getType() == bsol.getType()); + sol_map[fvar] = bsol; + } +} + +void CegConjecture::getSynthSolutionsInternal(std::vector<Node>& sols, + std::vector<int>& statuses, + bool singleInvocation) +{ + for (unsigned i = 0, size = d_embed_quant[0].getNumChildren(); i < size; i++) + { + Node prog = d_embed_quant[0][i]; + Trace("cegqi-debug") << " get solution for " << prog << std::endl; + TypeNode tn = prog.getType(); + Assert(tn.isDatatype()); + // get the solution + Node sol; + int status = -1; + if (singleInvocation) + { + Assert(d_ceg_si != NULL); + sol = d_ceg_si->getSolution(i, tn, status, true); + if (!sol.isNull()) + { + sol = sol.getKind() == LAMBDA ? sol[1] : sol; + } + } + else + { + Node cprog = getCandidate(i); + if (!d_cinfo[cprog].d_inst.empty()) + { + // the solution is just the last instantiated term + sol = d_cinfo[cprog].d_inst.back(); + status = 1; + + // check if there was a template + Node sf = d_quant[0][i]; + Node templ = d_ceg_si->getTemplate(sf); + if (!templ.isNull()) + { + Trace("cegqi-inv-debug") + << sf << " used template : " << templ << std::endl; + // if it was not embedded into the grammar + if (!options::sygusTemplEmbedGrammar()) + { + TNode templa = d_ceg_si->getTemplateArg(sf); + // make the builtin version of the full solution + TermDbSygus* sygusDb = d_qe->getTermDatabaseSygus(); + sol = sygusDb->sygusToBuiltin(sol, sol.getType()); + Trace("cegqi-inv") << "Builtin version of solution is : " << sol + << ", type : " << sol.getType() << std::endl; + TNode tsol = sol; + sol = templ.substitute(templa, tsol); + Trace("cegqi-inv-debug") << "With template : " << sol << std::endl; + sol = Rewriter::rewrite(sol); + Trace("cegqi-inv-debug") << "Simplified : " << sol << std::endl; + // now, reconstruct to the syntax + sol = d_ceg_si->reconstructToSyntax(sol, tn, status, true); + sol = sol.getKind() == LAMBDA ? sol[1] : sol; + Trace("cegqi-inv-debug") + << "Reconstructed to syntax : " << sol << std::endl; + } + else + { + Trace("cegqi-inv-debug") + << "...was embedding into grammar." << std::endl; + } + } + else + { + Trace("cegqi-inv-debug") + << sf << " did not use template" << std::endl; + } + } + else + { + Trace("cegqi-warn") << "WARNING : No recorded instantiations for " + "syntax-guided solution!" + << std::endl; + } + } + sols.push_back(sol); + statuses.push_back(status); + } +} + +Node CegConjecture::getSymmetryBreakingPredicate( + Node x, Node e, TypeNode tn, unsigned tindex, unsigned depth) +{ + std::vector<Node> sb_lemmas; + + // based on simple preprocessing + Node ppred = + d_ceg_proc->getSymmetryBreakingPredicate(x, e, tn, tindex, depth); + if (!ppred.isNull()) + { + sb_lemmas.push_back(ppred); + } + + // other static conjecture-dependent symmetry breaking goes here + + if (!sb_lemmas.empty()) + { + return sb_lemmas.size() == 1 + ? sb_lemmas[0] + : NodeManager::currentNM()->mkNode(kind::AND, sb_lemmas); + } + else + { + return Node::null(); + } +} + +bool CegConjecture::sampleAddRefinementLemma(std::vector<Node>& vals, + std::vector<Node>& lems) +{ + if (Trace.isOn("cegis-sample")) + { + Trace("cegis-sample") << "Check sampling for candidate solution" + << std::endl; + for (unsigned i = 0, size = vals.size(); i < size; i++) + { + Trace("cegis-sample") + << " " << d_candidates[i] << " -> " << vals[i] << std::endl; + } + } + Assert(vals.size() == d_candidates.size()); + Node sbody = d_base_body.substitute( + d_candidates.begin(), d_candidates.end(), vals.begin(), vals.end()); + Trace("cegis-sample-debug") << "Sample " << sbody << std::endl; + // do eager unfolding + std::map<Node, Node> visited_n; + sbody = d_qe->getTermDatabaseSygus()->getEagerUnfold(sbody, visited_n); + Trace("cegis-sample") << "Sample (after unfolding): " << sbody << std::endl; + + NodeManager* nm = NodeManager::currentNM(); + for (unsigned i = 0, size = d_cegis_sampler.getNumSamplePoints(); i < size; + i++) + { + if (d_cegis_sample_refine.find(i) == d_cegis_sample_refine.end()) + { + Node ev = d_cegis_sampler.evaluate(sbody, i); + Trace("cegis-sample-debug") + << "...evaluate point #" << i << " to " << ev << std::endl; + Assert(ev.isConst()); + Assert(ev.getType().isBoolean()); + if (!ev.getConst<bool>()) + { + Trace("cegis-sample-debug") << "...false for point #" << i << std::endl; + // mark this as a CEGIS point (no longer sampled) + d_cegis_sample_refine.insert(i); + std::vector<Node> vars; + std::vector<Node> pt; + d_cegis_sampler.getSamplePoint(i, vars, pt); + Assert(d_base_vars.size() == pt.size()); + Node rlem = d_base_body.substitute( + d_base_vars.begin(), d_base_vars.end(), pt.begin(), pt.end()); + rlem = Rewriter::rewrite(rlem); + if (std::find( + d_refinement_lemmas.begin(), d_refinement_lemmas.end(), rlem) + == d_refinement_lemmas.end()) + { + if (Trace.isOn("cegis-sample")) + { + Trace("cegis-sample") << " false for point #" << i << " : "; + for (const Node& cn : pt) + { + Trace("cegis-sample") << cn << " "; + } + Trace("cegis-sample") << std::endl; + } + Trace("cegqi-engine") << " *** Refine by sampling" << std::endl; + d_refinement_lemmas.push_back(rlem); + // if trust, we are not interested in sending out refinement lemmas + if (options::cegisSample() != CEGIS_SAMPLE_TRUST) + { + Node lem = nm->mkNode(OR, getGuard().negate(), rlem); + lems.push_back(lem); + } + return true; + } + else + { + Trace("cegis-sample-debug") << "...duplicate." << std::endl; + } + } + } + } + return false; +} + +}/* namespace CVC4::theory::quantifiers */ +}/* namespace CVC4::theory */ +}/* namespace CVC4 */ diff --git a/src/theory/quantifiers/sygus/ce_guided_conjecture.h b/src/theory/quantifiers/sygus/ce_guided_conjecture.h new file mode 100644 index 000000000..1ef8fef10 --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_conjecture.h @@ -0,0 +1,283 @@ +/********************* */ +/*! \file ce_guided_conjecture.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief class that encapsulates counterexample-guided instantiation + ** techniques for a single SyGuS synthesis conjecture + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_CONJECTURE_H +#define __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_CONJECTURE_H + +#include <memory> + +#include "theory/quantifiers/sygus/sygus_pbe.h" +#include "theory/quantifiers/sygus/ce_guided_single_inv.h" +#include "theory/quantifiers/sygus/sygus_grammar_cons.h" +#include "theory/quantifiers/sygus/sygus_process_conj.h" +#include "theory/quantifiers/sygus_sampler.h" +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +/** a synthesis conjecture + * This class implements approaches for a synthesis conecjture, given by data + * member d_quant. + * This includes both approaches for synthesis in Reynolds et al CAV 2015. It + * determines which approach and optimizations are applicable to the + * conjecture, and has interfaces for implementing them. + */ +class CegConjecture { +public: + CegConjecture( QuantifiersEngine * qe ); + ~CegConjecture(); + /** get original version of conjecture */ + Node getConjecture() { return d_quant; } + /** get deep embedding version of conjecture */ + Node getEmbeddedConjecture() { return d_embed_quant; } + /** get next decision request */ + Node getNextDecisionRequest( unsigned& priority ); + + //-------------------------------for counterexample-guided check/refine + /** increment the number of times we have successfully done candidate + * refinement */ + void incrementRefineCount() { d_refine_count++; } + /** whether the conjecture is waiting for a call to doCheck below */ + bool needsCheck( std::vector< Node >& lem ); + /** whether the conjecture is waiting for a call to doRefine below */ + bool needsRefinement(); + /** get the list of candidates */ + void getCandidateList( std::vector< Node >& clist, bool forceOrig = false ); + /** do single invocation check + * This updates Gamma for an iteration of step 2 of Figure 1 of Reynolds et al CAV 2015. + */ + void doSingleInvCheck(std::vector< Node >& lems); + /** do syntax-guided enumerative check + * This is step 2(a) of Figure 3 of Reynolds et al CAV 2015. + */ + void doCheck(std::vector< Node >& lems, std::vector< Node >& model_values); + /** do basic check + * This is called for non-SyGuS synthesis conjectures + */ + void doBasicCheck(std::vector< Node >& lems); + /** do refinement + * This is step 2(b) of Figure 3 of Reynolds et al CAV 2015. + */ + void doRefine(std::vector< Node >& lems); + //-------------------------------end for counterexample-guided check/refine + /** + * prints the synthesis solution to output stream out. + * + * singleInvocation : set to true if we should consult the single invocation + * module to get synthesis solutions. + */ + void printSynthSolution( std::ostream& out, bool singleInvocation ); + /** get synth solutions + * + * This returns a map from function-to-synthesize variables to their + * builtin solution, which has the same type. For example, for synthesis + * conjecture exists f. forall x. f( x )>x, this function may return the map + * containing the entry: + * f -> (lambda x. x+1) + * + * singleInvocation : set to true if we should consult the single invocation + * module to get synthesis solutions. + */ + void getSynthSolutions(std::map<Node, Node>& sol_map, bool singleInvocation); + /** get guard, this is "G" in Figure 3 of Reynolds et al CAV 2015 */ + Node getGuard(); + /** is ground */ + bool isGround() { return d_inner_vars.empty(); } + /** does this conjecture correspond to a syntax-guided synthesis input */ + bool isSyntaxGuided() const { return d_syntax_guided; } + /** are we using single invocation techniques */ + bool isSingleInvocation() const; + /** preregister conjecture + * This is used as a heuristic for solution reconstruction, so that we + * remember expressions in the conjecture before preprocessing, since they + * may be helpful during solution reconstruction (Figure 5 of Reynolds et al CAV 2015) + */ + void preregisterConjecture( Node q ); + /** assign conjecture q to this class */ + void assign( Node q ); + /** has a conjecture been assigned to this class */ + bool isAssigned() { return !d_embed_quant.isNull(); } + /** get model values for terms n, store in vector v */ + void getModelValues( std::vector< Node >& n, std::vector< Node >& v ); + /** get model value for term n */ + Node getModelValue( Node n ); + + //-----------------------------------refinement lemmas + /** get number of refinement lemmas we have added so far */ + unsigned getNumRefinementLemmas() { return d_refinement_lemmas.size(); } + /** get refinement lemma + * + * If d_embed_quant is forall d. exists y. P( d, y ), then a refinement + * lemma is one of the form ~P( d_candidates, c ) for some c. + */ + Node getRefinementLemma( unsigned i ) { return d_refinement_lemmas[i]; } + /** sample add refinement lemma + * + * This function will check if there is a sample point in d_sampler that + * refutes the candidate solution (d_quant_vars->vals). If so, it adds a + * refinement lemma to the lists d_refinement_lemmas that corresponds to that + * sample point, and adds a lemma to lems if cegisSample mode is not trust. + */ + bool sampleAddRefinementLemma(std::vector<Node>& vals, + std::vector<Node>& lems); + //-----------------------------------end refinement lemmas + + /** get program by examples utility */ + CegConjecturePbe* getPbe() { return d_ceg_pbe.get(); } + /** get utility for static preprocessing and analysis of conjectures */ + CegConjectureProcess* getProcess() { return d_ceg_proc.get(); } + /** get the symmetry breaking predicate for type */ + Node getSymmetryBreakingPredicate( + Node x, Node e, TypeNode tn, unsigned tindex, unsigned depth); + /** print out debug information about this conjecture */ + void debugPrint( const char * c ); +private: + /** reference to quantifier engine */ + QuantifiersEngine * d_qe; + /** single invocation utility */ + std::unique_ptr<CegConjectureSingleInv> d_ceg_si; + /** program by examples utility */ + std::unique_ptr<CegConjecturePbe> d_ceg_pbe; + /** utility for static preprocessing and analysis of conjectures */ + std::unique_ptr<CegConjectureProcess> d_ceg_proc; + /** grammar utility */ + std::unique_ptr<CegGrammarConstructor> d_ceg_gc; + /** list of constants for quantified formula + * The outer Skolems for the negation of d_embed_quant. + */ + std::vector< Node > d_candidates; + /** base instantiation + * If d_embed_quant is forall d. exists y. P( d, y ), then + * this is the formula exists y. P( d_candidates, y ). + */ + Node d_base_inst; + /** If d_base_inst is exists y. P( d, y ), then this is y. */ + std::vector<Node> d_base_vars; + /** + * If d_base_inst is exists y. P( d, y ), then this is the formula + * P( d_candidates, y ). + */ + Node d_base_body; + /** expand base inst to disjuncts */ + std::vector< Node > d_base_disj; + /** list of variables on inner quantification */ + std::vector< Node > d_inner_vars; + std::vector< std::vector< Node > > d_inner_vars_disj; + /** current extential quantifeirs whose couterexamples we must refine */ + std::vector< std::vector< Node > > d_ce_sk; + + //-----------------------------------refinement lemmas + /** refinement lemmas */ + std::vector< Node > d_refinement_lemmas; + //-----------------------------------end refinement lemmas + + /** the asserted (negated) conjecture */ + Node d_quant; + /** (negated) conjecture after simplification */ + Node d_simp_quant; + /** (negated) conjecture after simplification, conversion to deep embedding */ + Node d_embed_quant; + /** candidate information */ + class CandidateInfo { + public: + CandidateInfo(){} + /** list of terms we have instantiated candidates with */ + std::vector< Node > d_inst; + }; + std::map< Node, CandidateInfo > d_cinfo; + /** number of times we have called doRefine */ + unsigned d_refine_count; + /** construct candidates */ + bool constructCandidates( std::vector< Node >& clist, std::vector< Node >& model_values, + std::vector< Node >& candidate_values, std::vector< Node >& lems ); + /** get candidadate */ + Node getCandidate( unsigned int i ) { return d_candidates[i]; } + /** record instantiation (this is used to construct solutions later) */ + void recordInstantiation( std::vector< Node >& vs ) { + Assert( vs.size()==d_candidates.size() ); + for( unsigned i=0; i<vs.size(); i++ ){ + d_cinfo[d_candidates[i]].d_inst.push_back( vs[i] ); + } + } + /** get synth solutions internal + * + * This function constructs the body of solutions for all + * functions-to-synthesize in this conjecture and stores them in sols, in + * order. For each solution added to sols, we add an integer indicating what + * kind of solution n is, where if sols[i] = n, then + * if status[i] = 0: n is the (builtin term) corresponding to the solution, + * if status[i] = 1: n is the sygus representation of the solution. + * We store builtin versions under some conditions (such as when the sygus + * grammar is being ignored). + * + * singleInvocation : set to true if we should consult the single invocation + * module to get synthesis solutions. + * + * For example, for conjecture exists fg. forall x. f(x)>g(x), this function + * may set ( sols, status ) to ( { x+1, d_x() }, { 1, 0 } ), where d_x() is + * the sygus datatype constructor corresponding to variable x. + */ + void getSynthSolutionsInternal(std::vector<Node>& sols, + std::vector<int>& status, + bool singleInvocation); + //-------------------------------- sygus stream + /** the streaming guards for sygus streaming mode */ + std::vector< Node > d_stream_guards; + /** get current stream guard */ + Node getCurrentStreamGuard() const; + /** get stream guarded lemma + * + * If sygusStream is enabled, this returns ( G V n ) where G is the guard + * returned by getCurrentStreamGuard, otherwise this returns n. + */ + Node getStreamGuardedLemma(Node n) const; + //-------------------------------- end sygus stream + //-------------------------------- non-syntax guided (deprecated) + /** Whether we are syntax-guided (e.g. was the input in SyGuS format). + * This includes SyGuS inputs where no syntactic restrictions are provided. + */ + bool d_syntax_guided; + /** the guard for non-syntax-guided synthesis */ + Node d_nsg_guard; + //-------------------------------- end non-syntax guided (deprecated) + /** sygus sampler objects for each program variable + * + * This is used for the sygusRewSynth() option to synthesize new candidate + * rewrite rules. + */ + std::map<Node, SygusSamplerExt> d_sampler; + /** sampler object for the option cegisSample() + * + * This samples points of the type of the inner variables of the synthesis + * conjecture (d_base_vars). + */ + SygusSampler d_cegis_sampler; + /** cegis sample refine points + * + * Stores the list of indices of sample points in d_cegis_sampler we have + * added as refinement lemmas. + */ + std::unordered_set<unsigned> d_cegis_sample_refine; +}; + +} /* namespace CVC4::theory::quantifiers */ +} /* namespace CVC4::theory */ +} /* namespace CVC4 */ + +#endif diff --git a/src/theory/quantifiers/sygus/ce_guided_instantiation.cpp b/src/theory/quantifiers/sygus/ce_guided_instantiation.cpp new file mode 100644 index 000000000..35098f5ea --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_instantiation.cpp @@ -0,0 +1,388 @@ +/********************* */ +/*! \file ce_guided_instantiation.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King, Morgan Deters + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief counterexample guided instantiation class + ** This class is the entry point for both synthesis algorithms in Reynolds et al CAV 2015 + ** + **/ +#include "theory/quantifiers/sygus/ce_guided_instantiation.h" + +#include "options/quantifiers_options.h" +#include "smt/smt_statistics_registry.h" +#include "theory/theory_engine.h" +#include "theory/quantifiers/quantifiers_attributes.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_util.h" +//FIXME : remove this include (github issue #1156) +#include "theory/bv/theory_bv_rewriter.h" + +using namespace CVC4::kind; +using namespace std; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +CegInstantiation::CegInstantiation( QuantifiersEngine * qe, context::Context* c ) : QuantifiersModule( qe ){ + d_conj = new CegConjecture( qe ); + d_last_inst_si = false; +} + +CegInstantiation::~CegInstantiation(){ + delete d_conj; +} + +bool CegInstantiation::needsCheck( Theory::Effort e ) { + return e>=Theory::EFFORT_LAST_CALL; +} + +QuantifiersModule::QEffort CegInstantiation::needsModel(Theory::Effort e) +{ + return d_conj->isSingleInvocation() ? QEFFORT_STANDARD : QEFFORT_MODEL; +} + +void CegInstantiation::check(Theory::Effort e, QEffort quant_e) +{ + unsigned echeck = + d_conj->isSingleInvocation() ? QEFFORT_STANDARD : QEFFORT_MODEL; + if( quant_e==echeck ){ + Trace("cegqi-engine") << "---Counterexample Guided Instantiation Engine---" << std::endl; + Trace("cegqi-engine-debug") << std::endl; + bool active = false; + bool value; + if( d_quantEngine->getValuation().hasSatValue( d_conj->getConjecture(), value ) ) { + active = value; + }else{ + Trace("cegqi-engine-debug") << "...no value for quantified formula." << std::endl; + } + Trace("cegqi-engine-debug") << "Current conjecture status : active : " << active << std::endl; + std::vector< Node > lem; + if( active && d_conj->needsCheck( lem ) ){ + checkCegConjecture( d_conj ); + }else{ + Trace("cegqi-engine-debug") << "...does not need check." << std::endl; + for( unsigned i=0; i<lem.size(); i++ ){ + Trace("cegqi-lemma") << "Cegqi::Lemma : check lemma : " << lem[i] << std::endl; + d_quantEngine->addLemma( lem[i] ); + } + } + Trace("cegqi-engine") << "Finished Counterexample Guided Instantiation engine." << std::endl; + } +} + +void CegInstantiation::registerQuantifier( Node q ) { + if( d_quantEngine->getOwner( q )==this ){ // && d_eval_axioms.find( q )==d_eval_axioms.end() ){ + if( !d_conj->isAssigned() ){ + Trace("cegqi") << "Register conjecture : " << q << std::endl; + d_conj->assign( q ); + }else{ + Assert( d_conj->getEmbeddedConjecture()==q ); + } + }else{ + Trace("cegqi-debug") << "Register quantifier : " << q << std::endl; + } +} + +Node CegInstantiation::getNextDecisionRequest( unsigned& priority ) { + if( d_conj->isAssigned() ){ + Node dec_req = d_conj->getNextDecisionRequest( priority ); + if( !dec_req.isNull() ){ + Trace("cegqi-debug") << "CEGQI : Decide next on : " << dec_req << "..." << std::endl; + return dec_req; + } + } + return Node::null(); +} + +void CegInstantiation::checkCegConjecture( CegConjecture * conj ) { + Node q = conj->getEmbeddedConjecture(); + Node aq = conj->getConjecture(); + if( Trace.isOn("cegqi-engine-debug") ){ + conj->debugPrint("cegqi-engine-debug"); + Trace("cegqi-engine-debug") << std::endl; + } + + if( !conj->needsRefinement() ){ + Trace("cegqi-engine-debug") << "Do conjecture check..." << std::endl; + if( conj->isSyntaxGuided() ){ + std::vector< Node > clems; + conj->doSingleInvCheck( clems ); + if( !clems.empty() ){ + d_last_inst_si = true; + for( unsigned j=0; j<clems.size(); j++ ){ + Trace("cegqi-lemma") << "Cegqi::Lemma : single invocation instantiation : " << clems[j] << std::endl; + d_quantEngine->addLemma( clems[j] ); + } + d_statistics.d_cegqi_si_lemmas += clems.size(); + Trace("cegqi-engine") << " ...try single invocation." << std::endl; + return; + } + //ignore return value here + std::vector< Node > clist; + conj->getCandidateList( clist ); + std::vector< Node > model_values; + conj->getModelValues( clist, model_values ); + if( options::sygusDirectEval() ){ + bool addedEvalLemmas = false; + if( options::sygusCRefEval() ){ + Trace("cegqi-engine") << " *** Do conjecture refinement evaluation..." << std::endl; + // see if any refinement lemma is refuted by evaluation + std::vector< Node > cre_lems; + getCRefEvaluationLemmas( conj, clist, model_values, cre_lems ); + if( !cre_lems.empty() ){ + for( unsigned j=0; j<cre_lems.size(); j++ ){ + Node lem = cre_lems[j]; + if( d_quantEngine->addLemma( lem ) ){ + Trace("cegqi-lemma") << "Cegqi::Lemma : cref evaluation : " << lem << std::endl; + addedEvalLemmas = true; + } + } + if( addedEvalLemmas ){ + //return; + } + } + } + Trace("cegqi-engine") << " *** Do direct evaluation..." << std::endl; + std::vector< Node > eager_terms; + std::vector< Node > eager_vals; + std::vector< Node > eager_exps; + for( unsigned j=0; j<clist.size(); j++ ){ + Trace("cegqi-debug") << " register " << clist[j] << " -> " << model_values[j] << std::endl; + d_quantEngine->getTermDatabaseSygus()->registerModelValue( clist[j], model_values[j], eager_terms, eager_vals, eager_exps ); + } + Trace("cegqi-debug") << "...produced " << eager_terms.size() << " eager evaluation lemmas." << std::endl; + if( !eager_terms.empty() ){ + for( unsigned j=0; j<eager_terms.size(); j++ ){ + Node lem = NodeManager::currentNM()->mkNode( kind::OR, eager_exps[j].negate(), eager_terms[j].eqNode( eager_vals[j] ) ); + if( d_quantEngine->getTheoryEngine()->isTheoryEnabled(THEORY_BV) ){ + //FIXME: hack to incorporate hacks from BV for division by zero (github issue #1156) + lem = bv::TheoryBVRewriter::eliminateBVSDiv( lem ); + } + if( d_quantEngine->addLemma( lem ) ){ + Trace("cegqi-lemma") << "Cegqi::Lemma : evaluation : " << lem << std::endl; + addedEvalLemmas = true; + } + } + } + if( addedEvalLemmas ){ + return; + } + } + + Trace("cegqi-engine") << " *** Check candidate phase..." << std::endl; + std::vector< Node > cclems; + conj->doCheck( cclems, model_values ); + bool addedLemma = false; + for( unsigned i=0; i<cclems.size(); i++ ){ + Node lem = cclems[i]; + d_last_inst_si = false; + Trace("cegqi-lemma") << "Cegqi::Lemma : counterexample : " << lem << std::endl; + if( d_quantEngine->addLemma( lem ) ){ + ++(d_statistics.d_cegqi_lemmas_ce); + addedLemma = true; + }else{ + //this may happen if we eagerly unfold, simplify to true + if( !options::sygusDirectEval() ){ + Trace("cegqi-warn") << " ...FAILED to add candidate!" << std::endl; + }else{ + Trace("cegqi-engine-debug") << " ...FAILED to add candidate!" << std::endl; + } + } + } + if( addedLemma ){ + Trace("cegqi-engine") << " ...check for counterexample." << std::endl; + }else{ + if( conj->needsRefinement() ){ + //immediately go to refine candidate + checkCegConjecture( conj ); + return; + } + } + }else{ + Assert( aq==q ); + Trace("cegqi-engine") << " *** Check candidate phase (non-SyGuS)." << std::endl; + std::vector< Node > lems; + conj->doBasicCheck(lems); + Assert(lems.empty()); + } + }else{ + Trace("cegqi-engine") << " *** Refine candidate phase..." << std::endl; + std::vector< Node > rlems; + conj->doRefine( rlems ); + bool addedLemma = false; + for( unsigned i=0; i<rlems.size(); i++ ){ + Node lem = rlems[i]; + Trace("cegqi-lemma") << "Cegqi::Lemma : candidate refinement : " << lem << std::endl; + bool res = d_quantEngine->addLemma( lem ); + if( res ){ + ++(d_statistics.d_cegqi_lemmas_refine); + conj->incrementRefineCount(); + addedLemma = true; + }else{ + Trace("cegqi-warn") << " ...FAILED to add refinement!" << std::endl; + } + } + if( addedLemma ){ + Trace("cegqi-engine") << " ...refine candidate." << std::endl; + } + } +} + +void CegInstantiation::getCRefEvaluationLemmas( CegConjecture * conj, std::vector< Node >& vs, std::vector< Node >& ms, std::vector< Node >& lems ) { + Trace("sygus-cref-eval") << "Cref eval : conjecture has " << conj->getNumRefinementLemmas() << " refinement lemmas." << std::endl; + unsigned nlemmas = conj->getNumRefinementLemmas(); + if (nlemmas > 0 || options::cegisSample() != CEGIS_SAMPLE_NONE) + { + Assert( vs.size()==ms.size() ); + + TermDbSygus* tds = d_quantEngine->getTermDatabaseSygus(); + Node nfalse = d_quantEngine->getTermUtil()->d_false; + Node neg_guard = conj->getGuard().negate(); + for (unsigned i = 0; i <= nlemmas; i++) + { + if (i == nlemmas) + { + bool addedSample = false; + // find a new one by sampling, if applicable + if (options::cegisSample() != CEGIS_SAMPLE_NONE) + { + addedSample = conj->sampleAddRefinementLemma(ms, lems); + } + if (!addedSample) + { + return; + } + } + Node lem; + std::map< Node, Node > visited; + std::map< Node, std::vector< Node > > exp; + lem = conj->getRefinementLemma(i); + if( !lem.isNull() ){ + std::vector< Node > lem_conj; + //break into conjunctions + if( lem.getKind()==kind::AND ){ + for( unsigned i=0; i<lem.getNumChildren(); i++ ){ + lem_conj.push_back( lem[i] ); + } + }else{ + lem_conj.push_back( lem ); + } + EvalSygusInvarianceTest vsit; + for( unsigned j=0; j<lem_conj.size(); j++ ){ + Node lemc = lem_conj[j]; + Trace("sygus-cref-eval") << "Check refinement lemma conjunct " << lemc << " against current model." << std::endl; + Trace("sygus-cref-eval2") << "Check refinement lemma conjunct " << lemc << " against current model." << std::endl; + Node cre_lem; + Node lemcs = lemc.substitute( vs.begin(), vs.end(), ms.begin(), ms.end() ); + Trace("sygus-cref-eval2") << "...under substitution it is : " << lemcs << std::endl; + Node lemcsu = vsit.doEvaluateWithUnfolding(tds, lemcs); + Trace("sygus-cref-eval2") << "...after unfolding is : " << lemcsu << std::endl; + if( lemcsu==d_quantEngine->getTermUtil()->d_false ){ + std::vector< Node > msu; + std::vector< Node > mexp; + msu.insert( msu.end(), ms.begin(), ms.end() ); + for( unsigned k=0; k<vs.size(); k++ ){ + vsit.setUpdatedTerm(msu[k]); + msu[k] = vs[k]; + // substitute for everything except this + Node sconj = + lemc.substitute(vs.begin(), vs.end(), msu.begin(), msu.end()); + vsit.init(sconj, vs[k], nfalse); + // get minimal explanation for this + Node ut = vsit.getUpdatedTerm(); + Trace("sygus-cref-eval2-debug") + << " compute min explain of : " << vs[k] << " = " << ut + << std::endl; + d_quantEngine->getTermDatabaseSygus() + ->getExplain() + ->getExplanationFor(vs[k], ut, mexp, vsit); + msu[k] = ut; + } + if( !mexp.empty() ){ + Node en = mexp.size()==1 ? mexp[0] : NodeManager::currentNM()->mkNode( kind::AND, mexp ); + cre_lem = NodeManager::currentNM()->mkNode( kind::OR, en.negate(), neg_guard ); + }else{ + cre_lem = neg_guard; + } + } + if( !cre_lem.isNull() ){ + if( std::find( lems.begin(), lems.end(), cre_lem )==lems.end() ){ + Trace("sygus-cref-eval") << "...produced lemma : " << cre_lem << std::endl; + lems.push_back( cre_lem ); + } + } + } + } + } + } +} + +void CegInstantiation::printSynthSolution( std::ostream& out ) { + if( d_conj->isAssigned() ) + { + d_conj->printSynthSolution( out, d_last_inst_si ); + } + else + { + Assert( false ); + } +} + +void CegInstantiation::getSynthSolutions(std::map<Node, Node>& sol_map) +{ + if (d_conj->isAssigned()) + { + d_conj->getSynthSolutions(sol_map, d_last_inst_si); + } + else + { + Assert(false); + } +} + +void CegInstantiation::preregisterAssertion( Node n ) { + //check if it sygus conjecture + if( QuantAttributes::checkSygusConjecture( n ) ){ + //this is a sygus conjecture + Trace("cegqi") << "Preregister sygus conjecture : " << n << std::endl; + d_conj->preregisterConjecture( n ); + } +} + +CegInstantiation::Statistics::Statistics() + : d_cegqi_lemmas_ce("CegInstantiation::cegqi_lemmas_ce", 0), + d_cegqi_lemmas_refine("CegInstantiation::cegqi_lemmas_refine", 0), + d_cegqi_si_lemmas("CegInstantiation::cegqi_lemmas_si", 0), + d_solutions("CegConjecture::solutions", 0), + d_candidate_rewrites_print("CegConjecture::candidate_rewrites_print", 0), + d_candidate_rewrites("CegConjecture::candidate_rewrites", 0) + +{ + smtStatisticsRegistry()->registerStat(&d_cegqi_lemmas_ce); + smtStatisticsRegistry()->registerStat(&d_cegqi_lemmas_refine); + smtStatisticsRegistry()->registerStat(&d_cegqi_si_lemmas); + smtStatisticsRegistry()->registerStat(&d_solutions); + smtStatisticsRegistry()->registerStat(&d_candidate_rewrites_print); + smtStatisticsRegistry()->registerStat(&d_candidate_rewrites); +} + +CegInstantiation::Statistics::~Statistics(){ + smtStatisticsRegistry()->unregisterStat(&d_cegqi_lemmas_ce); + smtStatisticsRegistry()->unregisterStat(&d_cegqi_lemmas_refine); + smtStatisticsRegistry()->unregisterStat(&d_cegqi_si_lemmas); + smtStatisticsRegistry()->unregisterStat(&d_solutions); + smtStatisticsRegistry()->unregisterStat(&d_candidate_rewrites_print); + smtStatisticsRegistry()->unregisterStat(&d_candidate_rewrites); +} + +}/* namespace CVC4::theory::quantifiers */ +}/* namespace CVC4::theory */ +}/* namespace CVC4 */ diff --git a/src/theory/quantifiers/sygus/ce_guided_instantiation.h b/src/theory/quantifiers/sygus/ce_guided_instantiation.h new file mode 100644 index 000000000..087836d5a --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_instantiation.h @@ -0,0 +1,90 @@ +/********************* */ +/*! \file ce_guided_instantiation.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief counterexample guided instantiation class + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_INSTANTIATION_H +#define __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_INSTANTIATION_H + +#include "context/cdhashmap.h" +#include "theory/quantifiers/sygus/ce_guided_conjecture.h" +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +class CegInstantiation : public QuantifiersModule +{ + typedef context::CDHashMap<Node, bool, NodeHashFunction> NodeBoolMap; +private: + /** the quantified formula stating the synthesis conjecture */ + CegConjecture * d_conj; + /** last instantiation by single invocation module? */ + bool d_last_inst_si; +private: //for direct evaluation + /** get refinement evaluation */ + void getCRefEvaluationLemmas( CegConjecture * conj, std::vector< Node >& vs, std::vector< Node >& ms, std::vector< Node >& lems ); +private: + /** check conjecture */ + void checkCegConjecture( CegConjecture * conj ); +public: + CegInstantiation( QuantifiersEngine * qe, context::Context* c ); + ~CegInstantiation(); +public: + bool needsCheck( Theory::Effort e ); + QEffort needsModel(Theory::Effort e); + /* Call during quantifier engine's check */ + void check(Theory::Effort e, QEffort quant_e); + /* Called for new quantifiers */ + void registerQuantifier( Node q ); + /** get the next decision request */ + Node getNextDecisionRequest( unsigned& priority ); + /** Identify this module (for debugging, dynamic configuration, etc..) */ + std::string identify() const { return "CegInstantiation"; } + /** print solution for synthesis conjectures */ + void printSynthSolution( std::ostream& out ); + /** get synth solutions + * + * This function adds entries to sol_map that map functions-to-synthesize + * with their solutions, for all active conjectures (currently just the one + * assigned to d_conj). This should be called immediately after the solver + * answers unsat for sygus input. + * + * For details on what is added to sol_map, see + * CegConjecture::getSynthSolutions. + */ + void getSynthSolutions(std::map<Node, Node>& sol_map); + /** preregister assertion (before rewrite) */ + void preregisterAssertion( Node n ); +public: + class Statistics { + public: + IntStat d_cegqi_lemmas_ce; + IntStat d_cegqi_lemmas_refine; + IntStat d_cegqi_si_lemmas; + IntStat d_solutions; + IntStat d_candidate_rewrites_print; + IntStat d_candidate_rewrites; + Statistics(); + ~Statistics(); + };/* class CegInstantiation::Statistics */ + Statistics d_statistics; +}; /* class CegInstantiation */ + +} /* namespace CVC4::theory::quantifiers */ +} /* namespace CVC4::theory */ +} /* namespace CVC4 */ + +#endif diff --git a/src/theory/quantifiers/sygus/ce_guided_single_inv.cpp b/src/theory/quantifiers/sygus/ce_guided_single_inv.cpp new file mode 100644 index 000000000..d59f1f370 --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_single_inv.cpp @@ -0,0 +1,1004 @@ +/********************* */ +/*! \file ce_guided_single_inv.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief utility for processing single invocation synthesis conjectures + ** + **/ +#include "theory/quantifiers/sygus/ce_guided_single_inv.h" + +#include "options/quantifiers_options.h" +#include "theory/arith/arith_msum.h" +#include "theory/quantifiers/term_enumeration.h" +#include "theory/quantifiers/term_util.h" + +using namespace CVC4; +using namespace CVC4::kind; +using namespace CVC4::theory; +using namespace CVC4::theory::quantifiers; +using namespace std; + +namespace CVC4 { + +bool CegqiOutputSingleInv::doAddInstantiation( std::vector< Node >& subs ) { + return d_out->doAddInstantiation( subs ); +} + +bool CegqiOutputSingleInv::isEligibleForInstantiation( Node n ) { + return d_out->isEligibleForInstantiation( n ); +} + +bool CegqiOutputSingleInv::addLemma( Node n ) { + return d_out->addLemma( n ); +} + +CegConjectureSingleInv::CegConjectureSingleInv(QuantifiersEngine* qe, + CegConjecture* p) + : d_qe(qe), + d_parent(p), + d_sip(new SingleInvocationPartition), + d_sol(new CegConjectureSingleInvSol(qe)), + d_cosi(new CegqiOutputSingleInv(this)), + d_cinst(NULL), + d_c_inst_match_trie(NULL), + d_has_ites(true), + d_single_invocation(false) { + // third and fourth arguments set to (false,false) until we have solution + // reconstruction for delta and infinity + d_cinst = new CegInstantiator(d_qe, d_cosi, false, false); + + if (options::incrementalSolving()) { + d_c_inst_match_trie = new inst::CDInstMatchTrie(qe->getUserContext()); + } +} + +CegConjectureSingleInv::~CegConjectureSingleInv() { + if (d_c_inst_match_trie) { + delete d_c_inst_match_trie; + } + delete d_cinst; + delete d_cosi; + delete d_sol; // (new CegConjectureSingleInvSol(qe)), + delete d_sip; // d_sip(new SingleInvocationPartition), +} + +void CegConjectureSingleInv::getInitialSingleInvLemma( std::vector< Node >& lems ) { + Assert( d_si_guard.isNull() ); + //single invocation guard + d_si_guard = Rewriter::rewrite( NodeManager::currentNM()->mkSkolem( "G", NodeManager::currentNM()->booleanType() ) ); + d_si_guard = d_qe->getValuation().ensureLiteral( d_si_guard ); + AlwaysAssert( !d_si_guard.isNull() ); + d_qe->getOutputChannel().requirePhase( d_si_guard, true ); + + if( !d_single_inv.isNull() ) { + //make for new var/sk + d_single_inv_var.clear(); + d_single_inv_sk.clear(); + Node inst; + if( d_single_inv.getKind()==FORALL ){ + for( unsigned i=0; i<d_single_inv[0].getNumChildren(); i++ ){ + std::stringstream ss; + ss << "k_" << d_single_inv[0][i]; + Node k = NodeManager::currentNM()->mkSkolem( ss.str(), d_single_inv[0][i].getType(), "single invocation function skolem" ); + d_single_inv_var.push_back( d_single_inv[0][i] ); + d_single_inv_sk.push_back( k ); + d_single_inv_sk_index[k] = i; + } + inst = d_single_inv[1].substitute( d_single_inv_var.begin(), d_single_inv_var.end(), d_single_inv_sk.begin(), d_single_inv_sk.end() ); + }else{ + inst = d_single_inv; + } + inst = TermUtil::simpleNegate( inst ); + Trace("cegqi-si") << "Single invocation initial lemma : " << inst << std::endl; + + //register with the instantiator + Node ginst = NodeManager::currentNM()->mkNode( OR, d_si_guard.negate(), inst ); + lems.push_back( ginst ); + //make and register the instantiator + if( d_cinst ){ + delete d_cinst; + } + d_cinst = new CegInstantiator( d_qe, d_cosi, false, false ); + d_cinst->registerCounterexampleLemma( lems, d_single_inv_sk ); + } +} + +void CegConjectureSingleInv::initialize( Node q ) { + // can only register one quantified formula with this + Assert( d_quant.isNull() ); + d_quant = q; + d_simp_quant = q; + Trace("cegqi-si") << "CegConjectureSingleInv::initialize : " << q << std::endl; + // infer single invocation-ness + std::vector< Node > progs; + std::map< Node, std::vector< Node > > prog_vars; + for( unsigned i=0; i<q[0].getNumChildren(); i++ ){ + Node sf = q[0][i]; + progs.push_back( sf ); + Node sfvl = sf.getAttribute(SygusSynthFunVarListAttribute()); + for( unsigned j=0; j<sfvl.getNumChildren(); j++ ){ + prog_vars[sf].push_back( sfvl[j] ); + } + } + // compute single invocation partition + if( options::cegqiSingleInvMode()!=CEGQI_SI_MODE_NONE ){ + Node qq; + if( q[1].getKind()==NOT && q[1][0].getKind()==FORALL ){ + qq = q[1][0][1]; + }else{ + qq = TermUtil::simpleNegate( q[1] ); + } + //process the single invocation-ness of the property + if( !d_sip->init( progs, qq ) ){ + Trace("cegqi-si") << "...not single invocation (type mismatch)" << std::endl; + }else{ + Trace("cegqi-si") << "- Partitioned to single invocation parts : " << std::endl; + d_sip->debugPrint( "cegqi-si" ); + + //map from program to bound variables + std::vector<Node> funcs; + d_sip->getFunctions(funcs); + for (unsigned j = 0, size = funcs.size(); j < size; j++) + { + Assert(std::find(progs.begin(), progs.end(), funcs[j]) != progs.end()); + d_prog_to_sol_index[funcs[j]] = j; + } + + //check if it is single invocation + if (!d_sip->isPurelySingleInvocation()) + { + if( options::sygusInvTemplMode() != SYGUS_INV_TEMPL_MODE_NONE ){ + //if we are doing invariant templates, then construct the template + Trace("cegqi-si") << "- Do transition inference..." << std::endl; + d_ti[q].process( qq ); + Trace("cegqi-inv") << std::endl; + if( !d_ti[q].d_func.isNull() ){ + // map the program back via non-single invocation map + Node prog = d_ti[q].d_func; + std::vector< Node > prog_templ_vars; + prog_templ_vars.insert( prog_templ_vars.end(), d_ti[q].d_vars.begin(), d_ti[q].d_vars.end() ); + d_trans_pre[prog] = d_ti[q].getComponent( 1 ); + d_trans_post[prog] = d_ti[q].getComponent( -1 ); + Trace("cegqi-inv") << " precondition : " << d_trans_pre[prog] << std::endl; + Trace("cegqi-inv") << " postcondition : " << d_trans_post[prog] << std::endl; + std::vector<Node> sivars; + d_sip->getSingleInvocationVariables(sivars); + Node invariant = d_sip->getFunctionInvocationFor(prog); + Assert(!invariant.isNull()); + invariant = invariant.substitute(sivars.begin(), + sivars.end(), + prog_templ_vars.begin(), + prog_templ_vars.end()); + Trace("cegqi-inv") << " invariant : " << invariant << std::endl; + + // store simplified version of quantified formula + d_simp_quant = d_sip->getFullSpecification(); + std::vector< Node > new_bv; + for (unsigned j = 0, size = sivars.size(); j < size; j++) + { + new_bv.push_back( + NodeManager::currentNM()->mkBoundVar(sivars[j].getType())); + } + d_simp_quant = d_simp_quant.substitute( + sivars.begin(), sivars.end(), new_bv.begin(), new_bv.end()); + Assert( q[1].getKind()==NOT && q[1][0].getKind()==FORALL ); + for( unsigned j=0; j<q[1][0][0].getNumChildren(); j++ ){ + new_bv.push_back( q[1][0][0][j] ); + } + d_simp_quant = NodeManager::currentNM()->mkNode( kind::FORALL, NodeManager::currentNM()->mkNode( BOUND_VAR_LIST, new_bv ), d_simp_quant ).negate(); + d_simp_quant = Rewriter::rewrite( d_simp_quant ); + d_simp_quant = NodeManager::currentNM()->mkNode( kind::FORALL, q[0], d_simp_quant, q[2] ); + Trace("cegqi-si") << "Rewritten quantifier : " << d_simp_quant << std::endl; + + //construct template argument + d_templ_arg[prog] = NodeManager::currentNM()->mkSkolem( "I", invariant.getType() ); + + //construct template + Node templ; + if( options::sygusInvAutoUnfold() ){ + if( d_ti[q].isComplete() ){ + Trace("cegqi-inv-auto-unfold") << "Automatic deterministic unfolding... " << std::endl; + // auto-unfold + DetTrace dt; + int init_dt = d_ti[q].initializeTrace( dt ); + if( init_dt==0 ){ + Trace("cegqi-inv-auto-unfold") << " Init : "; + dt.print("cegqi-inv-auto-unfold"); + Trace("cegqi-inv-auto-unfold") << std::endl; + unsigned counter = 0; + unsigned status = 0; + while( counter<100 && status==0 ){ + status = d_ti[q].incrementTrace( dt ); + counter++; + Trace("cegqi-inv-auto-unfold") << " #" << counter << " : "; + dt.print("cegqi-inv-auto-unfold"); + Trace("cegqi-inv-auto-unfold") << "...status = " << status << std::endl; + } + if( status==1 ){ + // we have a trivial invariant + templ = d_ti[q].constructFormulaTrace( dt ); + Trace("cegqi-inv") << "By finite deterministic terminating trace, a solution invariant is : " << std::endl; + Trace("cegqi-inv") << " " << templ << std::endl; + // FIXME : this should be unnecessary + templ = NodeManager::currentNM()->mkNode( AND, templ, d_templ_arg[prog] ); + } + }else{ + Trace("cegqi-inv-auto-unfold") << "...failed initialize." << std::endl; + } + } + } + if( templ.isNull() ){ + if( options::sygusInvTemplMode() == SYGUS_INV_TEMPL_MODE_PRE ){ + //d_templ[prog] = NodeManager::currentNM()->mkNode( AND, NodeManager::currentNM()->mkNode( OR, d_trans_pre[prog], invariant ), d_trans_post[prog] ); + templ = NodeManager::currentNM()->mkNode( OR, d_trans_pre[prog], d_templ_arg[prog] ); + }else{ + Assert( options::sygusInvTemplMode() == SYGUS_INV_TEMPL_MODE_POST ); + //d_templ[prog] = NodeManager::currentNM()->mkNode( OR, d_trans_pre[prog], NodeManager::currentNM()->mkNode( AND, d_trans_post[prog], invariant ) ); + templ = NodeManager::currentNM()->mkNode( AND, d_trans_post[prog], d_templ_arg[prog] ); + } + } + Trace("cegqi-inv") << " template (pre-substitution) : " << templ << std::endl; + Assert( !templ.isNull() ); + // subsitute the template arguments + templ = templ.substitute( prog_templ_vars.begin(), prog_templ_vars.end(), prog_vars[prog].begin(), prog_vars[prog].end() ); + Trace("cegqi-inv") << " template : " << templ << std::endl; + d_templ[prog] = templ; + } + } + }else{ + //we are fully single invocation + d_single_invocation = true; + } + } + } +} + +void CegConjectureSingleInv::finishInit( bool syntaxRestricted, bool hasItes ) { + d_has_ites = hasItes; + // do not do single invocation if grammar is restricted and CEGQI_SI_MODE_ALL is not enabled + if( options::cegqiSingleInvMode()==CEGQI_SI_MODE_USE && d_single_invocation && syntaxRestricted ){ + d_single_invocation = false; + Trace("cegqi-si") << "...grammar is restricted, do not use single invocation techniques." << std::endl; + } + + // we now have determined whether we will do single invocation techniques + if( d_single_invocation ){ + d_single_inv = d_sip->getSingleInvocation(); + d_single_inv = TermUtil::simpleNegate( d_single_inv ); + std::vector<Node> func_vars; + d_sip->getFunctionVariables(func_vars); + if (!func_vars.empty()) + { + Node pbvl = NodeManager::currentNM()->mkNode(BOUND_VAR_LIST, func_vars); + d_single_inv = NodeManager::currentNM()->mkNode( FORALL, pbvl, d_single_inv ); + } + //now, introduce the skolems + std::vector<Node> sivars; + d_sip->getSingleInvocationVariables(sivars); + for (unsigned i = 0, size = sivars.size(); i < size; i++) + { + Node v = NodeManager::currentNM()->mkSkolem( + "a", sivars[i].getType(), "single invocation arg"); + d_single_inv_arg_sk.push_back( v ); + } + d_single_inv = d_single_inv.substitute(sivars.begin(), + sivars.end(), + d_single_inv_arg_sk.begin(), + d_single_inv_arg_sk.end()); + Trace("cegqi-si") << "Single invocation formula is : " << d_single_inv << std::endl; + if( options::cbqiPreRegInst() && d_single_inv.getKind()==FORALL ){ + //just invoke the presolve now + d_cinst->presolve( d_single_inv ); + } + }else{ + d_single_inv = Node::null(); + Trace("cegqi-si") << "Formula is not single invocation." << std::endl; + if( options::cegqiSingleInvAbort() ){ + Notice() << "Property is not single invocation." << std::endl; + exit( 1 ); + } + } +} + +bool CegConjectureSingleInv::doAddInstantiation( std::vector< Node >& subs ){ + Assert( d_single_inv_sk.size()==subs.size() ); + Trace("cegqi-si-inst-debug") << "CegConjectureSingleInv::doAddInstantiation, #vars = "; + Trace("cegqi-si-inst-debug") << d_single_inv_sk.size() << "..." << std::endl; + std::stringstream siss; + if( Trace.isOn("cegqi-si-inst-debug") || Trace.isOn("cegqi-engine") ){ + siss << " * single invocation: " << std::endl; + for( unsigned j=0; j<d_single_inv_sk.size(); j++ ){ + Node op = d_sip->getFunctionForFirstOrderVariable(d_single_inv[0][j]); + Assert(!op.isNull()); + siss << " * " << op; + siss << " (" << d_single_inv_sk[j] << ")"; + siss << " -> " << subs[j] << std::endl; + } + } + Trace("cegqi-si-inst-debug") << siss.str(); + + bool alreadyExists; + Node lem; + if( subs.empty() ){ + Assert( d_single_inv.getKind()!=FORALL ); + alreadyExists = false; + lem = d_single_inv; + }else{ + Assert( d_single_inv.getKind()==FORALL ); + if( options::incrementalSolving() ){ + alreadyExists = !d_c_inst_match_trie->addInstMatch( d_qe, d_single_inv, subs, d_qe->getUserContext() ); + }else{ + alreadyExists = !d_inst_match_trie.addInstMatch( d_qe, d_single_inv, subs ); + } + Trace("cegqi-si-inst-debug") << " * success = " << !alreadyExists << std::endl; + //Trace("cegqi-si-inst-debug") << siss.str(); + //Trace("cegqi-si-inst-debug") << " * success = " << !alreadyExists << std::endl; + if( alreadyExists ){ + return false; + }else{ + Trace("cegqi-engine") << siss.str() << std::endl; + Assert( d_single_inv_var.size()==subs.size() ); + lem = d_single_inv[1].substitute( d_single_inv_var.begin(), d_single_inv_var.end(), subs.begin(), subs.end() ); + if( d_qe->getTermUtil()->containsVtsTerm( lem ) ){ + Trace("cegqi-engine-debug") << "Rewrite based on vts symbols..." << std::endl; + lem = d_qe->getTermUtil()->rewriteVtsSymbols( lem ); + } + } + } + Trace("cegqi-engine-debug") << "Rewrite..." << std::endl; + lem = Rewriter::rewrite( lem ); + Trace("cegqi-si") << "Single invocation lemma : " << lem << std::endl; + if( std::find( d_lemmas_produced.begin(), d_lemmas_produced.end(), lem )==d_lemmas_produced.end() ){ + d_curr_lemmas.push_back( lem ); + d_lemmas_produced.push_back( lem ); + d_inst.push_back( std::vector< Node >() ); + d_inst.back().insert( d_inst.back().end(), subs.begin(), subs.end() ); + } + return true; +} + +bool CegConjectureSingleInv::isEligibleForInstantiation( Node n ) { + return n.getKind()!=SKOLEM || std::find( d_single_inv_arg_sk.begin(), d_single_inv_arg_sk.end(), n )!=d_single_inv_arg_sk.end(); +} + +bool CegConjectureSingleInv::addLemma( Node n ) { + d_curr_lemmas.push_back( n ); + return true; +} + +bool CegConjectureSingleInv::check( std::vector< Node >& lems ) { + if( !d_single_inv.isNull() ) { + Trace("cegqi-si-debug") << "CegConjectureSingleInv::check..." << std::endl; + Trace("cegqi-si-debug") << "CegConjectureSingleInv::check consulting ceg instantiation..." << std::endl; + d_curr_lemmas.clear(); + Assert( d_cinst!=NULL ); + //call check for instantiator + d_cinst->check(); + Trace("cegqi-si-debug") << "...returned " << d_curr_lemmas.size() << " lemmas " << std::endl; + //add lemmas + lems.insert( lems.end(), d_curr_lemmas.begin(), d_curr_lemmas.end() ); + return !lems.empty(); + }else{ + // not single invocation + return false; + } +} + +Node CegConjectureSingleInv::constructSolution( std::vector< unsigned >& indices, unsigned i, unsigned index, std::map< Node, Node >& weak_imp ) { + Assert( index<d_inst.size() ); + Assert( i<d_inst[index].size() ); + unsigned uindex = indices[index]; + if( index==indices.size()-1 ){ + return d_inst[uindex][i]; + }else{ + Node cond = d_lemmas_produced[uindex]; + //weaken based on unsat core + std::map< Node, Node >::iterator itw = weak_imp.find( cond ); + if( itw!=weak_imp.end() ){ + cond = itw->second; + } + cond = TermUtil::simpleNegate( cond ); + Node ite1 = d_inst[uindex][i]; + Node ite2 = constructSolution( indices, i, index+1, weak_imp ); + return NodeManager::currentNM()->mkNode( ITE, cond, ite1, ite2 ); + } +} + +//TODO: use term size? +struct sortSiInstanceIndices { + CegConjectureSingleInv* d_ccsi; + int d_i; + bool operator() (unsigned i, unsigned j) { + if( d_ccsi->d_inst[i][d_i].isConst() && !d_ccsi->d_inst[j][d_i].isConst() ){ + return true; + }else{ + return false; + } + } +}; + + +Node CegConjectureSingleInv::postProcessSolution( Node n ){ + ////remove boolean ITE (not allowed for sygus comp 2015) + //if( n.getKind()==ITE && n.getType().isBoolean() ){ + // Node n1 = postProcessSolution( n[1] ); + // Node n2 = postProcessSolution( n[2] ); + // return NodeManager::currentNM()->mkNode( OR, NodeManager::currentNM()->mkNode( AND, n[0], n1 ), + // NodeManager::currentNM()->mkNode( AND, n[0].negate(), n2 ) ); + //}else{ + bool childChanged = false; + Kind k = n.getKind(); + if( n.getKind()==INTS_DIVISION_TOTAL ){ + k = INTS_DIVISION; + childChanged = true; + }else if( n.getKind()==INTS_MODULUS_TOTAL ){ + k = INTS_MODULUS; + childChanged = true; + } + std::vector< Node > children; + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + Node nn = postProcessSolution( n[i] ); + children.push_back( nn ); + childChanged = childChanged || nn!=n[i]; + } + if( childChanged ){ + if( n.hasOperator() && k==n.getKind() ){ + children.insert( children.begin(), n.getOperator() ); + } + return NodeManager::currentNM()->mkNode( k, children ); + }else{ + return n; + } + //} +} + + +Node CegConjectureSingleInv::getSolution( unsigned sol_index, TypeNode stn, int& reconstructed, bool rconsSygus ){ + Assert( d_sol!=NULL ); + Assert( !d_lemmas_produced.empty() ); + const Datatype& dt = ((DatatypeType)(stn).toType()).getDatatype(); + Node varList = Node::fromExpr( dt.getSygusVarList() ); + Node prog = d_quant[0][sol_index]; + std::vector< Node > vars; + Node s; + if( d_prog_to_sol_index.find( prog )==d_prog_to_sol_index.end() ){ + Trace("csi-sol") << "Get solution for (unconstrained) " << prog << std::endl; + s = d_qe->getTermEnumeration()->getEnumerateTerm( + TypeNode::fromType(dt.getSygusType()), 0); + }else{ + Trace("csi-sol") << "Get solution for " << prog << ", with skolems : "; + sol_index = d_prog_to_sol_index[prog]; + d_sol->d_varList.clear(); + Assert( d_single_inv_arg_sk.size()==varList.getNumChildren() ); + for( unsigned i=0; i<d_single_inv_arg_sk.size(); i++ ){ + Trace("csi-sol") << d_single_inv_arg_sk[i] << " "; + vars.push_back( d_single_inv_arg_sk[i] ); + d_sol->d_varList.push_back( varList[i] ); + } + Trace("csi-sol") << std::endl; + + //construct the solution + Trace("csi-sol") << "Sort solution return values " << sol_index << std::endl; + bool useUnsatCore = false; + std::vector< Node > active_lemmas; + //minimize based on unsat core, if possible + std::map< Node, Node > weak_imp; + if( options::cegqiSolMinCore() ){ + if( options::cegqiSolMinInst() ){ + if( d_qe->getUnsatCoreLemmas( active_lemmas, weak_imp ) ){ + useUnsatCore = true; + } + }else{ + if( d_qe->getUnsatCoreLemmas( active_lemmas ) ){ + useUnsatCore = true; + } + } + } + Assert( d_lemmas_produced.size()==d_inst.size() ); + std::vector< unsigned > indices; + for( unsigned i=0; i<d_lemmas_produced.size(); i++ ){ + bool incl = true; + if( useUnsatCore ){ + incl = std::find( active_lemmas.begin(), active_lemmas.end(), d_lemmas_produced[i] )!=active_lemmas.end(); + } + if( incl ){ + Assert( sol_index<d_inst[i].size() ); + indices.push_back( i ); + } + } + Trace("csi-sol") << "...included " << indices.size() << " / " << d_lemmas_produced.size() << " instantiations." << std::endl; + Assert( !indices.empty() ); + //sort indices based on heuristic : currently, do all constant returns first (leads to simpler conditions) + // TODO : to minimize solution size, put the largest term last + sortSiInstanceIndices ssii; + ssii.d_ccsi = this; + ssii.d_i = sol_index; + std::sort( indices.begin(), indices.end(), ssii ); + Trace("csi-sol") << "Construct solution" << std::endl; + s = constructSolution( indices, sol_index, 0, weak_imp ); + Assert( vars.size()==d_sol->d_varList.size() ); + s = s.substitute( vars.begin(), vars.end(), d_sol->d_varList.begin(), d_sol->d_varList.end() ); + } + d_orig_solution = s; + + //simplify the solution + Trace("csi-sol") << "Solution (pre-simplification): " << d_orig_solution << std::endl; + s = d_sol->simplifySolution( s, stn ); + Trace("csi-sol") << "Solution (post-simplification): " << s << std::endl; + return reconstructToSyntax( s, stn, reconstructed, rconsSygus ); +} + +Node CegConjectureSingleInv::reconstructToSyntax( Node s, TypeNode stn, int& reconstructed, bool rconsSygus ) { + d_solution = s; + const Datatype& dt = ((DatatypeType)(stn).toType()).getDatatype(); + + //reconstruct the solution into sygus if necessary + reconstructed = 0; + if( options::cegqiSingleInvReconstruct() && !dt.getSygusAllowAll() && !stn.isNull() && rconsSygus ){ + d_sol->preregisterConjecture( d_orig_conjecture ); + d_sygus_solution = d_sol->reconstructSolution( s, stn, reconstructed ); + if( reconstructed==1 ){ + Trace("csi-sol") << "Solution (post-reconstruction into Sygus): " << d_sygus_solution << std::endl; + } + }else{ + Trace("csi-sol") << "Post-process solution..." << std::endl; + Node prev = d_solution; + d_solution = postProcessSolution( d_solution ); + if( prev!=d_solution ){ + Trace("csi-sol") << "Solution (after post process) : " << d_solution << std::endl; + } + } + + + if( Trace.isOn("csi-sol") ){ + //debug solution + if( !d_sol->debugSolution( d_solution ) ){ + Trace("csi-sol") << "WARNING : solution " << d_solution << " contains free constants." << std::endl; + //exit( 47 ); + }else{ + //exit( 49 ); + } + } + if( Trace.isOn("cegqi-stats") ){ + int tsize, itesize; + tsize = 0;itesize = 0; + d_sol->debugTermSize( d_orig_solution, tsize, itesize ); + Trace("cegqi-stats") << tsize << " " << itesize << " "; + tsize = 0;itesize = 0; + d_sol->debugTermSize( d_solution, tsize, itesize ); + Trace("cegqi-stats") << tsize << " " << itesize << " "; + if( !d_sygus_solution.isNull() ){ + tsize = 0;itesize = 0; + d_sol->debugTermSize( d_sygus_solution, tsize, itesize ); + Trace("cegqi-stats") << tsize << " - "; + }else{ + Trace("cegqi-stats") << "null "; + } + Trace("cegqi-stats") << std::endl; + } + Node sol; + if( reconstructed==1 ){ + sol = d_sygus_solution; + }else if( reconstructed==-1 ){ + return Node::null(); + }else{ + sol = d_solution; + } + //make into lambda + if( !dt.getSygusVarList().isNull() ){ + Node varList = Node::fromExpr( dt.getSygusVarList() ); + return NodeManager::currentNM()->mkNode( LAMBDA, varList, sol ); + }else{ + return sol; + } +} + +bool CegConjectureSingleInv::needsCheck() { + if( options::cegqiSingleInvMode()==CEGQI_SI_MODE_ALL_ABORT ){ + if( !d_has_ites ){ + return d_inst.empty(); + } + } + return true; +} + +void CegConjectureSingleInv::preregisterConjecture( Node q ) { + d_orig_conjecture = q; +} + +bool DetTrace::DetTraceTrie::add( Node loc, std::vector< Node >& val, unsigned index ){ + if( index==val.size() ){ + if( d_children.empty() ){ + d_children[loc].clear(); + return true; + }else{ + return false; + } + }else{ + return d_children[val[index]].add( loc, val, index+1 ); + } +} + +Node DetTrace::DetTraceTrie::constructFormula( std::vector< Node >& vars, unsigned index ){ + if( index==vars.size() ){ + return NodeManager::currentNM()->mkConst( true ); + }else{ + std::vector< Node > disj; + for( std::map< Node, DetTraceTrie >::iterator it = d_children.begin(); it != d_children.end(); ++it ){ + Node eq = vars[index].eqNode( it->first ); + if( index<vars.size()-1 ){ + Node conc = it->second.constructFormula( vars, index+1 ); + disj.push_back( NodeManager::currentNM()->mkNode( kind::AND, eq, conc ) ); + }else{ + disj.push_back( eq ); + } + } + Assert( !disj.empty() ); + return disj.size()==1 ? disj[0] : NodeManager::currentNM()->mkNode( kind::OR, disj ); + } +} + +bool DetTrace::increment( Node loc, std::vector< Node >& vals ){ + if( d_trie.add( loc, vals ) ){ + for( unsigned i=0; i<vals.size(); i++ ){ + d_curr[i] = vals[i]; + } + return true; + }else{ + return false; + } +} + +Node DetTrace::constructFormula( std::vector< Node >& vars ) { + return d_trie.constructFormula( vars ); +} + + +void DetTrace::print( const char* c ) { + for( unsigned i=0; i<d_curr.size(); i++ ){ + Trace(c) << d_curr[i] << " "; + } +} + +void TransitionInference::initialize( Node f, std::vector< Node >& vars ) { + Assert( d_vars.empty() ); + d_func = f; + d_vars.insert( d_vars.end(), vars.begin(), vars.end() ); +} + + +void TransitionInference::getConstantSubstitution( std::vector< Node >& vars, std::vector< Node >& disjuncts, std::vector< Node >& const_var, std::vector< Node >& const_subs, bool reqPol ) { + for( unsigned j=0; j<disjuncts.size(); j++ ){ + Node sn; + if( !const_var.empty() ){ + sn = disjuncts[j].substitute( const_var.begin(), const_var.end(), const_subs.begin(), const_subs.end() ); + sn = Rewriter::rewrite( sn ); + }else{ + sn = disjuncts[j]; + } + bool slit_pol = sn.getKind()!=NOT; + Node slit = sn.getKind()==NOT ? sn[0] : sn; + if( slit.getKind()==EQUAL && slit_pol==reqPol ){ + // check if it is a variable equality + TNode v; + Node s; + for( unsigned r=0; r<2; r++ ){ + if( std::find( vars.begin(), vars.end(), slit[r] )!=vars.end() ){ + if( !TermUtil::containsTerm( slit[1-r], slit[r] ) ){ + v = slit[r]; + s = slit[1-r]; + break; + } + } + } + if( v.isNull() ){ + //solve for var + std::map< Node, Node > msum; + if (ArithMSum::getMonomialSumLit(slit, msum)) + { + for( std::map< Node, Node >::iterator itm = msum.begin(); itm != msum.end(); ++itm ){ + if( std::find( vars.begin(), vars.end(), itm->first )!=vars.end() ){ + Node veq_c; + Node val; + int ires = + ArithMSum::isolate(itm->first, msum, veq_c, val, EQUAL); + if( ires!=0 && veq_c.isNull() && !TermUtil::containsTerm( val, itm->first ) ){ + v = itm->first; + s = val; + } + } + } + } + } + if( !v.isNull() ){ + TNode ts = s; + for( unsigned k=0; k<const_subs.size(); k++ ){ + const_subs[k] = Rewriter::rewrite( const_subs[k].substitute( v, ts ) ); + } + Trace("cegqi-inv-debug2") << "...substitution : " << v << " -> " << s << std::endl; + const_var.push_back( v ); + const_subs.push_back( s ); + } + } + } +} + +void TransitionInference::process( Node n ) { + d_complete = true; + std::vector< Node > n_check; + if( n.getKind()==AND ){ + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + n_check.push_back( n[i] ); + } + }else{ + n_check.push_back( n ); + } + for( unsigned i=0; i<n_check.size(); i++ ){ + Node nn = n_check[i]; + std::map< Node, bool > visited; + std::map< bool, Node > terms; + std::vector< Node > disjuncts; + Trace("cegqi-inv") << "TransitionInference : Process disjunct : " << nn << std::endl; + if( processDisjunct( nn, terms, disjuncts, visited, true ) ){ + if( !terms.empty() ){ + Node norm_args; + int comp_num; + std::map< bool, Node >::iterator itt = terms.find( false ); + if( itt!=terms.end() ){ + norm_args = itt->second; + if( terms.find( true )!=terms.end() ){ + comp_num = 0; + }else{ + comp_num = -1; + } + }else{ + norm_args = terms[true]; + comp_num = 1; + } + std::vector< Node > subs; + for( unsigned j=0; j<norm_args.getNumChildren(); j++ ){ + subs.push_back( norm_args[j] ); + } + Trace("cegqi-inv-debug2") << " normalize based on " << norm_args << std::endl; + Assert( d_vars.size()==subs.size() ); + for( unsigned j=0; j<disjuncts.size(); j++ ){ + disjuncts[j] = Rewriter::rewrite( disjuncts[j].substitute( subs.begin(), subs.end(), d_vars.begin(), d_vars.end() ) ); + Trace("cegqi-inv-debug2") << " ..." << disjuncts[j] << std::endl; + } + std::vector< Node > const_var; + std::vector< Node > const_subs; + if( comp_num==0 ){ + //transition + Assert( terms.find( true )!=terms.end() ); + Node next = terms[true]; + next = Rewriter::rewrite( next.substitute( subs.begin(), subs.end(), d_vars.begin(), d_vars.end() ) ); + Trace("cegqi-inv-debug") << "transition next predicate : " << next << std::endl; + // normalize the other direction + std::vector< Node > rvars; + for( unsigned i=0; i<next.getNumChildren(); i++ ){ + rvars.push_back( next[i] ); + } + if( d_prime_vars.size()<next.getNumChildren() ){ + for( unsigned i=0; i<next.getNumChildren(); i++ ){ + Node v = NodeManager::currentNM()->mkSkolem( "ir", next[i].getType(), "template inference rev argument" ); + d_prime_vars.push_back( v ); + } + } + Trace("cegqi-inv-debug2") << " normalize based on " << next << std::endl; + Assert( d_vars.size()==subs.size() ); + for( unsigned j=0; j<disjuncts.size(); j++ ){ + disjuncts[j] = Rewriter::rewrite( disjuncts[j].substitute( rvars.begin(), rvars.end(), d_prime_vars.begin(), d_prime_vars.end() ) ); + Trace("cegqi-inv-debug2") << " ..." << disjuncts[j] << std::endl; + } + getConstantSubstitution( d_prime_vars, disjuncts, const_var, const_subs, false ); + }else{ + getConstantSubstitution( d_vars, disjuncts, const_var, const_subs, false ); + } + Node res; + if( disjuncts.empty() ){ + res = NodeManager::currentNM()->mkConst( false ); + }else if( disjuncts.size()==1 ){ + res = disjuncts[0]; + }else{ + res = NodeManager::currentNM()->mkNode( kind::OR, disjuncts ); + } + if( !res.hasBoundVar() ){ + Trace("cegqi-inv") << "*** inferred " << ( comp_num==1 ? "pre" : ( comp_num==-1 ? "post" : "trans" ) ) << "-condition : " << res << std::endl; + d_com[comp_num].d_conjuncts.push_back( res ); + if( !const_var.empty() ){ + bool has_const_eq = const_var.size()==d_vars.size(); + Trace("cegqi-inv") << " with constant substitution, complete = " << has_const_eq << " : " << std::endl; + for( unsigned i=0; i<const_var.size(); i++ ){ + Trace("cegqi-inv") << " " << const_var[i] << " -> " << const_subs[i] << std::endl; + if( has_const_eq ){ + d_com[comp_num].d_const_eq[res][const_var[i]] = const_subs[i]; + } + } + Trace("cegqi-inv") << "...size = " << const_var.size() << ", #vars = " << d_vars.size() << std::endl; + } + }else{ + Trace("cegqi-inv-debug2") << "...failed, free variable." << std::endl; + d_complete = false; + } + } + }else{ + d_complete = false; + } + } + + // finalize the components + for( int i=-1; i<=1; i++ ){ + Node ret; + if( d_com[i].d_conjuncts.empty() ){ + ret = NodeManager::currentNM()->mkConst( true ); + }else if( d_com[i].d_conjuncts.size()==1 ){ + ret = d_com[i].d_conjuncts[0]; + }else{ + ret = NodeManager::currentNM()->mkNode( kind::AND, d_com[i].d_conjuncts ); + } + if( i==0 || i==1 ){ + // pre-condition and transition are negated + ret = TermUtil::simpleNegate( ret ); + } + d_com[i].d_this = ret; + } +} + +bool TransitionInference::processDisjunct( Node n, std::map< bool, Node >& terms, std::vector< Node >& disjuncts, + std::map< Node, bool >& visited, bool topLevel ) { + if( visited.find( n )==visited.end() ){ + visited[n] = true; + bool childTopLevel = n.getKind()==OR && topLevel; + //if another part mentions UF or a free variable, then fail + bool lit_pol = n.getKind()!=NOT; + Node lit = n.getKind()==NOT ? n[0] : n; + if( lit.getKind()==APPLY_UF ){ + Node op = lit.getOperator(); + if( d_func.isNull() ){ + d_func = op; + Trace("cegqi-inv-debug") << "Use " << op << " with args "; + for( unsigned i=0; i<lit.getNumChildren(); i++ ){ + Node v = NodeManager::currentNM()->mkSkolem( "i", lit[i].getType(), "template inference argument" ); + d_vars.push_back( v ); + Trace("cegqi-inv-debug") << v << " "; + } + Trace("cegqi-inv-debug") << std::endl; + } + if( op!=d_func ){ + Trace("cegqi-inv-debug") << "...failed, free function : " << n << std::endl; + return false; + }else if( topLevel ){ + if( terms.find( lit_pol )==terms.end() ){ + terms[lit_pol] = lit; + return true; + }else{ + Trace("cegqi-inv-debug") << "...failed, repeated inv-app : " << lit << std::endl; + return false; + } + }else{ + Trace("cegqi-inv-debug") << "...failed, non-entailed inv-app : " << lit << std::endl; + return false; + } + }else if( topLevel && !childTopLevel ){ + disjuncts.push_back( n ); + } + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + if( !processDisjunct( n[i], terms, disjuncts, visited, childTopLevel ) ){ + return false; + } + } + } + return true; +} + +Node TransitionInference::getComponent( int i ) { + return d_com[i].d_this; +} + +int TransitionInference::initializeTrace( DetTrace& dt, Node loc, bool fwd ) { + int index = fwd ? 1 : -1; + Assert( d_com[index].has( loc ) ); + std::map< Node, std::map< Node, Node > >::iterator it = d_com[index].d_const_eq.find( loc ); + if( it!=d_com[index].d_const_eq.end() ){ + std::vector< Node > next; + for( unsigned i=0; i<d_vars.size(); i++ ){ + Node v = d_vars[i]; + Assert( it->second.find( v )!=it->second.end() ); + next.push_back( it->second[v] ); + dt.d_curr.push_back( it->second[v] ); + } + Trace("cegqi-inv-debug2") << "dtrace : initial increment" << std::endl; + bool ret = dt.increment( loc, next ); + AlwaysAssert( ret ); + return 0; + } + return -1; +} + +int TransitionInference::incrementTrace( DetTrace& dt, Node loc, bool fwd ) { + Assert( d_com[0].has( loc ) ); + // check if it satisfies the pre/post condition + int check_index = fwd ? -1 : 1; + Node cc = getComponent( check_index ); + Assert( !cc.isNull() ); + Node ccr = Rewriter::rewrite( cc.substitute( d_vars.begin(), d_vars.end(), dt.d_curr.begin(), dt.d_curr.end() ) ); + if( ccr.isConst() ){ + if( ccr.getConst<bool>()==( fwd ? false : true ) ){ + Trace("cegqi-inv-debug2") << "dtrace : counterexample" << std::endl; + return 2; + } + } + + + // terminates? + Node c = getComponent( 0 ); + Assert( !c.isNull() ); + + Assert( d_vars.size()==dt.d_curr.size() ); + Node cr = Rewriter::rewrite( c.substitute( d_vars.begin(), d_vars.end(), dt.d_curr.begin(), dt.d_curr.end() ) ); + if( cr.isConst() ){ + if( !cr.getConst<bool>() ){ + Trace("cegqi-inv-debug2") << "dtrace : terminated" << std::endl; + return 1; + }else{ + return -1; + } + } + if( fwd ){ + std::map< Node, std::map< Node, Node > >::iterator it = d_com[0].d_const_eq.find( loc ); + if( it!=d_com[0].d_const_eq.end() ){ + std::vector< Node > next; + for( unsigned i=0; i<d_prime_vars.size(); i++ ){ + Node pv = d_prime_vars[i]; + Assert( it->second.find( pv )!=it->second.end() ); + Node pvs = it->second[pv]; + Assert( d_vars.size()==dt.d_curr.size() ); + Node pvsr = Rewriter::rewrite( pvs.substitute( d_vars.begin(), d_vars.end(), dt.d_curr.begin(), dt.d_curr.end() ) ); + next.push_back( pvsr ); + } + if( dt.increment( loc, next ) ){ + Trace("cegqi-inv-debug2") << "dtrace : success increment" << std::endl; + return 0; + }else{ + // looped + Trace("cegqi-inv-debug2") << "dtrace : looped" << std::endl; + return 1; + } + } + }else{ + //TODO + } + return -1; +} + +int TransitionInference::initializeTrace( DetTrace& dt, bool fwd ) { + Trace("cegqi-inv-debug2") << "Initialize trace" << std::endl; + int index = fwd ? 1 : -1; + if( d_com[index].d_conjuncts.size()==1 ){ + return initializeTrace( dt, d_com[index].d_conjuncts[0], fwd ); + }else{ + return -1; + } +} + +int TransitionInference::incrementTrace( DetTrace& dt, bool fwd ) { + if( d_com[0].d_conjuncts.size()==1 ){ + return incrementTrace( dt, d_com[0].d_conjuncts[0], fwd ); + }else{ + return -1; + } +} + +Node TransitionInference::constructFormulaTrace( DetTrace& dt ) { + return dt.constructFormula( d_vars ); +} + +} //namespace CVC4 + diff --git a/src/theory/quantifiers/sygus/ce_guided_single_inv.h b/src/theory/quantifiers/sygus/ce_guided_single_inv.h new file mode 100644 index 000000000..abdbef708 --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_single_inv.h @@ -0,0 +1,248 @@ +/********************* */ +/*! \file ce_guided_single_inv.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief utility for processing single invocation synthesis conjectures + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_SINGLE_INV_H +#define __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_SINGLE_INV_H + +#include "context/cdlist.h" +#include "theory/quantifiers/sygus/ce_guided_single_inv_sol.h" +#include "theory/quantifiers/inst_match_trie.h" +#include "theory/quantifiers/cegqi/inst_strategy_cbqi.h" +#include "theory/quantifiers/single_inv_partition.h" +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +class CegConjecture; +class CegConjectureSingleInv; +class CegEntailmentInfer; + +class CegqiOutputSingleInv : public CegqiOutput { +public: + CegqiOutputSingleInv( CegConjectureSingleInv * out ) : d_out( out ){} + virtual ~CegqiOutputSingleInv() {} + CegConjectureSingleInv * d_out; + bool doAddInstantiation( std::vector< Node >& subs ); + bool isEligibleForInstantiation( Node n ); + bool addLemma( Node lem ); +}; + +class DetTrace { +private: + class DetTraceTrie { + public: + std::map< Node, DetTraceTrie > d_children; + bool add( Node loc, std::vector< Node >& val, unsigned index = 0 ); + void clear() { d_children.clear(); } + Node constructFormula( std::vector< Node >& vars, unsigned index = 0 ); + }; + DetTraceTrie d_trie; +public: + std::vector< Node > d_curr; + bool increment( Node loc, std::vector< Node >& vals ); + Node constructFormula( std::vector< Node >& vars ); + void print( const char* c ); +}; + +class TransitionInference { +private: + bool processDisjunct( Node n, std::map< bool, Node >& terms, std::vector< Node >& disjuncts, std::map< Node, bool >& visited, bool topLevel ); + void getConstantSubstitution( std::vector< Node >& vars, std::vector< Node >& disjuncts, std::vector< Node >& const_var, std::vector< Node >& const_subs, bool reqPol ); + bool d_complete; +public: + TransitionInference() : d_complete( false ) {} + std::vector< Node > d_vars; + std::vector< Node > d_prime_vars; + Node d_func; + + class Component { + public: + Component(){} + Node d_this; + std::vector< Node > d_conjuncts; + std::map< Node, std::map< Node, Node > > d_const_eq; + bool has( Node c ) { return std::find( d_conjuncts.begin(), d_conjuncts.end(), c )!=d_conjuncts.end(); } + }; + std::map< int, Component > d_com; + + void initialize( Node f, std::vector< Node >& vars ); + void process( Node n ); + Node getComponent( int i ); + bool isComplete() { return d_complete; } + + // 0 : success, 1 : terminated, 2 : counterexample, -1 : invalid + int initializeTrace( DetTrace& dt, Node loc, bool fwd = true ); + int incrementTrace( DetTrace& dt, Node loc, bool fwd = true ); + int initializeTrace( DetTrace& dt, bool fwd = true ); + int incrementTrace( DetTrace& dt, bool fwd = true ); + Node constructFormulaTrace( DetTrace& dt ); +}; + +// this class infers whether a conjecture is single invocation (Reynolds et al CAV 2015), and sets up the +// counterexample-guided quantifier instantiation utility (d_cinst), and methods for solution +// reconstruction (d_sol). +// It also has more advanced techniques for: +// (1) partitioning a conjecture into single invocation / non-single invocation portions for invariant synthesis, +// (2) inferring whether the conjecture corresponds to a deterministic transistion system (by utility d_ti). +// For these techniques, we may generate a template (d_templ) which specifies a restricted +// solution space. We may in turn embed this template as a SyGuS grammar. +class CegConjectureSingleInv { + private: + friend class CegqiOutputSingleInv; + //presolve + void collectPresolveEqTerms( Node n, + std::map< Node, std::vector< Node > >& teq ); + void getPresolveEqConjuncts( std::vector< Node >& vars, + std::vector< Node >& terms, + std::map< Node, std::vector< Node > >& teq, + Node n, std::vector< Node >& conj ); + // constructing solution + Node constructSolution(std::vector<unsigned>& indices, unsigned i, + unsigned index, std::map<Node, Node>& weak_imp); + Node postProcessSolution(Node n); + private: + QuantifiersEngine* d_qe; + CegConjecture* d_parent; + // single invocation inference utility + SingleInvocationPartition* d_sip; + // transition inference module for each function to synthesize + std::map< Node, TransitionInference > d_ti; + // solution reconstruction + CegConjectureSingleInvSol* d_sol; + // the instantiator's output channel + CegqiOutputSingleInv* d_cosi; + // the instantiator + CegInstantiator* d_cinst; + + // list of skolems for each argument of programs + std::vector<Node> d_single_inv_arg_sk; + // list of variables/skolems for each program + std::vector<Node> d_single_inv_var; + std::vector<Node> d_single_inv_sk; + std::map<Node, int> d_single_inv_sk_index; + // program to solution index + std::map<Node, unsigned> d_prog_to_sol_index; + // lemmas produced + inst::InstMatchTrie d_inst_match_trie; + inst::CDInstMatchTrie* d_c_inst_match_trie; + // original conjecture + Node d_orig_conjecture; + // solution + Node d_orig_solution; + Node d_solution; + Node d_sygus_solution; + // whether the grammar for our solution allows ITEs, this tells us when reconstruction is infeasible + bool d_has_ites; + + public: + // lemmas produced + std::vector<Node> d_lemmas_produced; + std::vector<std::vector<Node> > d_inst; + + private: + std::vector<Node> d_curr_lemmas; + // add instantiation + bool doAddInstantiation( std::vector< Node >& subs ); + //is eligible for instantiation + bool isEligibleForInstantiation( Node n ); + // add lemma + bool addLemma( Node lem ); + // conjecture + Node d_quant; + Node d_simp_quant; + // are we single invocation? + bool d_single_invocation; + // single invocation portion of quantified formula + Node d_single_inv; + Node d_si_guard; + // transition relation version per program + std::map< Node, Node > d_trans_pre; + std::map< Node, Node > d_trans_post; + // the template for each function to synthesize + std::map< Node, Node > d_templ; + // the template argument for each function to synthesize (occurs in exactly one position of its template) + std::map< Node, Node > d_templ_arg; + + public: + CegConjectureSingleInv( QuantifiersEngine * qe, CegConjecture * p ); + ~CegConjectureSingleInv(); + + // get simplified conjecture + Node getSimplifiedConjecture() { return d_simp_quant; } + // get single invocation guard + Node getGuard() { return d_si_guard; } + public: + //get the single invocation lemma(s) + void getInitialSingleInvLemma( std::vector< Node >& lems ); + // initialize this class for synthesis conjecture q + void initialize( Node q ); + // finish initialize, sets up final decisions about whether to use single invocation techniques + // syntaxRestricted is whether the syntax for solutions for the initialized conjecture is restricted + // hasItes is whether the syntax for solutions for the initialized conjecture allows ITEs + void finishInit( bool syntaxRestricted, bool hasItes ); + //check + bool check( std::vector< Node >& lems ); + //get solution + Node getSolution( unsigned sol_index, TypeNode stn, int& reconstructed, bool rconsSygus = true ); + //reconstruct to syntax + Node reconstructToSyntax( Node s, TypeNode stn, int& reconstructed, + bool rconsSygus = true ); + // has ites + bool hasITEs() { return d_has_ites; } + // is single invocation + bool isSingleInvocation() const { return !d_single_inv.isNull(); } + //needs check + bool needsCheck(); + /** preregister conjecture */ + void preregisterConjecture( Node q ); + + Node getTransPre(Node prog) const { + std::map<Node, Node>::const_iterator location = d_trans_pre.find(prog); + return location->second; + } + + Node getTransPost(Node prog) const { + std::map<Node, Node>::const_iterator location = d_trans_post.find(prog); + return location->second; + } + // get template for program prog. This returns a term of the form t[x] where x is the template argument (see below) + Node getTemplate(Node prog) const { + std::map<Node, Node>::const_iterator tmpl = d_templ.find(prog); + if( tmpl!=d_templ.end() ){ + return tmpl->second; + }else{ + return Node::null(); + } + } + // get the template argument for program prog. + // This is a variable which indicates the position of the function/predicate to synthesize. + Node getTemplateArg(Node prog) const { + std::map<Node, Node>::const_iterator tmpla = d_templ_arg.find(prog); + if( tmpla != d_templ_arg.end() ){ + return tmpla->second; + }else{ + return Node::null(); + } + } +}; + +}/* namespace CVC4::theory::quantifiers */ +}/* namespace CVC4::theory */ +}/* namespace CVC4 */ + +#endif diff --git a/src/theory/quantifiers/sygus/ce_guided_single_inv_sol.cpp b/src/theory/quantifiers/sygus/ce_guided_single_inv_sol.cpp new file mode 100644 index 000000000..f900297e5 --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_single_inv_sol.cpp @@ -0,0 +1,1512 @@ +/********************* */ +/*! \file ce_guided_single_inv_sol.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Paul Meng, Tim King + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief utility for processing single invocation synthesis conjectures + ** + **/ +#include "theory/quantifiers/sygus/ce_guided_single_inv_sol.h" + +#include "expr/datatype.h" +#include "options/quantifiers_options.h" +#include "theory/quantifiers/sygus/ce_guided_instantiation.h" +#include "theory/quantifiers/sygus/ce_guided_single_inv.h" +#include "theory/quantifiers/first_order_model.h" +#include "theory/quantifiers/quantifiers_attributes.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_enumeration.h" +#include "theory/quantifiers/term_util.h" +#include "theory/quantifiers/ematching/trigger.h" +#include "theory/theory_engine.h" + +using namespace CVC4::kind; +using namespace std; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +bool doCompare(Node a, Node b, Kind k) +{ + Node com = NodeManager::currentNM()->mkNode(k, a, b); + com = Rewriter::rewrite(com); + Assert(com.getType().isBoolean()); + return com.isConst() && com.getConst<bool>(); +} + +CegConjectureSingleInvSol::CegConjectureSingleInvSol(QuantifiersEngine* qe) + : d_qe(qe), d_id_count(0), d_root_id() {} + +bool CegConjectureSingleInvSol::debugSolution( Node sol ) { + if( sol.getKind()==SKOLEM ){ + return false; + }else{ + for( unsigned i=0; i<sol.getNumChildren(); i++ ){ + if( !debugSolution( sol[i] ) ){ + return false; + } + } + return true; + } + +} + +void CegConjectureSingleInvSol::debugTermSize( Node sol, int& t_size, int& num_ite ) { + std::map< Node, int >::iterator it = d_dterm_size.find( sol ); + if( it==d_dterm_size.end() ){ + int prev = t_size; + int prev_ite = num_ite; + t_size++; + if( sol.getKind()==ITE ){ + num_ite++; + } + for( unsigned i=0; i<sol.getNumChildren(); i++ ){ + debugTermSize( sol[i], t_size, num_ite ); + } + d_dterm_size[sol] = t_size-prev; + d_dterm_ite_size[sol] = num_ite-prev_ite; + }else{ + t_size += it->second; + num_ite += d_dterm_ite_size[sol]; + } +} + + +Node CegConjectureSingleInvSol::pullITEs( Node s ) { + if( s.getKind()==ITE ){ + bool success; + do { + success = false; + std::vector< Node > conj; + Node t; + Node rem; + if( pullITECondition( s, s, conj, t, rem, 0 ) ){ + Assert( !conj.empty() ); + Node cond = conj.size()==1 ? conj[0] : NodeManager::currentNM()->mkNode( AND, conj ); + Trace("csi-sol-debug") << "For " << s << ", can pull " << cond << " -> " << t << " with remainder " << rem << std::endl; + t = pullITEs( t ); + rem = pullITEs( rem ); + Trace("csi-pull-ite") << "PI: Rewrite : " << s << std::endl; + Node prev = s; + s = NodeManager::currentNM()->mkNode( ITE, TermUtil::simpleNegate( cond ), t, rem ); + Trace("csi-pull-ite") << "PI: Rewrite Now : " << s << std::endl; + Trace("csi-pull-ite") << "(= " << prev << " " << s << ")" << std::endl; + success = true; + } + }while( success ); + } + return s; +} + +// pull condition common to all ITE conditions in path of size > 1 +bool CegConjectureSingleInvSol::pullITECondition( Node root, Node n_ite, std::vector< Node >& conj, Node& t, Node& rem, int depth ) { + Assert( n_ite.getKind()==ITE ); + std::vector< Node > curr_conj; + std::vector< Node > orig_conj; + bool isAnd; + if( n_ite[0].getKind()==AND || n_ite[0].getKind()==OR ){ + isAnd = n_ite[0].getKind()==AND; + for( unsigned i=0; i<n_ite[0].getNumChildren(); i++ ){ + Node cond = n_ite[0][i]; + orig_conj.push_back( cond ); + if( n_ite[0].getKind()==OR ){ + cond = TermUtil::simpleNegate( cond ); + } + curr_conj.push_back( cond ); + } + }else{ + Node neg = n_ite[0].negate(); + if( std::find( conj.begin(), conj.end(), neg )!=conj.end() ){ + //if negation of condition exists, use it + isAnd = false; + curr_conj.push_back( neg ); + }else{ + //otherwise, use condition + isAnd = true; + curr_conj.push_back( n_ite[0] ); + } + orig_conj.push_back( n_ite[0] ); + } + // take intersection with current conditions + std::vector< Node > new_conj; + std::vector< Node > prev_conj; + if( n_ite==root ){ + new_conj.insert( new_conj.end(), curr_conj.begin(), curr_conj.end() ); + Trace("csi-sol-debug") << "Pull ITE root " << n_ite << ", #conj = " << new_conj.size() << std::endl; + }else{ + for( unsigned i=0; i<curr_conj.size(); i++ ){ + if( std::find( conj.begin(), conj.end(), curr_conj[i] )!=conj.end() ){ + new_conj.push_back( curr_conj[i] ); + } + } + Trace("csi-sol-debug") << "Pull ITE " << n_ite << ", #conj = " << conj.size() << " intersect " << curr_conj.size() << " = " << new_conj.size() << std::endl; + } + //cannot go further + if( new_conj.empty() ){ + return false; + } + //it is an intersection with current + conj.clear(); + conj.insert( conj.end(), new_conj.begin(), new_conj.end() ); + //recurse if possible + Node trec = n_ite[ isAnd ? 2 : 1 ]; + Node tval = n_ite[ isAnd ? 1 : 2 ]; + bool success = false; + if( trec.getKind()==ITE ){ + prev_conj.insert( prev_conj.end(), conj.begin(), conj.end() ); + success = pullITECondition( root, trec, conj, t, rem, depth+1 ); + } + if( !success && depth>0 ){ + t = trec; + rem = trec; + success = true; + if( trec.getKind()==ITE ){ + //restore previous state + conj.clear(); + conj.insert( conj.end(), prev_conj.begin(), prev_conj.end() ); + } + } + if( success ){ + //make remainder : strip out conditions in conj + Assert( !conj.empty() ); + std::vector< Node > cond_c; + Assert( orig_conj.size()==curr_conj.size() ); + for( unsigned i=0; i<curr_conj.size(); i++ ){ + if( std::find( conj.begin(), conj.end(), curr_conj[i] )==conj.end() ){ + cond_c.push_back( orig_conj[i] ); + } + } + if( cond_c.empty() ){ + rem = tval; + }else{ + Node new_cond = cond_c.size()==1 ? cond_c[0] : NodeManager::currentNM()->mkNode( n_ite[0].getKind(), cond_c ); + rem = NodeManager::currentNM()->mkNode( ITE, new_cond, isAnd ? tval : rem, isAnd ? rem : tval ); + } + return true; + }else{ + return false; + } +} + +Node CegConjectureSingleInvSol::flattenITEs( Node n, bool rec ) { + Assert( !n.isNull() ); + if( n.getKind()==ITE ){ + Trace("csi-sol-debug") << "Flatten ITE." << std::endl; + Node ret; + Node n0 = rec ? flattenITEs( n[0] ) : n[0]; + Node n1 = rec ? flattenITEs( n[1] ) : n[1]; + Node n2 = rec ? flattenITEs( n[2] ) : n[2]; + Assert( !n0.isNull() ); + Assert( !n1.isNull() ); + Assert( !n2.isNull() ); + if( n0.getKind()==NOT ){ + ret = NodeManager::currentNM()->mkNode( ITE, n0[0], n2, n1 ); + }else if( n0.getKind()==AND || n0.getKind()==OR ){ + std::vector< Node > children; + for( unsigned i=1; i<n0.getNumChildren(); i++ ){ + children.push_back( n0[i] ); + } + Node rem = children.size()==1 ? children[0] : NodeManager::currentNM()->mkNode( n0.getKind(), children ); + if( n0.getKind()==AND ){ + ret = NodeManager::currentNM()->mkNode( ITE, rem, NodeManager::currentNM()->mkNode( ITE, n0[0], n1, n2 ), n2 ); + }else{ + ret = NodeManager::currentNM()->mkNode( ITE, rem, n1, NodeManager::currentNM()->mkNode( ITE, n0[0], n1, n2 ) ); + } + }else{ + if( n0.getKind()==ITE ){ + n0 = NodeManager::currentNM()->mkNode( OR, NodeManager::currentNM()->mkNode( AND, n0, n1 ), + NodeManager::currentNM()->mkNode( AND, n0.negate(), n2 ) ); + }else if( n0.getKind()==EQUAL && n0[0].getType().isBoolean() ){ + n0 = NodeManager::currentNM()->mkNode( OR, NodeManager::currentNM()->mkNode( AND, n0, n1 ), + NodeManager::currentNM()->mkNode( AND, n0.negate(), n1.negate() ) ); + }else{ + return NodeManager::currentNM()->mkNode( ITE, n0, n1, n2 ); + } + ret = NodeManager::currentNM()->mkNode( ITE, n0, n1, n2 ); + } + Assert( !ret.isNull() ); + return flattenITEs( ret, false ); + }else{ + if( n.getNumChildren()>0 ){ + std::vector< Node > children; + if( n.getMetaKind() == kind::metakind::PARAMETERIZED ){ + children.push_back( n.getOperator() ); + } + bool childChanged = false; + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + Node nc = flattenITEs( n[i] ); + children.push_back( nc ); + childChanged = childChanged || nc!=n[i]; + } + if( !childChanged ){ + return n; + }else{ + return NodeManager::currentNM()->mkNode( n.getKind(), children ); + } + }else{ + return n; + } + } +} + +// assign is from literals to booleans +// union_find is from args to values + +bool CegConjectureSingleInvSol::getAssign( bool pol, Node n, std::map< Node, bool >& assign, std::vector< Node >& new_assign, std::vector< Node >& vars, + std::vector< Node >& new_vars, std::vector< Node >& new_subs ) { + std::map< Node, bool >::iterator ita = assign.find( n ); + if( ita!=assign.end() ){ + Trace("csi-simp-debug") << "---already assigned, lookup " << pol << " " << ita->second << std::endl; + return pol==ita->second; + }else if( n.isConst() ){ + return pol==(n==d_qe->getTermUtil()->d_true); + }else{ + Trace("csi-simp-debug") << "---assign " << n << " " << pol << std::endl; + assign[n] = pol; + new_assign.push_back( n ); + if( ( pol && n.getKind()==AND ) || ( !pol && n.getKind()==OR ) ){ + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + if( !getAssign( pol, n[i], assign, new_assign, vars, new_vars, new_subs ) ){ + return false; + } + } + }else if( n.getKind()==NOT ){ + return getAssign( !pol, n[0], assign, new_assign, vars, new_vars, new_subs ); + }else if( pol && n.getKind()==EQUAL ){ + getAssignEquality( n, vars, new_vars, new_subs ); + } + } + return true; +} + +bool CegConjectureSingleInvSol::getAssignEquality( Node eq, std::vector< Node >& vars, std::vector< Node >& new_vars, std::vector< Node >& new_subs ) { + Assert( eq.getKind()==EQUAL ); + //try to find valid argument + for( unsigned r=0; r<2; r++ ){ + if( std::find( d_varList.begin(), d_varList.end(), eq[r] )!=d_varList.end() ){ + Assert( std::find( vars.begin(), vars.end(), eq[r] )==vars.end() ); + if( std::find( new_vars.begin(), new_vars.end(), eq[r] )==new_vars.end() ){ + Node eqro = eq[r==0 ? 1 : 0 ]; + if( !d_qe->getTermUtil()->containsTerm( eqro, eq[r] ) ){ + Trace("csi-simp-debug") << "---equality " << eq[r] << " = " << eqro << std::endl; + new_vars.push_back( eq[r] ); + new_subs.push_back( eqro ); + return true; + } + } + } + } + return false; +} + +Node CegConjectureSingleInvSol::simplifySolution( Node sol, TypeNode stn ){ + int tsize, itesize; + if( Trace.isOn("csi-sol") ){ + tsize = 0;itesize = 0; + debugTermSize( sol, tsize, itesize ); + Trace("csi-sol") << tsize << " " << itesize << " rewrite..." << std::endl; + Trace("csi-sol-debug") << "sol : " << sol << "..." << std::endl; + } + Node sol0 = Rewriter::rewrite( sol ); + Trace("csi-sol") << "now : " << sol0 << std::endl; + + Node curr_sol = sol0; + Node prev_sol; + do{ + prev_sol = curr_sol; + //first, pull ITE conditions + if( Trace.isOn("csi-sol") ){ + tsize = 0;itesize = 0; + debugTermSize( curr_sol, tsize, itesize ); + Trace("csi-sol") << tsize << " " << itesize << " pull ITE..." << std::endl; + Trace("csi-sol-debug") << "sol : " << curr_sol << "..." << std::endl; + } + Node sol1 = pullITEs( curr_sol ); + Trace("csi-sol") << "now : " << sol1 << std::endl; + //do standard rewriting + if( sol1!=curr_sol ){ + if( Trace.isOn("csi-sol") ){ + tsize = 0;itesize = 0; + debugTermSize( sol1, tsize, itesize ); + Trace("csi-sol") << tsize << " " << itesize << " rewrite..." << std::endl; + Trace("csi-sol-debug") << "sol : " << sol1 << "..." << std::endl; + } + Node sol2 = Rewriter::rewrite( sol1 ); + Trace("csi-sol") << "now : " << sol2 << std::endl; + curr_sol = sol2; + } + //now do branch analysis + if( Trace.isOn("csi-sol") ){ + tsize = 0;itesize = 0; + debugTermSize( curr_sol, tsize, itesize ); + Trace("csi-sol") << tsize << " " << itesize << " simplify solution..." << std::endl; + Trace("csi-sol-debug") << "sol : " << curr_sol << "..." << std::endl; + } + std::map< Node, bool > sassign; + std::vector< Node > svars; + std::vector< Node > ssubs; + Node sol3 = simplifySolutionNode( curr_sol, stn, sassign, svars, ssubs, 0 ); + Trace("csi-sol") << "now : " << sol3 << std::endl; + if( sol3!=curr_sol ){ + //do standard rewriting again + if( Trace.isOn("csi-sol" ) ){ + tsize = 0;itesize = 0; + debugTermSize( sol3, tsize, itesize ); + Trace("csi-sol") << tsize << " " << itesize << " rewrite..." << std::endl; + } + Node sol4 = Rewriter::rewrite( sol3 ); + Trace("csi-sol") << "now : " << sol4 << std::endl; + curr_sol = sol4; + } + }while( curr_sol!=prev_sol ); + + return curr_sol; +} + +Node CegConjectureSingleInvSol::simplifySolutionNode( Node sol, TypeNode stn, std::map< Node, bool >& assign, + std::vector< Node >& vars, std::vector< Node >& subs, int status ) { + + Assert( vars.size()==subs.size() ); + std::map< Node, bool >::iterator ita = assign.find( sol ); + if( ita!=assign.end() ){ + //it is currently assigned a boolean value + return NodeManager::currentNM()->mkConst( ita->second ); + }else{ + d_qe->getTermDatabaseSygus()->registerSygusType( stn ); + std::map< int, TypeNode > stnc; + if( !stn.isNull() ){ + int karg = d_qe->getTermDatabaseSygus()->getKindConsNum( stn, sol.getKind() ); + if( karg!=-1 ){ + const Datatype& dt = ((DatatypeType)(stn).toType()).getDatatype(); + if( dt[karg].getNumArgs()==sol.getNumChildren() ){ + for( unsigned i=0; i<dt[karg].getNumArgs(); i++ ){ + stnc[i] = d_qe->getTermDatabaseSygus()->getArgType( dt[karg], i ); + } + } + } + } + + if( sol.getKind()==ITE ){ + Trace("csi-simp") << "Simplify ITE " << std::endl; + std::vector< Node > children; + for( unsigned r=1; r<=2; r++ ){ + std::vector< Node > new_assign; + std::vector< Node > new_vars; + std::vector< Node > new_subs; + if( getAssign( r==1, sol[0], assign, new_assign, vars, new_vars, new_subs ) ){ + Trace("csi-simp") << "- branch " << r << " led to " << new_assign.size() << " assignments, " << new_vars.size() << " equalities." << std::endl; + unsigned prev_size = vars.size(); + Node nc = sol[r]; + if( !new_vars.empty() ){ + nc = nc.substitute( new_vars.begin(), new_vars.end(), new_subs.begin(), new_subs.end() ); + vars.insert( vars.end(), new_vars.begin(), new_vars.end() ); + subs.insert( subs.end(), new_subs.begin(), new_subs.end() ); + } + nc = simplifySolutionNode( nc, stnc[r], assign, vars, subs, 0 ); + children.push_back( nc ); + //clean up substitution + if( !new_vars.empty() ){ + vars.resize( prev_size ); + subs.resize( prev_size ); + } + }else{ + Trace("csi-simp") << "- branch " << r << " of " << sol[0] << " is infeasible." << std::endl; + } + //clean up assignment + for( unsigned i=0; i<new_assign.size(); i++ ){ + assign.erase( new_assign[i] ); + } + } + if( children.size()==1 || ( children.size()==2 && children[0]==children[1] ) ){ + return children[0]; + }else{ + Assert( children.size()==2 ); + Node ncond = simplifySolutionNode( sol[0], stnc[0], assign, vars, subs, 0 ); + Node ret = NodeManager::currentNM()->mkNode( ITE, ncond, children[0], children[1] ); + + //expand/flatten if necessary + Node orig_ret = ret; + if( !stnc[0].isNull() ){ + d_qe->getTermDatabaseSygus()->registerSygusType( stnc[0] ); + Node prev_ret; + while( !d_qe->getTermDatabaseSygus()->hasKind( stnc[0], ret[0].getKind() ) && ret!=prev_ret ){ + prev_ret = ret; + Node exp_c = d_qe->getTermDatabaseSygus()->expandBuiltinTerm( ret[0] ); + if( !exp_c.isNull() ){ + Trace("csi-simp-debug") << "Pre expand to " << ret[0] << " to " << exp_c << std::endl; + ret = NodeManager::currentNM()->mkNode( ITE, exp_c, ret[1], ret[2] ); + } + if( !d_qe->getTermDatabaseSygus()->hasKind( stnc[0], ret[0].getKind() ) ){ + Trace("csi-simp-debug") << "Flatten based on " << ret[0] << "." << std::endl; + ret = flattenITEs( ret, false ); + } + } + } + return ret; + /* + if( orig_ret!=ret ){ + Trace("csi-simp") << "Try expanded ITE" << std::endl; + return ret;//simplifySolutionNode( ret, stn, assign, vars, subs, status ); + }else{ + return ret; + } + */ + } + }else if( sol.getKind()==OR || sol.getKind()==AND ){ + Trace("csi-simp") << "Simplify " << sol.getKind() << std::endl; + //collect new equalities + std::map< Node, bool > atoms; + std::vector< Node > inc; + std::vector< Node > children; + std::vector< Node > new_vars; + std::vector< Node > new_subs; + Node bc = sol.getKind()==OR ? d_qe->getTermUtil()->d_true : d_qe->getTermUtil()->d_false; + for( unsigned i=0; i<sol.getNumChildren(); i++ ){ + bool do_exc = false; + Node c; + std::map< Node, bool >::iterator ita = assign.find( sol[i] ); + if( ita==assign.end() ){ + c = sol[i]; + }else{ + c = NodeManager::currentNM()->mkConst( ita->second ); + } + Trace("csi-simp") << " - child " << i << " : " << c << std::endl; + if( c.isConst() ){ + if( c==bc ){ + Trace("csi-simp") << " ...singularity." << std::endl; + return bc; + }else{ + do_exc = true; + } + }else{ + Node atom = c.getKind()==NOT ? c[0] : c; + bool pol = c.getKind()!=NOT; + std::map< Node, bool >::iterator it = atoms.find( atom ); + if( it==atoms.end() ){ + atoms[atom] = pol; + if( status==0 && atom.getKind()==EQUAL ){ + if( pol==( sol.getKind()==AND ) ){ + Trace("csi-simp") << " ...equality." << std::endl; + if( getAssignEquality( atom, vars, new_vars, new_subs ) ){ + children.push_back( sol[i] ); + do_exc = true; + } + } + } + }else{ + //repeated atom + if( it->second!=pol ){ + return NodeManager::currentNM()->mkConst( sol.getKind()==OR ); + }else{ + do_exc = true; + } + } + } + if( !do_exc ){ + inc.push_back( sol[i] ); + }else{ + Trace("csi-simp") << " ...exclude." << std::endl; + } + } + if( !new_vars.empty() ){ + if( !inc.empty() ){ + Node ret = inc.size()==1 ? inc[0] : NodeManager::currentNM()->mkNode( sol.getKind(), inc ); + Trace("csi-simp") << "Base return is : " << ret << std::endl; + // apply substitution + ret = ret.substitute( new_vars.begin(), new_vars.end(), new_subs.begin(), new_subs.end() ); + ret = Rewriter::rewrite( ret ); + Trace("csi-simp") << "After substitution : " << ret << std::endl; + unsigned prev_size = vars.size(); + vars.insert( vars.end(), new_vars.begin(), new_vars.end() ); + subs.insert( subs.end(), new_subs.begin(), new_subs.end() ); + ret = simplifySolutionNode( ret, TypeNode::null(), assign, vars, subs, 1 ); + //clean up substitution + if( !vars.empty() ){ + vars.resize( prev_size ); + subs.resize( prev_size ); + } + //Trace("csi-simp") << "After simplification : " << ret << std::endl; + if( ret.isConst() ){ + if( ret==bc ){ + return bc; + } + }else{ + if( ret.getKind()==sol.getKind() ){ + for( unsigned i=0; i<ret.getNumChildren(); i++ ){ + children.push_back( ret[i] ); + } + }else{ + children.push_back( ret ); + } + } + } + }else{ + //recurse on children + for( unsigned i=0; i<inc.size(); i++ ){ + Node retc = simplifySolutionNode( inc[i], TypeNode::null(), assign, vars, subs, 0 ); + if( retc.isConst() ){ + if( retc==bc ){ + return bc; + } + }else{ + children.push_back( retc ); + } + } + } + // now, remove all equalities that are implied + std::vector< Node > final_children; + for( unsigned i=0; i<children.size(); i++ ){ + bool red = false; + Node atom = children[i].getKind()==NOT ? children[i][0] : children[i]; + bool pol = children[i].getKind()!=NOT; + if( status==0 && atom.getKind()==EQUAL ){ + if( pol!=( sol.getKind()==AND ) ){ + std::vector< Node > tmp_vars; + std::vector< Node > tmp_subs; + if( getAssignEquality( atom, vars, tmp_vars, tmp_subs ) ){ + Trace("csi-simp-debug") << "Check if " << children[i] << " is redundant in " << sol << std::endl; + for( unsigned j=0; j<children.size(); j++ ){ + if( j!=i && ( j>i || std::find( final_children.begin(), final_children.end(), children[j] )!=final_children.end() ) ){ + Node sj = children[j].substitute( tmp_vars.begin(), tmp_vars.end(), tmp_subs.begin(), tmp_subs.end() ); + sj = Rewriter::rewrite( sj ); + if( sj==( sol.getKind()==AND ? d_qe->getTermUtil()->d_false : d_qe->getTermUtil()->d_true ) ){ + Trace("csi-simp") << "--- " << children[i].negate() << " is implied by " << children[j].negate() << std::endl; + red = true; + break; + } + } + } + if( !red ){ + Trace("csi-simp-debug") << "...is not." << std::endl; + } + } + } + } + if( !red ){ + final_children.push_back( children[i] ); + } + } + return final_children.size()==0 ? NodeManager::currentNM()->mkConst( sol.getKind()==AND ) : + ( final_children.size()==1 ? final_children[0] : NodeManager::currentNM()->mkNode( sol.getKind(), final_children ) ); + }else{ + //generic simplification + std::vector< Node > children; + if( sol.getMetaKind() == kind::metakind::PARAMETERIZED ){ + children.push_back( sol.getOperator() ); + } + bool childChanged = false; + for( unsigned i=0; i<sol.getNumChildren(); i++ ){ + Node nc = simplifySolutionNode( sol[i], stnc[i], assign, vars, subs, 0 ); + childChanged = childChanged || nc!=sol[i]; + children.push_back( nc ); + } + if( childChanged ){ + return NodeManager::currentNM()->mkNode( sol.getKind(), children ); + } + } + return sol; + } +} + + +void CegConjectureSingleInvSol::preregisterConjecture( Node q ) { + Trace("csi-sol") << "Preregister conjecture : " << q << std::endl; + Node n = q; + if( n.getKind()==FORALL ){ + n = n[1]; + } + if( n.getKind()==EXISTS ){ + if( n[0].getNumChildren()==d_varList.size() ){ + std::vector< Node > evars; + for( unsigned i=0; i<n[0].getNumChildren(); i++ ){ + evars.push_back( n[0][i] ); + } + n = n[1].substitute( evars.begin(), evars.end(), d_varList.begin(), d_varList.end() ); + }else{ + Trace("csi-sol") << "Not the same number of variables, return." << std::endl; + return; + } + } + Trace("csi-sol") << "Preregister node for solution reconstruction : " << n << std::endl; + registerEquivalentTerms( n ); +} + +Node CegConjectureSingleInvSol::reconstructSolution( Node sol, TypeNode stn, int& reconstructed ) { + Trace("csi-rcons") << "Solution (pre-reconstruction) is : " << sol << std::endl; + int status; + d_root_id = collectReconstructNodes( sol, stn, status ); + if( status==0 ){ + Node ret = getReconstructedSolution( d_root_id ); + Trace("csi-rcons") << "Sygus solution is : " << ret << std::endl; + Assert( !ret.isNull() ); + reconstructed = 1; + return ret; + }else{ + //Trace("csi-debug-sol") << "Induced solution template is : " << d_templ_solution << std::endl; + if( Trace.isOn("csi-rcons") ){ + for( std::map< TypeNode, std::map< Node, int > >::iterator it = d_rcons_to_id.begin(); it != d_rcons_to_id.end(); ++it ){ + TypeNode tn = it->first; + Assert( tn.isDatatype() ); + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Trace("csi-rcons") << "Terms to reconstruct of type " << dt.getName() << " : " << std::endl; + for( std::map< Node, int >::iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2 ){ + if( d_reconstruct.find( it2->second )==d_reconstruct.end() ){ + Trace("csi-rcons") << " " << it2->first << std::endl; + } + } + Assert( !it->second.empty() ); + } + } + unsigned index = 0; + std::map< TypeNode, bool > active; + for( std::map< TypeNode, std::map< Node, int > >::iterator it = d_rcons_to_id.begin(); it != d_rcons_to_id.end(); ++it ){ + active[it->first] = true; + } + //enumerate for all types + do { + std::vector< TypeNode > to_erase; + for( std::map< TypeNode, bool >::iterator it = active.begin(); it != active.end(); ++it ){ + TypeNode stn = it->first; + Node ns = d_qe->getTermEnumeration()->getEnumerateTerm(stn, index); + if( ns.isNull() ){ + to_erase.push_back( stn ); + }else{ + Node nb = d_qe->getTermDatabaseSygus()->sygusToBuiltin( ns, stn ); + Node nr = Rewriter::rewrite( nb );//d_qe->getTermDatabaseSygus()->getNormalized( stn, nb, false, false ); + Trace("csi-rcons-debug2") << " - try " << ns << " -> " << nr << " for " << stn << " " << nr.getKind() << std::endl; + std::map< Node, int >::iterator itt = d_rcons_to_id[stn].find( nr ); + if( itt!= d_rcons_to_id[stn].end() ){ + // if it is not already reconstructed + if( d_reconstruct.find( itt->second )==d_reconstruct.end() ){ + Trace("csi-rcons") << "...reconstructed " << ns << " for term " << nr << std::endl; + bool do_check = true;//getPathToRoot( itt->second ); + setReconstructed( itt->second, ns ); + if( do_check ){ + Trace("csi-rcons-debug") << "...path to root, try reconstruction." << std::endl; + d_tmp_fail.clear(); + Node ret = getReconstructedSolution( d_root_id ); + if( !ret.isNull() ){ + Trace("csi-rcons") << "Sygus solution (after enumeration) is : " << ret << std::endl; + reconstructed = 1; + return ret; + } + }else{ + Trace("csi-rcons-debug") << "...no path to root." << std::endl; + } + } + } + } + } + for( unsigned i=0; i<to_erase.size(); i++ ){ + active.erase( to_erase[i] ); + } + index++; + if( index%100==0 ){ + Trace("csi-rcons-stats") << "Tried " << index << " for each type." << std::endl; + } + }while( !active.empty() ); + + // we ran out of elements, return null + reconstructed = -1; + Warning() << CommandFailure("Cannot get synth function: reconstruction to syntax failed."); + return Node::null(); // return sol; + } +} + +int CegConjectureSingleInvSol::collectReconstructNodes( Node t, TypeNode stn, int& status ) { + std::map< Node, int >::iterator itri = d_rcons_to_status[stn].find( t ); + if( itri!=d_rcons_to_status[stn].end() ){ + status = itri->second; + //Trace("csi-rcons-debug") << "-> (cached) " << status << " for " << d_rcons_to_id[stn][t] << std::endl; + return d_rcons_to_id[stn][t]; + }else{ + status = 1; + // register the type + registerType(stn); + int id = allocate( t, stn ); + d_rcons_to_status[stn][t] = -1; + TypeNode tn = t.getType(); + Assert( stn.isDatatype() ); + const Datatype& dt = ((DatatypeType)(stn).toType()).getDatatype(); + Assert( dt.isSygus() ); + Trace("csi-rcons-debug") << "Check reconstruct " << t << ", sygus type " << dt.getName() << ", kind " << t.getKind() << ", id : " << id << std::endl; + int carg = -1; + int karg = -1; + // first, do standard minimizations + Node min_t = d_qe->getTermDatabaseSygus()->minimizeBuiltinTerm( t ); + Trace("csi-rcons-debug") << "Minimized term is : " << min_t << std::endl; + //check if op is in syntax sort + carg = d_qe->getTermDatabaseSygus()->getOpConsNum( stn, min_t ); + if( carg!=-1 ){ + Trace("csi-rcons-debug") << " Type has operator." << std::endl; + d_reconstruct[id] = NodeManager::currentNM()->mkNode( APPLY_CONSTRUCTOR, Node::fromExpr( dt[carg].getConstructor() ) ); + status = 0; + }else{ + //check if kind is in syntax sort + karg = d_qe->getTermDatabaseSygus()->getKindConsNum( stn, min_t.getKind() ); + if( karg!=-1 ){ + //collect the children of min_t + std::vector< Node > tchildren; + if( min_t.getNumChildren()>dt[karg].getNumArgs() && quantifiers::TermUtil::isAssoc( min_t.getKind() ) && dt[karg].getNumArgs()==2 ){ + tchildren.push_back( min_t[0] ); + std::vector< Node > rem_children; + for( unsigned i=1; i<min_t.getNumChildren(); i++ ){ + rem_children.push_back( min_t[i] ); + } + Node t2 = NodeManager::currentNM()->mkNode( min_t.getKind(), rem_children ); + tchildren.push_back( t2 ); + Trace("csi-rcons-debug") << "...split n-ary to binary " << min_t[0] << " " << t2 << "." << std::endl; + }else{ + for( unsigned i=0; i<min_t.getNumChildren(); i++ ){ + tchildren.push_back( min_t[i] ); + } + } + //recurse on the children + if( tchildren.size()==dt[karg].getNumArgs() ){ + Trace("csi-rcons-debug") << "Type for " << id << " has kind " << min_t.getKind() << ", recurse." << std::endl; + status = 0; + Node cons = Node::fromExpr( dt[karg].getConstructor() ); + if( !collectReconstructNodes( id, tchildren, dt[karg], d_reconstruct_op[id][cons], status ) ){ + Trace("csi-rcons-debug") << "...failure for " << id << " " << dt[karg].getName() << std::endl; + d_reconstruct_op[id].erase( cons ); + status = 1; + } + }else{ + Trace("csi-rcons-debug") << "Type for " << id << " has kind " << min_t.getKind() << ", but argument # mismatch." << std::endl; + } + } + if( status!=0 ){ + //try constant reconstruction + if( min_t.isConst() ){ + Trace("csi-rcons-debug") << "...try constant reconstruction." << std::endl; + Node min_t_c = builtinToSygusConst(min_t, stn); + if( !min_t_c.isNull() ){ + Trace("csi-rcons-debug") << " constant reconstruction success for " << id << ", result = " << min_t_c << std::endl; + d_reconstruct[id] = min_t_c; + status = 0; + } + } + if( status!=0 ){ + //try identity functions + for (unsigned ii : d_id_funcs[stn]) + { + Assert( dt[ii].getNumArgs()==1 ); + //try to directly reconstruct from single argument + std::vector< Node > tchildren; + tchildren.push_back( min_t ); + TypeNode stnc = TypeNode::fromType( ((SelectorType)dt[ii][0].getType()).getRangeType() ); + Trace("csi-rcons-debug") << "...try identity function " << dt[ii].getSygusOp() << ", child type is " << stnc << std::endl; + status = 0; + Node cons = Node::fromExpr( dt[ii].getConstructor() ); + if( !collectReconstructNodes( id, tchildren, dt[ii], d_reconstruct_op[id][cons], status ) ){ + d_reconstruct_op[id].erase( cons ); + status = 1; + }else{ + Trace("csi-rcons-debug") << " identity function success for " << id << std::endl; + break; + } + } + if( status!=0 ){ + //try other options, such as matching against other constructors + Trace("csi-rcons-debug") << "Try matching for " << id << "." << std::endl; + bool success; + int c_index = 0; + do{ + success = false; + int index_found; + std::vector< Node > args; + if (getMatch(min_t, stn, index_found, args, karg, c_index)) + { + success = true; + status = 0; + Node cons = Node::fromExpr( dt[index_found].getConstructor() ); + Trace("csi-rcons-debug") << "Try alternative for " << id << ", matching " << dt[index_found].getName() << " with children : " << std::endl; + for( unsigned i=0; i<args.size(); i++ ){ + Trace("csi-rcons-debug") << " " << args[i] << std::endl; + } + if( !collectReconstructNodes( id, args, dt[index_found], d_reconstruct_op[id][cons], status ) ){ + d_reconstruct_op[id].erase( cons ); + status = 1; + }else{ + c_index = index_found+1; + } + } + }while( success && status!=0 ); + + if( status!=0 ){ + // construct an equivalence class of terms that are equivalent to t + if( d_rep[id]==id ){ + Trace("csi-rcons-debug") << "Try rewriting for " << id << "." << std::endl; + //get equivalence class of term + std::vector< Node > equiv; + if( tn.isBoolean() ){ + Node curr = min_t; + Node new_t; + do{ + new_t = Node::null(); + if( curr.getKind()==EQUAL ){ + if( curr[0].getType().isInteger() || curr[0].getType().isReal() ){ + new_t = NodeManager::currentNM()->mkNode( AND, NodeManager::currentNM()->mkNode( LEQ, curr[0], curr[1] ), + NodeManager::currentNM()->mkNode( LEQ, curr[1], curr[0] ) ); + }else if( curr[0].getType().isBoolean() ){ + new_t = NodeManager::currentNM()->mkNode( OR, NodeManager::currentNM()->mkNode( AND, curr[0], curr[1] ), + NodeManager::currentNM()->mkNode( AND, curr[0].negate(), curr[1].negate() ) ); + }else{ + new_t = NodeManager::currentNM()->mkNode( NOT, NodeManager::currentNM()->mkNode( NOT, curr ) ); + } + }else if( curr.getKind()==ITE ){ + new_t = NodeManager::currentNM()->mkNode( OR, NodeManager::currentNM()->mkNode( AND, curr[0], curr[1] ), + NodeManager::currentNM()->mkNode( AND, curr[0].negate(), curr[2] ) ); + }else if( curr.getKind()==OR || curr.getKind()==AND ){ + new_t = TermUtil::simpleNegate( curr ).negate(); + }else if( curr.getKind()==NOT ){ + new_t = TermUtil::simpleNegate( curr[0] ); + }else{ + new_t = NodeManager::currentNM()->mkNode( NOT, NodeManager::currentNM()->mkNode( NOT, curr ) ); + } + if( !new_t.isNull() ){ + if( new_t!=min_t && std::find( equiv.begin(), equiv.end(), new_t )==equiv.end() ){ + curr = new_t; + equiv.push_back( new_t ); + }else{ + new_t = Node::null(); + } + } + }while( !new_t.isNull() ); + } + //get decompositions + for( unsigned i=0; i<dt.getNumConstructors(); i++ ){ + Kind k = d_qe->getTermDatabaseSygus()->getConsNumKind( stn, i ); + getEquivalentTerms( k, min_t, equiv ); + } + //assign ids to terms + Trace("csi-rcons-debug") << "Term " << id << " is equivalent to " << equiv.size() << " terms : " << std::endl; + std::vector< int > equiv_ids; + for( unsigned i=0; i<equiv.size(); i++ ){ + Trace("csi-rcons-debug") << " " << equiv[i] << std::endl; + if( d_rcons_to_id[stn].find( equiv[i] )==d_rcons_to_id[stn].end() ){ + int eq_id = allocate( equiv[i], stn ); + d_eqc.erase( eq_id ); + d_rep[eq_id] = id; + d_eqc[id].push_back( eq_id ); + equiv_ids.push_back( eq_id ); + }else{ + equiv_ids.push_back( -1 ); + } + } + // now, try each of them + for( unsigned i=0; i<equiv.size(); i++ ){ + if( equiv_ids[i]!=-1 ){ + collectReconstructNodes( equiv[i], stn, status ); + //if one succeeds + if( status==0 ){ + Node rsol = getReconstructedSolution( equiv_ids[i] ); + Assert( !rsol.isNull() ); + //set all members of the equivalence class that this is the reconstructed solution + setReconstructed( id, rsol ); + break; + } + } + } + }else{ + Trace("csi-rcons-debug") << "Do not try rewriting for " << id << ", rep = " << d_rep[id] << std::endl; + } + } + } + } + } + } + if( status!=0 ){ + Trace("csi-rcons-debug") << "-> *** reconstruction required for id " << id << std::endl; + }else{ + Trace("csi-rcons-debug") << "-> success for " << id << std::endl; + } + d_rcons_to_status[stn][t] = status; + return id; + } +} + +bool CegConjectureSingleInvSol::collectReconstructNodes( int pid, std::vector< Node >& ts, const DatatypeConstructor& dtc, std::vector< int >& ids, int& status ) { + Assert( dtc.getNumArgs()==ts.size() ); + for( unsigned i=0; i<ts.size(); i++ ){ + TypeNode cstn = d_qe->getTermDatabaseSygus()->getArgType( dtc, i ); + int cstatus; + int c_id = collectReconstructNodes( ts[i], cstn, cstatus ); + if( cstatus==-1 ){ + return false; + }else if( cstatus!=0 ){ + status = 1; + } + ids.push_back( c_id ); + } + for( unsigned i=0; i<ids.size(); i++ ){ + d_parents[ids[i]].push_back( pid ); + } + return true; +} + + /* + //flatten ITEs if necessary TODO : carry assignment or move this elsewhere + if( t.getKind()==ITE ){ + TypeNode cstn = tds->getArgType( dt[karg], 0 ); + tds->registerSygusType( cstn ); + Node prev_t; + while( !tds->hasKind( cstn, t[0].getKind() ) && t!=prev_t ){ + prev_t = t; + Node exp_c = tds->expandBuiltinTerm( t[0] ); + if( !exp_c.isNull() ){ + t = NodeManager::currentNM()->mkNode( ITE, exp_c, t[1], t[2] ); + Trace("csi-rcons-debug") << "Pre expand to " << t << std::endl; + } + t = flattenITEs( t, false ); + if( t!=prev_t ){ + Trace("csi-rcons-debug") << "Flatten ITE to " << t << std::endl; + std::map< Node, bool > sassign; + std::vector< Node > svars; + std::vector< Node > ssubs; + t = simplifySolutionNode( t, sassign, svars, ssubs, 0 ); + } + Assert( t.getKind()==ITE ); + } + } + */ + + +Node CegConjectureSingleInvSol::CegConjectureSingleInvSol::getReconstructedSolution( int id, bool mod_eq ) { + std::map< int, Node >::iterator it = d_reconstruct.find( id ); + if( it!=d_reconstruct.end() ){ + return it->second; + }else{ + if( std::find( d_tmp_fail.begin(), d_tmp_fail.end(), id )!=d_tmp_fail.end() ){ + return Node::null(); + }else{ + // try each child option + std::map< int, std::map< Node, std::vector< int > > >::iterator ito = d_reconstruct_op.find( id ); + if( ito!=d_reconstruct_op.end() ){ + for( std::map< Node, std::vector< int > >::iterator itt = ito->second.begin(); itt != ito->second.end(); ++itt ){ + std::vector< Node > children; + children.push_back( itt->first ); + bool success = true; + for( unsigned i=0; i<itt->second.size(); i++ ){ + Node nc = getReconstructedSolution( itt->second[i] ); + if( nc.isNull() ){ + success = false; + break; + }else{ + children.push_back( nc ); + } + } + if( success ){ + Node ret = NodeManager::currentNM()->mkNode( APPLY_CONSTRUCTOR, children ); + setReconstructed( id, ret ); + return ret; + } + } + } + // try terms in the equivalence class of this + if( mod_eq ){ + int rid = d_rep[id]; + for( unsigned i=0; i<d_eqc[rid].size(); i++ ){ + int tid = d_eqc[rid][i]; + if( tid!=id ){ + Node eret = getReconstructedSolution( tid, false ); + if( !eret.isNull() ){ + setReconstructed( id, eret ); + return eret; + } + } + } + } + d_tmp_fail.push_back( id ); + return Node::null(); + } + } +} + +int CegConjectureSingleInvSol::allocate( Node n, TypeNode stn ) { + std::map< Node, int >::iterator it = d_rcons_to_id[stn].find( n ); + if( it==d_rcons_to_id[stn].end() ){ + int ret = d_id_count; + if( Trace.isOn("csi-rcons-debug") ){ + const Datatype& dt = ((DatatypeType)(stn).toType()).getDatatype(); + Trace("csi-rcons-debug") << "id " << ret << " : " << n << " " << dt.getName() << std::endl; + } + d_id_node[d_id_count] = n; + d_id_type[d_id_count] = stn; + d_rep[d_id_count] = d_id_count; + d_eqc[d_id_count].push_back( d_id_count ); + d_rcons_to_id[stn][n] = d_id_count; + d_id_count++; + return ret; + }else{ + return it->second; + } +} + +bool CegConjectureSingleInvSol::getPathToRoot( int id ) { + if( id==d_root_id ){ + return true; + }else{ + std::map< int, Node >::iterator it = d_reconstruct.find( id ); + if( it!=d_reconstruct.end() ){ + return false; + }else{ + int rid = d_rep[id]; + for( unsigned j=0; j<d_parents[rid].size(); j++ ){ + if( getPathToRoot( d_parents[rid][j] ) ){ + return true; + } + } + return false; + } + } +} + +void CegConjectureSingleInvSol::setReconstructed( int id, Node n ) { + //set all equivalent to this as reconstructed + int rid = d_rep[id]; + for( unsigned i=0; i<d_eqc[rid].size(); i++ ){ + d_reconstruct[d_eqc[rid][i]] = n; + } +} + +void CegConjectureSingleInvSol::getEquivalentTerms( Kind k, Node n, std::vector< Node >& equiv ) { + if( k==AND || k==OR ){ + equiv.push_back( NodeManager::currentNM()->mkNode( k, n, n ) ); + equiv.push_back( NodeManager::currentNM()->mkNode( k, n, NodeManager::currentNM()->mkConst( k==AND ) ) ); + } + //multiplication for integers + //TODO for bitvectors + Kind mk = ( k==PLUS || k==MINUS ) ? MULT : UNDEFINED_KIND; + if( mk!=UNDEFINED_KIND ){ + if( n.getKind()==mk && n[0].isConst() && n[0].getType().isInteger() ){ + bool success = true; + for( unsigned i=0; i<2; i++ ){ + Node eq; + if( k==PLUS || k==MINUS ){ + Node oth = NodeManager::currentNM()->mkConst( Rational(i==0 ? 1000 : -1000) ); + eq = i==0 ? NodeManager::currentNM()->mkNode( LEQ, n[0], oth ) : NodeManager::currentNM()->mkNode( GEQ, n[0], oth ); + } + if( !eq.isNull() ){ + eq = Rewriter::rewrite( eq ); + if( eq!=d_qe->getTermUtil()->d_true ){ + success = false; + break; + } + } + } + if( success ){ + Node var = n[1]; + Node rem; + if( k==PLUS || k==MINUS ){ + int rem_coeff = (int)n[0].getConst<Rational>().getNumerator().getSignedInt(); + if( rem_coeff>0 && k==PLUS ){ + rem_coeff--; + }else if( rem_coeff<0 && k==MINUS ){ + rem_coeff++; + }else{ + success = false; + } + if( success ){ + rem = NodeManager::currentNM()->mkNode( MULT, NodeManager::currentNM()->mkConst( Rational(rem_coeff) ), var ); + rem = Rewriter::rewrite( rem ); + } + } + if( !rem.isNull() ){ + equiv.push_back( NodeManager::currentNM()->mkNode( k, rem, var ) ); + } + } + } + } + //negative constants + if( k==MINUS ){ + if( n.isConst() && n.getType().isInteger() && n.getConst<Rational>().getNumerator().strictlyNegative() ){ + Node nn = NodeManager::currentNM()->mkNode( UMINUS, n ); + nn = Rewriter::rewrite( nn ); + equiv.push_back( NodeManager::currentNM()->mkNode( MINUS, NodeManager::currentNM()->mkConst( Rational(0) ), nn ) ); + } + } + //inequalities + if( k==GEQ || k==LEQ || k==LT || k==GT || k==NOT ){ + Node atom = n.getKind()==NOT ? n[0] : n; + bool pol = n.getKind()!=NOT; + Kind ak = atom.getKind(); + if( ( ak==GEQ || ak==LEQ || ak==LT || ak==GT ) && ( pol || k!=NOT ) ){ + Node t1 = atom[0]; + Node t2 = atom[1]; + if( !pol ){ + ak = ak==GEQ ? LT : ( ak==LEQ ? GT : ( ak==LT ? GEQ : LEQ ) ); + } + if( k==NOT ){ + equiv.push_back( NodeManager::currentNM()->mkNode( ak==GEQ ? LT : ( ak==LEQ ? GT : ( ak==LT ? GEQ : LEQ ) ), t1, t2 ).negate() ); + }else if( k==ak ){ + equiv.push_back( NodeManager::currentNM()->mkNode( k, t1, t2 ) ); + }else if( (k==GEQ || k==LEQ)==(ak==GEQ || ak==LEQ) ){ + equiv.push_back( NodeManager::currentNM()->mkNode( k, t2, t1 ) ); + }else if( t1.getType().isInteger() && t2.getType().isInteger() ){ + if( (k==GEQ || k==GT)!=(ak==GEQ || ak==GT) ){ + Node ts = t1; + t1 = t2; + t2 = ts; + ak = ak==GEQ ? LEQ : ( ak==LEQ ? GEQ : ( ak==LT ? GT : LT ) ); + } + t2 = NodeManager::currentNM()->mkNode( PLUS, t2, NodeManager::currentNM()->mkConst( Rational( (ak==GT || ak==LEQ) ? 1 : -1 ) ) ); + t2 = Rewriter::rewrite( t2 ); + equiv.push_back( NodeManager::currentNM()->mkNode( k, t1, t2 ) ); + } + } + } + + //based on eqt cache + std::map< Node, Node >::iterator itet = d_eqt_rep.find( n ); + if( itet!=d_eqt_rep.end() ){ + Node rn = itet->second; + for( unsigned i=0; i<d_eqt_eqc[rn].size(); i++ ){ + if( d_eqt_eqc[rn][i]!=n && d_eqt_eqc[rn][i].getKind()==k ){ + if( std::find( equiv.begin(), equiv.end(), d_eqt_eqc[rn][i] )==equiv.end() ){ + equiv.push_back( d_eqt_eqc[rn][i] ); + } + } + } + } +} + +void CegConjectureSingleInvSol::registerEquivalentTerms( Node n ) { + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + registerEquivalentTerms( n[i] ); + } + Node rn = Rewriter::rewrite( n ); + if( rn!=n ){ + Trace("csi-equiv") << " eq terms : " << n << " " << rn << std::endl; + d_eqt_rep[n] = rn; + d_eqt_rep[rn] = rn; + if( std::find( d_eqt_eqc[rn].begin(), d_eqt_eqc[rn].end(), rn )==d_eqt_eqc[rn].end() ){ + d_eqt_eqc[rn].push_back( rn ); + } + if( std::find( d_eqt_eqc[rn].begin(), d_eqt_eqc[rn].end(), n )==d_eqt_eqc[rn].end() ){ + d_eqt_eqc[rn].push_back( n ); + } + } +} + +Node CegConjectureSingleInvSol::builtinToSygusConst(Node c, + TypeNode tn, + int rcons_depth) +{ + std::map<Node, Node>::iterator it = d_builtin_const_to_sygus[tn].find(c); + if (it != d_builtin_const_to_sygus[tn].end()) + { + return it->second; + } + TermDbSygus* tds = d_qe->getTermDatabaseSygus(); + NodeManager* nm = NodeManager::currentNM(); + Node sc; + d_builtin_const_to_sygus[tn][c] = sc; + Assert(c.isConst()); + Assert(tn.isDatatype()); + const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype(); + Trace("csi-rcons-debug") << "Try to reconstruct " << c << " in " + << dt.getName() << std::endl; + Assert(dt.isSygus()); + // if we are not interested in reconstructing constants, or the grammar allows + // them, return a proxy + if (!options::cegqiSingleInvReconstructConst() || dt.getSygusAllowConst()) + { + Node k = nm->mkSkolem("sy", tn, "sygus proxy"); + SygusPrintProxyAttribute spa; + k.setAttribute(spa, c); + sc = k; + } + else + { + int carg = tds->getOpConsNum(tn, c); + if (carg != -1) + { + sc = nm->mkNode(APPLY_CONSTRUCTOR, + Node::fromExpr(dt[carg].getConstructor())); + } + else + { + // identity functions + for (unsigned ii : d_id_funcs[tn]) + { + Assert(dt[ii].getNumArgs() == 1); + // try to directly reconstruct from single argument + TypeNode tnc = tds->getArgType(dt[ii], 0); + Trace("csi-rcons-debug") + << "Based on id function " << dt[ii].getSygusOp() + << ", try reconstructing " << c << " instead in " << tnc + << std::endl; + Node n = builtinToSygusConst(c, tnc, rcons_depth); + if (!n.isNull()) + { + sc = nm->mkNode( + APPLY_CONSTRUCTOR, Node::fromExpr(dt[ii].getConstructor()), n); + break; + } + } + if (sc.isNull()) + { + if (rcons_depth < 1000) + { + // accelerated, recursive reconstruction of constants + Kind pk = tds->getPlusKind(TypeNode::fromType(dt.getSygusType())); + if (pk != UNDEFINED_KIND) + { + int arg = tds->getKindConsNum(tn, pk); + if (arg != -1) + { + Kind ck = + tds->getComparisonKind(TypeNode::fromType(dt.getSygusType())); + Kind pkm = + tds->getPlusKind(TypeNode::fromType(dt.getSygusType()), true); + // get types + Assert(dt[arg].getNumArgs() == 2); + TypeNode tn1 = tds->getArgType(dt[arg], 0); + TypeNode tn2 = tds->getArgType(dt[arg], 1); + // initialize d_const_list for tn1 + registerType(tn1); + // iterate over all positive constants, largest to smallest + int start = d_const_list[tn1].size() - 1; + int end = d_const_list[tn1].size() - d_const_list_pos[tn1]; + for (int i = start; i >= end; --i) + { + Node c1 = d_const_list[tn1][i]; + // only consider if smaller than c, and + if (doCompare(c1, c, ck)) + { + Node c2 = nm->mkNode(pkm, c, c1); + c2 = Rewriter::rewrite(c2); + if (c2.isConst()) + { + // reconstruct constant on the other side + Node sc2 = builtinToSygusConst(c2, tn2, rcons_depth + 1); + if (!sc2.isNull()) + { + Node sc1 = builtinToSygusConst(c1, tn1, rcons_depth); + Assert(!sc1.isNull()); + sc = nm->mkNode(APPLY_CONSTRUCTOR, + Node::fromExpr(dt[arg].getConstructor()), + sc1, + sc2); + break; + } + } + } + } + } + } + } + } + } + } + d_builtin_const_to_sygus[tn][c] = sc; + return sc; +} + +struct sortConstants +{ + Kind d_comp_kind; + bool operator()(Node i, Node j) + { + return i != j && doCompare(i, j, d_comp_kind); + } +}; + +void CegConjectureSingleInvSol::registerType(TypeNode tn) +{ + if (d_const_list_pos.find(tn) != d_const_list_pos.end()) + { + return; + } + d_const_list_pos[tn] = 0; + Assert(tn.isDatatype()); + + TermDbSygus* tds = d_qe->getTermDatabaseSygus(); + // ensure it is registered + tds->registerSygusType(tn); + const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype(); + TypeNode btn = TypeNode::fromType(dt.getSygusType()); + // for constant reconstruction + Kind ck = tds->getComparisonKind(btn); + Node z = d_qe->getTermUtil()->getTypeValue(btn, 0); + + // iterate over constructors + for (unsigned i = 0, ncons = dt.getNumConstructors(); i < ncons; i++) + { + Node n = Node::fromExpr(dt[i].getSygusOp()); + if (n.getKind() != kind::BUILTIN && n.isConst()) + { + d_const_list[tn].push_back(n); + if (ck != UNDEFINED_KIND && doCompare(z, n, ck)) + { + d_const_list_pos[tn]++; + } + } + if (dt[i].isSygusIdFunc()) + { + d_id_funcs[tn].push_back(i); + } + } + // sort the constant list + if (!d_const_list[tn].empty()) + { + if (ck != UNDEFINED_KIND) + { + sortConstants sc; + sc.d_comp_kind = ck; + std::sort(d_const_list[tn].begin(), d_const_list[tn].end(), sc); + } + Trace("csi-rcons") << "Type has " << d_const_list[tn].size() + << " constants..." << std::endl + << " "; + for (unsigned i = 0; i < d_const_list[tn].size(); i++) + { + Trace("csi-rcons") << d_const_list[tn][i] << " "; + } + Trace("csi-rcons") << std::endl; + Trace("csi-rcons") << "Of these, " << d_const_list_pos[tn] + << " are marked as positive." << std::endl; + } +} + +bool CegConjectureSingleInvSol::getMatch(Node p, + Node n, + std::map<int, Node>& s, + std::vector<int>& new_s) +{ + TermDbSygus* tds = d_qe->getTermDatabaseSygus(); + if (tds->isFreeVar(p)) + { + unsigned vnum = tds->getVarNum(p); + Node prev = s[vnum]; + s[vnum] = n; + if (prev.isNull()) + { + new_s.push_back(vnum); + } + return prev.isNull() || prev == n; + } + if (n.getNumChildren() == 0) + { + return p == n; + } + if (n.getKind() == p.getKind() && n.getNumChildren() == p.getNumChildren()) + { + // try both ways? + unsigned rmax = + TermUtil::isComm(n.getKind()) && n.getNumChildren() == 2 ? 2 : 1; + std::vector<int> new_tmp; + for (unsigned r = 0; r < rmax; r++) + { + bool success = true; + for (unsigned i = 0, size = n.getNumChildren(); i < size; i++) + { + int io = r == 0 ? i : (i == 0 ? 1 : 0); + if (!getMatch(p[i], n[io], s, new_tmp)) + { + success = false; + for (unsigned j = 0; j < new_tmp.size(); j++) + { + s.erase(new_tmp[j]); + } + new_tmp.clear(); + break; + } + } + if (success) + { + new_s.insert(new_s.end(), new_tmp.begin(), new_tmp.end()); + return true; + } + } + } + return false; +} + +bool CegConjectureSingleInvSol::getMatch(Node t, + TypeNode st, + int& index_found, + std::vector<Node>& args, + int index_exc, + int index_start) +{ + Assert(st.isDatatype()); + const Datatype& dt = static_cast<DatatypeType>(st.toType()).getDatatype(); + Assert(dt.isSygus()); + std::map<Kind, std::vector<Node> > kgens; + std::vector<Node> gens; + for (unsigned i = index_start, ncons = dt.getNumConstructors(); i < ncons; + i++) + { + if ((int)i != index_exc) + { + Node g = getGenericBase(st, dt, i); + gens.push_back(g); + kgens[g.getKind()].push_back(g); + Trace("csi-sol-debug") << "Check generic base : " << g << " from " + << dt[i].getName() << std::endl; + if (g.getKind() == t.getKind()) + { + Trace("csi-sol-debug") << "Possible match ? " << g << " " << t + << " for " << dt[i].getName() << std::endl; + std::map<int, Node> sigma; + std::vector<int> new_s; + if (getMatch(g, t, sigma, new_s)) + { + // we found an exact match + bool msuccess = true; + for (unsigned j = 0, nargs = dt[i].getNumArgs(); j < nargs; j++) + { + if (sigma[j].isNull()) + { + msuccess = false; + break; + } + else + { + args.push_back(sigma[j]); + } + } + if (msuccess) + { + index_found = i; + return true; + } + } + } + } + } + return false; +} + +Node CegConjectureSingleInvSol::getGenericBase(TypeNode tn, + const Datatype& dt, + int c) +{ + std::map<int, Node>::iterator it = d_generic_base[tn].find(c); + if (it != d_generic_base[tn].end()) + { + return it->second; + } + TermDbSygus* tds = d_qe->getTermDatabaseSygus(); + Assert(tds->isRegistered(tn)); + std::map<TypeNode, int> var_count; + std::map<int, Node> pre; + Node g = tds->mkGeneric(dt, c, var_count, pre); + Trace("csi-sol-debug") << "Generic is " << g << std::endl; + Node gr = Rewriter::rewrite(g); + Trace("csi-sol-debug") << "Generic rewritten is " << gr << std::endl; + d_generic_base[tn][c] = gr; + return gr; +} +} +} +} diff --git a/src/theory/quantifiers/sygus/ce_guided_single_inv_sol.h b/src/theory/quantifiers/sygus/ce_guided_single_inv_sol.h new file mode 100644 index 000000000..7043e1ecf --- /dev/null +++ b/src/theory/quantifiers/sygus/ce_guided_single_inv_sol.h @@ -0,0 +1,191 @@ +/********************* */ +/*! \file ce_guided_single_inv_sol.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Paul Meng + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief utility for reconstructing solutions for single invocation synthesis conjectures + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_SINGLE_INV_SOL_H +#define __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_SINGLE_INV_SOL_H + +#include "context/cdhashmap.h" +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + + +class CegConjectureSingleInv; + +/** CegConjectureSingleInvSol + * + * This function implements Figure 5 of "Counterexample-Guided Quantifier + * Instantiation for Synthesis in SMT", Reynolds et al CAV 2015. + * + */ +class CegConjectureSingleInvSol +{ + friend class CegConjectureSingleInv; +private: + QuantifiersEngine * d_qe; + std::vector< Node > d_varList; + std::map< Node, int > d_dterm_size; + std::map< Node, int > d_dterm_ite_size; +//solution simplification +private: + bool debugSolution( Node sol ); + void debugTermSize( Node sol, int& t_size, int& num_ite ); + Node pullITEs( Node n ); + bool pullITECondition( Node root, Node n, std::vector< Node >& conj, Node& t, Node& rem, int depth ); + Node flattenITEs( Node n, bool rec = true ); + bool getAssign( bool pol, Node n, std::map< Node, bool >& assign, std::vector< Node >& new_assign, + std::vector< Node >& vars, std::vector< Node >& new_vars, std::vector< Node >& new_subs ); + bool getAssignEquality( Node eq, std::vector< Node >& vars, std::vector< Node >& new_vars, std::vector< Node >& new_subs ); + Node simplifySolutionNode( Node sol, TypeNode stn, std::map< Node, bool >& assign, + std::vector< Node >& vars, std::vector< Node >& subs, int status ); + + public: + CegConjectureSingleInvSol(QuantifiersEngine* qe); + /** simplify solution + * + * Returns the simplified version of node sol whose syntax is restricted by + * the grammar corresponding to sygus datatype stn. + */ + Node simplifySolution( Node sol, TypeNode stn ); + /** reconstruct solution + * + * Returns (if possible) a node that is equivalent to sol those syntax + * matches the grammar corresponding to sygus datatype stn. + * The value reconstructed is set to 1 if we successfully return a node, + * otherwise it is set to -1. + */ + Node reconstructSolution(Node sol, TypeNode stn, int& reconstructed); + /** preregister conjecture + * + * q : the synthesis conjecture this class is for. + * This is used as a heuristic to find terms in the original conjecture which + * may be helpful for using during reconstruction. + */ + void preregisterConjecture(Node q); + + private: + int d_id_count; + int d_root_id; + std::map< int, Node > d_id_node; + std::map< int, TypeNode > d_id_type; + std::map< TypeNode, std::map< Node, int > > d_rcons_to_id; + std::map< TypeNode, std::map< Node, int > > d_rcons_to_status; + + std::map< int, std::map< Node, std::vector< int > > > d_reconstruct_op; + std::map< int, Node > d_reconstruct; + std::map< int, std::vector< int > > d_parents; + + std::map< int, std::vector< int > > d_eqc; + std::map< int, int > d_rep; + + //equivalent terms + std::map< Node, Node > d_eqt_rep; + std::map< Node, std::vector< Node > > d_eqt_eqc; + + //cache when reconstructing solutions + std::vector< int > d_tmp_fail; + // get reconstructed solution + Node getReconstructedSolution( int id, bool mod_eq = true ); + + // allocate node with type + int allocate( Node n, TypeNode stn ); + // term t with sygus type st, returns inducted templated form of t + int collectReconstructNodes( Node t, TypeNode stn, int& status ); + bool collectReconstructNodes( int pid, std::vector< Node >& ts, const DatatypeConstructor& dtc, std::vector< int >& ids, int& status ); + bool getPathToRoot( int id ); + void setReconstructed( int id, Node n ); + //get equivalent terms to n with top symbol k + void getEquivalentTerms( Kind k, Node n, std::vector< Node >& equiv ); + //register equivalent terms + void registerEquivalentTerms( Node n ); + /** builtin to sygus const + * + * Returns a sygus term of type tn that encodes the builtin constant c. + * If the sygus datatype tn allows any constant, this may return a variable + * with the attribute SygusPrintProxyAttribute that associates it with c. + * + * rcons_depth limits the number of recursive calls when doing accelerated + * constant reconstruction (currently limited to 1000). Notice this is hacky: + * depending upon order of calls, constant rcons may succeed, e.g. 1001, 999 + * vs. 999, 1001. + */ + Node builtinToSygusConst(Node c, TypeNode tn, int rcons_depth = 0); + /** cache for the above function */ + std::map<TypeNode, std::map<Node, Node> > d_builtin_const_to_sygus; + /** sorted list of constants, per type */ + std::map<TypeNode, std::vector<Node> > d_const_list; + /** number of positive constants, per type */ + std::map<TypeNode, unsigned> d_const_list_pos; + /** list of constructor indices whose operators are identity functions */ + std::map<TypeNode, std::vector<int> > d_id_funcs; + /** initialize the above information for sygus type tn */ + void registerType(TypeNode tn); + /** get generic base + * + * This returns the builtin term that is the analog of an application of the + * c^th constructor of dt to fresh variables. + */ + Node getGenericBase(TypeNode tn, const Datatype& dt, int c); + /** cache for the above function */ + std::map<TypeNode, std::map<int, Node> > d_generic_base; + /** get match + * + * This function attempts to find a substitution for which p = n. If + * successful, this function returns a substitution in the form of s/new_s, + * where: + * s : substitution, where the domain are indices of terms in the sygus + * term database, and + * new_s : the members that were added to s on this call. + * Otherwise, this function returns false and s and new_s are unmodified. + */ + bool getMatch(Node p, + Node n, + std::map<int, Node>& s, + std::vector<int>& new_s); + /** get match + * + * This function attempts to find a builtin term that is analog to a value + * of the sygus datatype st that is equivalent to n. If this function returns + * true, then it has found such a term. Then we set: + * index_found : updated to the constructor index of the sygus term whose + * analog to equivalent to n. + * args : builtin terms corresponding to the match, in order. + * Otherwise, this function returns false and index_found and args are + * unmodified. + * For example, for grammar: + * A -> 0 | 1 | x | +( A, A ) + * Given input ( 5 + (x+1) ) and A we would return true, where: + * index_found is set to 3 and args is set to { 5, x+1 }. + * + * index_exc : (if applicable) exclude a constructor index of st + * index_start : start index of constructors of st to try + */ + bool getMatch(Node n, + TypeNode st, + int& index_found, + std::vector<Node>& args, + int index_exc = -1, + int index_start = 0); +}; + + +} +} +} + +#endif diff --git a/src/theory/quantifiers/sygus/sygus_explain.cpp b/src/theory/quantifiers/sygus/sygus_explain.cpp new file mode 100644 index 000000000..aafaa07e1 --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_explain.cpp @@ -0,0 +1,301 @@ +/********************* */ +/*! \file sygus_explain.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief Implementation of techniques for sygus explanations + **/ + +#include "theory/quantifiers/sygus/sygus_explain.h" + +#include "theory/datatypes/datatypes_rewriter.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" + +using namespace CVC4::kind; +using namespace std; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +void TermRecBuild::addTerm(Node n) +{ + d_term.push_back(n); + std::vector<Node> currc; + d_kind.push_back(n.getKind()); + if (n.getMetaKind() == kind::metakind::PARAMETERIZED) + { + currc.push_back(n.getOperator()); + d_has_op.push_back(true); + } + else + { + d_has_op.push_back(false); + } + for (unsigned i = 0; i < n.getNumChildren(); i++) + { + currc.push_back(n[i]); + } + d_children.push_back(currc); +} + +void TermRecBuild::init(Node n) +{ + Assert(d_term.empty()); + addTerm(n); +} + +void TermRecBuild::push(unsigned p) +{ + Assert(!d_term.empty()); + unsigned curr = d_term.size() - 1; + Assert(d_pos.size() == curr); + Assert(d_pos.size() + 1 == d_children.size()); + Assert(p < d_term[curr].getNumChildren()); + addTerm(d_term[curr][p]); + d_pos.push_back(p); +} + +void TermRecBuild::pop() +{ + Assert(!d_pos.empty()); + d_pos.pop_back(); + d_kind.pop_back(); + d_has_op.pop_back(); + d_children.pop_back(); + d_term.pop_back(); +} + +void TermRecBuild::replaceChild(unsigned i, Node r) +{ + Assert(!d_term.empty()); + unsigned curr = d_term.size() - 1; + unsigned o = d_has_op[curr] ? 1 : 0; + d_children[curr][i + o] = r; +} + +Node TermRecBuild::getChild(unsigned i) +{ + unsigned curr = d_term.size() - 1; + unsigned o = d_has_op[curr] ? 1 : 0; + return d_children[curr][i + o]; +} + +Node TermRecBuild::build(unsigned d) +{ + Assert(d_pos.size() + 1 == d_term.size()); + Assert(d < d_term.size()); + int p = d < d_pos.size() ? d_pos[d] : -2; + std::vector<Node> children; + unsigned o = d_has_op[d] ? 1 : 0; + for (unsigned i = 0; i < d_children[d].size(); i++) + { + Node nc; + if (p + o == i) + { + nc = build(d + 1); + } + else + { + nc = d_children[d][i]; + } + children.push_back(nc); + } + return NodeManager::currentNM()->mkNode(d_kind[d], children); +} + +void SygusExplain::getExplanationForConstantEquality(Node n, + Node vn, + std::vector<Node>& exp) +{ + std::map<unsigned, bool> cexc; + getExplanationForConstantEquality(n, vn, exp, cexc); +} + +void SygusExplain::getExplanationForConstantEquality( + Node n, Node vn, std::vector<Node>& exp, std::map<unsigned, bool>& cexc) +{ + Assert(vn.getKind() == kind::APPLY_CONSTRUCTOR); + Assert(n.getType() == vn.getType()); + TypeNode tn = n.getType(); + Assert(tn.isDatatype()); + const Datatype& dt = ((DatatypeType)tn.toType()).getDatatype(); + int i = Datatype::indexOf(vn.getOperator().toExpr()); + Node tst = datatypes::DatatypesRewriter::mkTester(n, i, dt); + exp.push_back(tst); + for (unsigned j = 0; j < vn.getNumChildren(); j++) + { + if (cexc.find(j) == cexc.end()) + { + Node sel = NodeManager::currentNM()->mkNode( + kind::APPLY_SELECTOR_TOTAL, + Node::fromExpr(dt[i].getSelectorInternal(tn.toType(), j)), + n); + getExplanationForConstantEquality(sel, vn[j], exp); + } + } +} + +Node SygusExplain::getExplanationForConstantEquality(Node n, Node vn) +{ + std::map<unsigned, bool> cexc; + return getExplanationForConstantEquality(n, vn, cexc); +} + +Node SygusExplain::getExplanationForConstantEquality( + Node n, Node vn, std::map<unsigned, bool>& cexc) +{ + std::vector<Node> exp; + getExplanationForConstantEquality(n, vn, exp, cexc); + Assert(!exp.empty()); + return exp.size() == 1 ? exp[0] + : NodeManager::currentNM()->mkNode(kind::AND, exp); +} + +// we have ( n = vn => eval( n ) = bvr ) ^ vn != vnr , returns exp such that exp +// => ( eval( n ) = bvr ^ vn != vnr ) +void SygusExplain::getExplanationFor(TermRecBuild& trb, + Node n, + Node vn, + std::vector<Node>& exp, + std::map<TypeNode, int>& var_count, + SygusInvarianceTest& et, + Node vnr, + Node& vnr_exp, + int& sz) +{ + Assert(vnr.isNull() || vn != vnr); + Assert(vn.getKind() == APPLY_CONSTRUCTOR); + Assert(vnr.isNull() || vnr.getKind() == APPLY_CONSTRUCTOR); + Assert(n.getType() == vn.getType()); + TypeNode ntn = n.getType(); + std::map<unsigned, bool> cexc; + // for each child, + // check whether replacing that child by a fresh variable + // also satisfies the invariance test. + for (unsigned i = 0; i < vn.getNumChildren(); i++) + { + TypeNode xtn = vn[i].getType(); + Node x = d_tdb->getFreeVarInc(xtn, var_count); + trb.replaceChild(i, x); + Node nvn = trb.build(); + Assert(nvn.getKind() == kind::APPLY_CONSTRUCTOR); + if (et.is_invariant(d_tdb, nvn, x)) + { + cexc[i] = true; + // we are tracking term size if positive + if (sz >= 0) + { + int s = d_tdb->getSygusTermSize(vn[i]); + sz = sz - s; + } + } + else + { + trb.replaceChild(i, vn[i]); + } + } + const Datatype& dt = ((DatatypeType)ntn.toType()).getDatatype(); + int cindex = Datatype::indexOf(vn.getOperator().toExpr()); + Assert(cindex >= 0 && cindex < (int)dt.getNumConstructors()); + Node tst = datatypes::DatatypesRewriter::mkTester(n, cindex, dt); + exp.push_back(tst); + // if the operator of vn is different than vnr, then disunification obligation + // is met + if (!vnr.isNull()) + { + if (vnr.getOperator() != vn.getOperator()) + { + vnr = Node::null(); + vnr_exp = NodeManager::currentNM()->mkConst(true); + } + } + for (unsigned i = 0; i < vn.getNumChildren(); i++) + { + Node sel = NodeManager::currentNM()->mkNode( + kind::APPLY_SELECTOR_TOTAL, + Node::fromExpr(dt[cindex].getSelectorInternal(ntn.toType(), i)), + n); + Node vnr_c = vnr.isNull() ? vnr : (vn[i] == vnr[i] ? Node::null() : vnr[i]); + if (cexc.find(i) == cexc.end()) + { + trb.push(i); + Node vnr_exp_c; + getExplanationFor( + trb, sel, vn[i], exp, var_count, et, vnr_c, vnr_exp_c, sz); + trb.pop(); + if (!vnr_c.isNull()) + { + Assert(!vnr_exp_c.isNull()); + if (vnr_exp_c.isConst() || vnr_exp.isNull()) + { + // recursively satisfied the disunification obligation + if (vnr_exp_c.isConst()) + { + // was successful, don't consider further + vnr = Node::null(); + } + vnr_exp = vnr_exp_c; + } + } + } + else + { + // if excluded, we may need to add the explanation for this + if (vnr_exp.isNull() && !vnr_c.isNull()) + { + vnr_exp = getExplanationForConstantEquality(sel, vnr[i]); + } + } + } +} + +void SygusExplain::getExplanationFor(Node n, + Node vn, + std::vector<Node>& exp, + SygusInvarianceTest& et, + Node vnr, + unsigned& sz) +{ + // naive : + // return getExplanationForConstantEquality( n, vn, exp ); + + // set up the recursion object + std::map<TypeNode, int> var_count; + TermRecBuild trb; + trb.init(vn); + Node vnr_exp; + int sz_use = sz; + getExplanationFor(trb, n, vn, exp, var_count, et, vnr, vnr_exp, sz_use); + Assert(sz_use >= 0); + sz = sz_use; + Assert(vnr.isNull() || !vnr_exp.isNull()); + if (!vnr_exp.isNull() && !vnr_exp.isConst()) + { + exp.push_back(vnr_exp.negate()); + } +} + +void SygusExplain::getExplanationFor(Node n, + Node vn, + std::vector<Node>& exp, + SygusInvarianceTest& et) +{ + int sz = -1; + std::map<TypeNode, int> var_count; + TermRecBuild trb; + trb.init(vn); + Node vnr; + Node vnr_exp; + getExplanationFor(trb, n, vn, exp, var_count, et, vnr, vnr_exp, sz); +} + +} /* CVC4::theory::quantifiers namespace */ +} /* CVC4::theory namespace */ +} /* CVC4 namespace */ diff --git a/src/theory/quantifiers/sygus/sygus_explain.h b/src/theory/quantifiers/sygus/sygus_explain.h new file mode 100644 index 000000000..ad26f29e4 --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_explain.h @@ -0,0 +1,222 @@ +/********************* */ +/*! \file sygus_explain.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief sygus explanations + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__SYGUS_EXPLAIN_H +#define __CVC4__THEORY__QUANTIFIERS__SYGUS_EXPLAIN_H + +#include <vector> + +#include "expr/node.h" +#include "theory/quantifiers/sygus/sygus_invariance.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +/** Recursive term builder + * + * This is a utility used to generate variants + * of a term n, where subterms of n can be replaced + * by others via calls to replaceChild(...). + * + * This class maintains a "context", which indicates + * a position in term n. Below, we call the subterm of + * the initial term n at this position the "active term". + * + */ +class TermRecBuild +{ + public: + TermRecBuild() {} + /** set the initial term to n + * + * The context initially empty, that is, + * the active term is initially n. + */ + void init(Node n); + + /** push the context + * + * This updates the context so that the + * active term is updated to curr[p], where + * curr is the previously active term. + */ + void push(unsigned p); + + /** pop the context */ + void pop(); + /** indicates that the i^th child of the active + * term should be replaced by r in calls to build(). + */ + void replaceChild(unsigned i, Node r); + /** get the i^th child of the active term */ + Node getChild(unsigned i); + /** build the (modified) version of the term + * we intialized via the call to init(). + */ + Node build(unsigned p = 0); + + private: + /** stack of active terms */ + std::vector<Node> d_term; + /** stack of children of active terms + * Notice that these may be modified with calls to replaceChild(...). + */ + std::vector<std::vector<Node> > d_children; + /** stack the kind of active terms */ + std::vector<Kind> d_kind; + /** stack of whether the active terms had an operator */ + std::vector<bool> d_has_op; + /** stack of positions that were pushed via calls to push(...) */ + std::vector<unsigned> d_pos; + /** add term to the context stack */ + void addTerm(Node n); +}; + +/*The SygusExplain utility + * + * This class is used to produce explanations for refinement lemmas + * in the counterexample-guided inductive synthesis (CEGIS) loop. + * + * When given an invariance test T traverses the AST of a given term, + * uses TermRecBuild to replace various subterms by fresh variables and + * recheck whether the invariant, as specified by T still holds. + * If it does, then we may exclude the explanation for that subterm. + * + * For example, say we have that the current value of + * (datatype) sygus term n is: + * (if (gt x 0) 0 0) + * where if, gt, x, 0 are datatype constructors. + * The explanation returned by getExplanationForConstantEquality + * below for n and the above term is: + * { ((_ is if) n), ((_ is geq) n.0), + * ((_ is x) n.0.0), ((_ is 0) n.0.1), + * ((_ is 0) n.1), ((_ is 0) n.2) } + * + * This class can also return more precise + * explanations based on a property that holds for + * variants of n. For instance, + * say we find that n's builtin analog rewrites to 0: + * ite( x>0, 0, 0 ) ----> 0 + * and we would like to find the minimal explanation for + * why the builtin analog of n rewrites to 0. + * We use the invariance test EquivSygusInvarianceTest + * (see sygus_invariance.h) for doing this. + * Using the SygusExplain::getExplanationFor method below, + * this will invoke the invariant test to check, e.g. + * ite( x>0, 0, y1 ) ----> 0 ? fail + * ite( x>0, y2, 0 ) ----> 0 ? fail + * ite( y3, 0, 0 ) ----> 0 ? success + * where y1, y2, y3 are fresh variables. + * Hence the explanation for the condition x>0 is irrelevant. + * This gives us the explanation: + * { ((_ is if) n), ((_ is 0) n.1), ((_ is 0) n.2) } + * indicating that all terms of the form: + * (if _ 0 0) have a builtin equivalent that rewrites to 0. + * + * For details, see Reynolds et al SYNT 2017. + * + * Below, we let [[exp]]_n denote the term induced by + * the explanation exp for n. + * For example: + * exp = { ((_ is plus) n), ((_ is y) n.1) } + * is such that: + * [[exp]]_n = (plus w y) + * where w is a fresh variable. + */ +class SygusExplain +{ + public: + SygusExplain(TermDbSygus* tdb) : d_tdb(tdb) {} + ~SygusExplain() {} + /** get explanation for constant equality + * + * This function constructs an explanation, stored in exp, such that: + * - All formulas in exp are of the form ((_ is C) ns), where ns + * is a chain of selectors applied to n, and + * - exp => ( n = vn ) + */ + void getExplanationForConstantEquality(Node n, + Node vn, + std::vector<Node>& exp); + /** returns the conjunction of exp computed in the above function */ + Node getExplanationForConstantEquality(Node n, Node vn); + + /** get explanation for constant equality + * This is identical to the above function except that we + * take an additional argument cexc, which says which + * children of vn should be excluded from the explanation. + * + * For example, if vn = plus( plus( x, x ), y ) and cexc is { 0 -> true }, + * then the following is appended to exp : + * { ((_ is plus) n), ((_ is y) n.1) } + * where notice that the 0^th argument of vn is excluded. + */ + void getExplanationForConstantEquality(Node n, + Node vn, + std::vector<Node>& exp, + std::map<unsigned, bool>& cexc); + /** returns the conjunction of exp computed in the above function */ + Node getExplanationForConstantEquality(Node n, + Node vn, + std::map<unsigned, bool>& cexc); + + /** get explanation for + * + * This function constructs an explanation, stored in exp, such that: + * - All formulas in exp are of the form ((_ is C) ns), where ns + * is a chain of selectors applied to n, and + * - The test et holds for [[exp]]_n, and + * - (if applicable) exp => ( n != vnr ). + * + * This function updates sz to be the term size of [[exp]]_n. + */ + void getExplanationFor(Node n, + Node vn, + std::vector<Node>& exp, + SygusInvarianceTest& et, + Node vnr, + unsigned& sz); + void getExplanationFor(Node n, + Node vn, + std::vector<Node>& exp, + SygusInvarianceTest& et); + + private: + /** sygus term database associated with this utility */ + TermDbSygus* d_tdb; + /** Helper function for getExplanationFor + * var_count is the number of free variables we have introduced, + * per type, for the purposes of generalizing subterms of n. + * vnr_exp stores the explanation, if one exists, for + * n != vnr. It is only non-null if vnr is non-null. + */ + void getExplanationFor(TermRecBuild& trb, + Node n, + Node vn, + std::vector<Node>& exp, + std::map<TypeNode, int>& var_count, + SygusInvarianceTest& et, + Node vnr, + Node& vnr_exp, + int& sz); +}; + +} /* CVC4::theory::quantifiers namespace */ +} /* CVC4::theory namespace */ +} /* CVC4 namespace */ + +#endif /* __CVC4__THEORY__QUANTIFIERS__SYGUS_EXPLAIN_H */ diff --git a/src/theory/quantifiers/sygus/sygus_grammar_cons.cpp b/src/theory/quantifiers/sygus/sygus_grammar_cons.cpp new file mode 100644 index 000000000..1ca774c5d --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_grammar_cons.cpp @@ -0,0 +1,693 @@ +/********************* */ +/*! \file sygus_grammar_cons.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief implementation of class for constructing inductive datatypes that correspond to + ** grammars that encode syntactic restrictions for SyGuS. + **/ +#include "theory/quantifiers/sygus/sygus_grammar_cons.h" + +#include <stack> + +#include "expr/datatype.h" +#include "options/quantifiers_options.h" +#include "theory/quantifiers/sygus/ce_guided_conjecture.h" +#include "theory/quantifiers/sygus/sygus_process_conj.h" +#include "theory/quantifiers/sygus/sygus_grammar_norm.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_util.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +CegGrammarConstructor::CegGrammarConstructor(QuantifiersEngine* qe, + CegConjecture* p) + : d_qe(qe), d_parent(p), d_is_syntax_restricted(false), d_has_ite(true) +{ +} + +void CegGrammarConstructor::collectTerms( Node n, std::map< TypeNode, std::vector< Node > >& consts ){ + std::unordered_map<TNode, bool, TNodeHashFunction> visited; + std::unordered_map<TNode, bool, TNodeHashFunction>::iterator it; + std::stack<TNode> visit; + TNode cur; + visit.push(n); + do { + cur = visit.top(); + visit.pop(); + it = visited.find(cur); + if (it == visited.end()) { + visited[cur] = true; + // is this a constant? + if( cur.isConst() ){ + TypeNode tn = cur.getType(); + Node c = cur; + if( tn.isReal() ){ + c = NodeManager::currentNM()->mkConst( c.getConst<Rational>().abs() ); + } + if( std::find( consts[tn].begin(), consts[tn].end(), c )==consts[tn].end() ){ + Trace("cegqi-debug") << "...consider const : " << c << std::endl; + consts[tn].push_back( c ); + } + } + // recurse + for (unsigned i = 0; i < cur.getNumChildren(); i++) { + visit.push(cur[i]); + } + } + } while (!visit.empty()); +} + + + +Node CegGrammarConstructor::process( Node q, std::map< Node, Node >& templates, std::map< Node, Node >& templates_arg ) { + // convert to deep embedding and finalize single invocation here + // now, construct the grammar + Trace("cegqi") << "CegConjecture : convert to deep embedding..." << std::endl; + std::map< TypeNode, std::vector< Node > > extra_cons; + if( options::sygusAddConstGrammar() ){ + Trace("cegqi") << "CegConjecture : collect constants..." << std::endl; + collectTerms( q[1], extra_cons ); + } + + std::vector< Node > qchildren; + std::map< Node, Node > synth_fun_vars; + std::vector< Node > ebvl; + Node qbody_subs = q[1]; + for( unsigned i=0; i<q[0].getNumChildren(); i++ ){ + Node sf = q[0][i]; + // v encodes the syntactic restrictions (via an inductive datatype) on sf + // from the input + Node v = sf.getAttribute(SygusSynthGrammarAttribute()); + Assert(!v.isNull()); + Node sfvl = sf.getAttribute(SygusSynthFunVarListAttribute()); + // sfvl may be null for constant synthesis functions + Trace("cegqi-debug") << "...sygus var list associated with " << sf << " is " << sfvl << std::endl; + + TypeNode tn; + std::stringstream ss; + ss << sf; + if( v.getType().isDatatype() && ((DatatypeType)v.getType().toType()).getDatatype().isSygus() ){ + tn = v.getType(); + }else{ + // check which arguments are irrelevant + std::unordered_set<unsigned> arg_irrelevant; + d_parent->getProcess()->getIrrelevantArgs(sf, arg_irrelevant); + std::unordered_set<Node, NodeHashFunction> term_irrelevant; + // convert to term + for (std::unordered_set<unsigned>::iterator ita = arg_irrelevant.begin(); + ita != arg_irrelevant.end(); + ++ita) + { + unsigned arg = *ita; + Assert(arg < sfvl.getNumChildren()); + term_irrelevant.insert(sfvl[arg]); + } + + // make the default grammar + tn = mkSygusDefaultType( + v.getType(), sfvl, ss.str(), extra_cons, term_irrelevant); + } + // normalize type + SygusGrammarNorm sygus_norm(d_qe); + tn = sygus_norm.normalizeSygusType(tn, sfvl); + // check if there is a template + std::map< Node, Node >::iterator itt = templates.find( sf ); + if( itt!=templates.end() ){ + Node templ = itt->second; + TNode templ_arg = templates_arg[sf]; + Assert( !templ_arg.isNull() ); + Trace("cegqi-debug") << "Template for " << sf << " is : " << templ << " with arg " << templ_arg << std::endl; + // if there is a template for this argument, make a sygus type on top of it + if( options::sygusTemplEmbedGrammar() ){ + Trace("cegqi-debug") << " embed this template as a grammar..." << std::endl; + tn = mkSygusTemplateType( templ, templ_arg, tn, sfvl, ss.str() ); + }else{ + // otherwise, apply it as a preprocessing pass + Trace("cegqi-debug") << " apply this template as a substituion during preprocess..." << std::endl; + std::vector< Node > schildren; + std::vector< Node > largs; + for( unsigned j=0; j<sfvl.getNumChildren(); j++ ){ + schildren.push_back( sfvl[j] ); + largs.push_back( NodeManager::currentNM()->mkBoundVar( sfvl[j].getType() ) ); + } + std::vector< Node > subsfn_children; + subsfn_children.push_back( sf ); + subsfn_children.insert( subsfn_children.end(), schildren.begin(), schildren.end() ); + Node subsfn = NodeManager::currentNM()->mkNode( kind::APPLY_UF, subsfn_children ); + TNode subsf = subsfn; + Trace("cegqi-debug") << " substitute arg : " << templ_arg << " -> " << subsf << std::endl; + templ = templ.substitute( templ_arg, subsf ); + // substitute lambda arguments + templ = templ.substitute( schildren.begin(), schildren.end(), largs.begin(), largs.end() ); + Node subsn = NodeManager::currentNM()->mkNode( kind::LAMBDA, NodeManager::currentNM()->mkNode( BOUND_VAR_LIST, largs ), templ ); + TNode var = sf; + TNode subs = subsn; + Trace("cegqi-debug") << " substitute : " << var << " -> " << subs << std::endl; + qbody_subs = qbody_subs.substitute( var, subs ); + Trace("cegqi-debug") << " body is now : " << qbody_subs << std::endl; + } + } + d_qe->getTermDatabaseSygus()->registerSygusType( tn ); + // check grammar restrictions + if( !d_qe->getTermDatabaseSygus()->sygusToBuiltinType( tn ).isBoolean() ){ + if( !d_qe->getTermDatabaseSygus()->hasKind( tn, ITE ) ){ + d_has_ite = false; + } + } + Assert( tn.isDatatype() ); + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Assert( dt.isSygus() ); + if( !dt.getSygusAllowAll() ){ + d_is_syntax_restricted = true; + } + + // ev is the first-order variable corresponding to this synth fun + std::stringstream ssf; + ssf << "f" << sf; + Node ev = NodeManager::currentNM()->mkBoundVar( ssf.str(), tn ); + ebvl.push_back( ev ); + synth_fun_vars[sf] = ev; + Trace("cegqi") << "...embedding synth fun : " << sf << " -> " << ev << std::endl; + } + qchildren.push_back( NodeManager::currentNM()->mkNode( kind::BOUND_VAR_LIST, ebvl ) ); + if( qbody_subs!=q[1] ){ + Trace("cegqi") << "...rewriting : " << qbody_subs << std::endl; + qbody_subs = Rewriter::rewrite( qbody_subs ); + Trace("cegqi") << "...got : " << qbody_subs << std::endl; + } + qchildren.push_back( convertToEmbedding( qbody_subs, synth_fun_vars ) ); + if( q.getNumChildren()==3 ){ + qchildren.push_back( q[2] ); + } + return NodeManager::currentNM()->mkNode( kind::FORALL, qchildren ); +} + +Node CegGrammarConstructor::convertToEmbedding( Node n, std::map< Node, Node >& synth_fun_vars ){ + std::unordered_map<TNode, Node, TNodeHashFunction> visited; + std::unordered_map<TNode, Node, TNodeHashFunction>::iterator it; + std::stack<TNode> visit; + TNode cur; + visit.push(n); + do { + cur = visit.top(); + visit.pop(); + it = visited.find(cur); + if (it == visited.end()) { + visited[cur] = Node::null(); + visit.push(cur); + for (unsigned i = 0; i < cur.getNumChildren(); i++) { + visit.push(cur[i]); + } + } else if (it->second.isNull()) { + Node ret = cur; + Kind ret_k = cur.getKind(); + Node op; + bool childChanged = false; + std::vector<Node> children; + // get the potential operator + if( cur.getNumChildren()>0 ){ + if( cur.getKind()==kind::APPLY_UF ){ + op = cur.getOperator(); + } + }else{ + op = cur; + } + // is the operator a synth function? + if( !op.isNull() ){ + std::map< Node, Node >::iterator its = synth_fun_vars.find( op ); + if( its!=synth_fun_vars.end() ){ + Assert( its->second.getType().isDatatype() ); + // will make into an application of an evaluation function + const Datatype& dt = ((DatatypeType)its->second.getType().toType()).getDatatype(); + Assert( dt.isSygus() ); + children.push_back( Node::fromExpr( dt.getSygusEvaluationFunc() ) ); + children.push_back( its->second ); + childChanged = true; + ret_k = kind::APPLY_UF; + } + } + if( !childChanged ){ + // otherwise, we apply the previous operator + if( cur.getMetaKind() == kind::metakind::PARAMETERIZED ){ + children.push_back( cur.getOperator() ); + } + } + for (unsigned i = 0; i < cur.getNumChildren(); i++) { + it = visited.find(cur[i]); + Assert(it != visited.end()); + Assert(!it->second.isNull()); + childChanged = childChanged || cur[i] != it->second; + children.push_back(it->second); + } + if (childChanged) { + ret = NodeManager::currentNM()->mkNode(ret_k, children); + } + visited[cur] = ret; + } + } while (!visit.empty()); + Assert(visited.find(n) != visited.end()); + Assert(!visited.find(n)->second.isNull()); + return visited[n]; +} + + +TypeNode CegGrammarConstructor::mkUnresolvedType(const std::string& name, std::set<Type>& unres) { + TypeNode unresolved = NodeManager::currentNM()->mkSort(name, ExprManager::SORT_FLAG_PLACEHOLDER); + unres.insert( unresolved.toType() ); + return unresolved; +} + +void CegGrammarConstructor::mkSygusConstantsForType( TypeNode type, std::vector<CVC4::Node>& ops ) { + if (type.isReal()) + { + ops.push_back(NodeManager::currentNM()->mkConst(Rational(0))); + ops.push_back(NodeManager::currentNM()->mkConst(Rational(1))); + }else if( type.isBitVector() ){ + unsigned sz = ((BitVectorType)type.toType()).getSize(); + BitVector bval0(sz, (unsigned int)0); + ops.push_back( NodeManager::currentNM()->mkConst(bval0) ); + BitVector bval1(sz, (unsigned int)1); + ops.push_back( NodeManager::currentNM()->mkConst(bval1) ); + }else if( type.isBoolean() ){ + ops.push_back(NodeManager::currentNM()->mkConst(true)); + ops.push_back(NodeManager::currentNM()->mkConst(false)); + } + //TODO : others? +} + +void CegGrammarConstructor::collectSygusGrammarTypesFor( TypeNode range, std::vector< TypeNode >& types, std::map< TypeNode, std::vector< DatatypeConstructorArg > >& sels ){ + if( !range.isBoolean() ){ + if( std::find( types.begin(), types.end(), range )==types.end() ){ + Trace("sygus-grammar-def") << "...will make grammar for " << range << std::endl; + types.push_back( range ); + if( range.isDatatype() ){ + const Datatype& dt = ((DatatypeType)range.toType()).getDatatype(); + for( unsigned i=0; i<dt.getNumConstructors(); i++ ){ + for( unsigned j=0; j<dt[i].getNumArgs(); j++ ){ + TypeNode crange = TypeNode::fromType( ((SelectorType)dt[i][j].getType()).getRangeType() ); + sels[crange].push_back( dt[i][j] ); + collectSygusGrammarTypesFor( crange, types, sels ); + } + } + } + } + } +} + +void CegGrammarConstructor::mkSygusDefaultGrammar( + TypeNode range, + Node bvl, + const std::string& fun, + std::map<TypeNode, std::vector<Node> >& extra_cons, + std::unordered_set<Node, NodeHashFunction>& term_irrelevant, + std::vector<CVC4::Datatype>& datatypes, + std::set<Type>& unres) +{ + Trace("sygus-grammar-def") << "Construct default grammar for " << fun << " " + << range << std::endl; + // collect the variables + std::vector<Node> sygus_vars; + if( !bvl.isNull() ){ + for( unsigned i=0; i<bvl.getNumChildren(); i++ ){ + if (term_irrelevant.find(bvl[i]) == term_irrelevant.end()) + { + sygus_vars.push_back(bvl[i]); + } + else + { + Trace("sygus-grammar-def") << "...synth var " << bvl[i] + << " has been marked irrelevant." + << std::endl; + } + } + } + //if( !range.isBoolean() && !range.isInteger() && !range.isBitVector() && !range.isDatatype() ){ + // parseError("No default grammar for type."); + //} + std::vector< std::vector< Expr > > ops; + int startIndex = -1; + std::map< Type, Type > sygus_to_builtin; + + std::vector< TypeNode > types; + std::map< TypeNode, std::vector< DatatypeConstructorArg > > sels; + //types for each of the variables of parametric sort + for( unsigned i=0; i<sygus_vars.size(); i++ ){ + collectSygusGrammarTypesFor( sygus_vars[i].getType(), types, sels ); + } + //types connected to range + collectSygusGrammarTypesFor( range, types, sels ); + + //name of boolean sort + std::stringstream ssb; + ssb << fun << "_Bool"; + std::string dbname = ssb.str(); + Type unres_bt = mkUnresolvedType(ssb.str(), unres).toType(); + + std::vector< Type > unres_types; + std::map< TypeNode, Type > type_to_unres; + for( unsigned i=0; i<types.size(); i++ ){ + std::stringstream ss; + ss << fun << "_" << types[i]; + std::string dname = ss.str(); + datatypes.push_back(Datatype(dname)); + ops.push_back(std::vector< Expr >()); + //make unresolved type + Type unres_t = mkUnresolvedType(dname, unres).toType(); + unres_types.push_back(unres_t); + type_to_unres[types[i]] = unres_t; + sygus_to_builtin[unres_t] = types[i].toType(); + } + for( unsigned i=0; i<types.size(); i++ ){ + Trace("sygus-grammar-def") << "Make grammar for " << types[i] << " " << unres_types[i] << std::endl; + std::vector<std::string> cnames; + std::vector<std::vector<CVC4::Type> > cargs; + Type unres_t = unres_types[i]; + //add variables + for( unsigned j=0; j<sygus_vars.size(); j++ ){ + if( sygus_vars[j].getType()==types[i] ){ + std::stringstream ss; + ss << sygus_vars[j]; + Trace("sygus-grammar-def") << "...add for variable " << ss.str() << std::endl; + ops[i].push_back( sygus_vars[j].toExpr() ); + cnames.push_back( ss.str() ); + cargs.push_back( std::vector< CVC4::Type >() ); + } + } + //add constants + std::vector< Node > consts; + mkSygusConstantsForType( types[i], consts ); + std::map< TypeNode, std::vector< Node > >::iterator itec = extra_cons.find( types[i] ); + if( itec!=extra_cons.end() ){ + //consts.insert( consts.end(), itec->second.begin(), itec->second.end() ); + for( unsigned j=0; j<itec->second.size(); j++ ){ + if( std::find( consts.begin(), consts.end(), itec->second[j] )==consts.end() ){ + consts.push_back( itec->second[j] ); + } + } + } + for( unsigned j=0; j<consts.size(); j++ ){ + std::stringstream ss; + ss << consts[j]; + Trace("sygus-grammar-def") << "...add for constant " << ss.str() << std::endl; + ops[i].push_back( consts[j].toExpr() ); + cnames.push_back( ss.str() ); + cargs.push_back( std::vector< CVC4::Type >() ); + } + //ITE + CVC4::Kind k = kind::ITE; + Trace("sygus-grammar-def") << "...add for " << k << std::endl; + ops[i].push_back(NodeManager::currentNM()->operatorOf(k).toExpr()); + cnames.push_back( kind::kindToString(k) ); + cargs.push_back( std::vector< CVC4::Type >() ); + cargs.back().push_back(unres_bt); + cargs.back().push_back(unres_t); + cargs.back().push_back(unres_t); + + if (types[i].isReal()) + { + for (unsigned j = 0; j < 2; j++) + { + Kind k = j == 0 ? PLUS : MINUS; + Trace("sygus-grammar-def") << "...add for " << k << std::endl; + ops[i].push_back(NodeManager::currentNM()->operatorOf(k).toExpr()); + cnames.push_back(kind::kindToString(k)); + cargs.push_back(std::vector<CVC4::Type>()); + cargs.back().push_back(unres_t); + cargs.back().push_back(unres_t); + } + if (!types[i].isInteger()) + { + Trace("sygus-grammar-def") << "...Dedicate to Real\n"; + /* Creating type for positive integers */ + std::stringstream ss; + ss << fun << "_PosInt"; + std::string pos_int_name = ss.str(); + // make unresolved type + Type unres_pos_int_t = mkUnresolvedType(pos_int_name, unres).toType(); + // make data type + datatypes.push_back(Datatype(pos_int_name)); + /* add placeholders */ + std::vector<Expr> ops_pos_int; + std::vector<std::string> cnames_pos_int; + std::vector<std::vector<Type>> cargs_pos_int; + /* Add operator 1 */ + Trace("sygus-grammar-def") << "\t...add for 1 to Pos_Int\n"; + ops_pos_int.push_back( + NodeManager::currentNM()->mkConst(Rational(1)).toExpr()); + ss << "_1"; + cnames_pos_int.push_back(ss.str()); + cargs_pos_int.push_back(std::vector<Type>()); + /* Add operator PLUS */ + Kind k = PLUS; + Trace("sygus-grammar-def") << "\t...add for PLUS to Pos_Int\n"; + ops_pos_int.push_back(NodeManager::currentNM()->operatorOf(k).toExpr()); + cnames_pos_int.push_back(kindToString(k)); + cargs_pos_int.push_back(std::vector<Type>()); + cargs_pos_int.back().push_back(unres_pos_int_t); + cargs_pos_int.back().push_back(unres_pos_int_t); + datatypes.back().setSygus(types[i].toType(), bvl.toExpr(), true, true); + for (unsigned j = 0; j < ops_pos_int.size(); j++) + { + datatypes.back().addSygusConstructor( + ops_pos_int[j], cnames_pos_int[j], cargs_pos_int[j]); + } + Trace("sygus-grammar-def") + << "...built datatype " << datatypes.back() << " "; + /* Adding division at root */ + k = DIVISION; + Trace("sygus-grammar-def") << "\t...add for " << k << std::endl; + ops[i].push_back(NodeManager::currentNM()->operatorOf(k).toExpr()); + cnames.push_back(kindToString(k)); + cargs.push_back(std::vector<Type>()); + cargs.back().push_back(unres_t); + cargs.back().push_back(unres_pos_int_t); + } + }else if( types[i].isDatatype() ){ + Trace("sygus-grammar-def") << "...add for constructors" << std::endl; + const Datatype& dt = ((DatatypeType)types[i].toType()).getDatatype(); + for( unsigned k=0; k<dt.getNumConstructors(); k++ ){ + Trace("sygus-grammar-def") << "...for " << dt[k].getName() << std::endl; + ops[i].push_back( dt[k].getConstructor() ); + cnames.push_back( dt[k].getName() ); + cargs.push_back( std::vector< CVC4::Type >() ); + for( unsigned j=0; j<dt[k].getNumArgs(); j++ ){ + TypeNode crange = TypeNode::fromType( ((SelectorType)dt[k][j].getType()).getRangeType() ); + //Assert( type_to_unres.find(crange)!=type_to_unres.end() ); + cargs.back().push_back( type_to_unres[crange] ); + } + } + }else{ + std::stringstream sserr; + sserr << "No implementation for default Sygus grammar of type " << types[i] << std::endl; + //AlwaysAssert( false, sserr.str() ); + // FIXME + AlwaysAssert( false ); + } + //add for all selectors to this type + if( !sels[types[i]].empty() ){ + Trace("sygus-grammar-def") << "...add for selectors" << std::endl; + for( unsigned j=0; j<sels[types[i]].size(); j++ ){ + Trace("sygus-grammar-def") << "...for " << sels[types[i]][j].getName() << std::endl; + TypeNode arg_type = TypeNode::fromType( ((SelectorType)sels[types[i]][j].getType()).getDomain() ); + ops[i].push_back( sels[types[i]][j].getSelector() ); + cnames.push_back( sels[types[i]][j].getName() ); + cargs.push_back( std::vector< CVC4::Type >() ); + //Assert( type_to_unres.find(arg_type)!=type_to_unres.end() ); + cargs.back().push_back( type_to_unres[arg_type] ); + } + } + Trace("sygus-grammar-def") << "...make datatype " << datatypes[i] << std::endl; + datatypes[i].setSygus( types[i].toType(), bvl.toExpr(), true, true ); + for( unsigned j=0; j<ops[i].size(); j++ ){ + datatypes[i].addSygusConstructor( ops[i][j], cnames[j], cargs[j] ); + } + Trace("sygus-grammar-def") + << "...built datatype " << datatypes[i] << " "; + //sorts.push_back( types[i] ); + //set start index if applicable + if( types[i]==range ){ + startIndex = i; + } + } + + //make Boolean type + TypeNode btype = NodeManager::currentNM()->booleanType(); + datatypes.push_back(Datatype(dbname)); + ops.push_back(std::vector<Expr>()); + std::vector<std::string> cnames; + std::vector<std::vector< Type > > cargs; + Trace("sygus-grammar-def") << "Make grammar for " << btype << " " << datatypes.back() << std::endl; + //add variables + for( unsigned i=0; i<sygus_vars.size(); i++ ){ + if( sygus_vars[i].getType().isBoolean() ){ + std::stringstream ss; + ss << sygus_vars[i]; + Trace("sygus-grammar-def") << "...add for variable " << ss.str() << std::endl; + ops.back().push_back( sygus_vars[i].toExpr() ); + cnames.push_back( ss.str() ); + cargs.push_back( std::vector< CVC4::Type >() ); + } + } + //add constants if no variables and no connected types + if( ops.back().empty() && types.empty() ){ + std::vector< Node > consts; + mkSygusConstantsForType( btype, consts ); + for( unsigned j=0; j<consts.size(); j++ ){ + std::stringstream ss; + ss << consts[j]; + Trace("sygus-grammar-def") << "...add for constant " << ss.str() << std::endl; + ops.back().push_back( consts[j].toExpr() ); + cnames.push_back( ss.str() ); + cargs.push_back( std::vector< CVC4::Type >() ); + } + } + //add operators + for( unsigned i=0; i<3; i++ ){ + CVC4::Kind k = i==0 ? kind::NOT : ( i==1 ? kind::AND : kind::OR ); + Trace("sygus-grammar-def") << "...add for " << k << std::endl; + ops.back().push_back(NodeManager::currentNM()->operatorOf(k).toExpr()); + cnames.push_back(kind::kindToString(k)); + cargs.push_back( std::vector< CVC4::Type >() ); + if( k==kind::NOT ){ + cargs.back().push_back(unres_bt); + }else if( k==kind::AND || k==kind::OR ){ + cargs.back().push_back(unres_bt); + cargs.back().push_back(unres_bt); + } + } + //add predicates for types + for( unsigned i=0; i<types.size(); i++ ){ + Trace("sygus-grammar-def") << "...add predicates for " << types[i] << std::endl; + //add equality per type + CVC4::Kind k = kind::EQUAL; + Trace("sygus-grammar-def") << "...add for " << k << std::endl; + ops.back().push_back(NodeManager::currentNM()->operatorOf(k).toExpr()); + std::stringstream ss; + ss << kind::kindToString(k) << "_" << types[i]; + cnames.push_back(ss.str()); + cargs.push_back( std::vector< CVC4::Type >() ); + cargs.back().push_back(unres_types[i]); + cargs.back().push_back(unres_types[i]); + //type specific predicates + if (types[i].isReal()) + { + CVC4::Kind k = kind::LEQ; + Trace("sygus-grammar-def") << "...add for " << k << std::endl; + ops.back().push_back(NodeManager::currentNM()->operatorOf(k).toExpr()); + cnames.push_back(kind::kindToString(k)); + cargs.push_back( std::vector< CVC4::Type >() ); + cargs.back().push_back(unres_types[i]); + cargs.back().push_back(unres_types[i]); + }else if( types[i].isDatatype() ){ + //add for testers + Trace("sygus-grammar-def") << "...add for testers" << std::endl; + const Datatype& dt = ((DatatypeType)types[i].toType()).getDatatype(); + for( unsigned k=0; k<dt.getNumConstructors(); k++ ){ + Trace("sygus-grammar-def") << "...for " << dt[k].getTesterName() << std::endl; + ops.back().push_back(dt[k].getTester()); + cnames.push_back(dt[k].getTesterName()); + cargs.push_back( std::vector< CVC4::Type >() ); + cargs.back().push_back(unres_types[i]); + } + } + } + if( range==btype ){ + startIndex = datatypes.size()-1; + } + Trace("sygus-grammar-def") << "...make datatype " << datatypes.back() << std::endl; + datatypes.back().setSygus( btype.toType(), bvl.toExpr(), true, true ); + for( unsigned j=0; j<ops.back().size(); j++ ){ + datatypes.back().addSygusConstructor( ops.back()[j], cnames[j], cargs[j] ); + } + //sorts.push_back( btype ); + Trace("sygus-grammar-def") << "...finished make default grammar for " << fun << " " << range << std::endl; + + if( startIndex>0 ){ + CVC4::Datatype tmp_dt = datatypes[0]; + datatypes[0] = datatypes[startIndex]; + datatypes[startIndex] = tmp_dt; + } +} + +TypeNode CegGrammarConstructor::mkSygusDefaultType( + TypeNode range, + Node bvl, + const std::string& fun, + std::map<TypeNode, std::vector<Node> >& extra_cons, + std::unordered_set<Node, NodeHashFunction>& term_irrelevant) +{ + Trace("sygus-grammar-def") << "*** Make sygus default type " << range << ", make datatypes..." << std::endl; + for( std::map< TypeNode, std::vector< Node > >::iterator it = extra_cons.begin(); it != extra_cons.end(); ++it ){ + Trace("sygus-grammar-def") << " ...using " << it->second.size() << " extra constants for " << it->first << std::endl; + } + std::set<Type> unres; + std::vector< CVC4::Datatype > datatypes; + mkSygusDefaultGrammar( + range, bvl, fun, extra_cons, term_irrelevant, datatypes, unres); + Trace("sygus-grammar-def") << "...made " << datatypes.size() << " datatypes, now make mutual datatype types..." << std::endl; + Assert( !datatypes.empty() ); + std::vector<DatatypeType> types = NodeManager::currentNM()->toExprManager()->mkMutualDatatypeTypes(datatypes, unres); + Assert( types.size()==datatypes.size() ); + return TypeNode::fromType( types[0] ); +} + +TypeNode CegGrammarConstructor::mkSygusTemplateTypeRec( Node templ, Node templ_arg, TypeNode templ_arg_sygus_type, Node bvl, + const std::string& fun, unsigned& tcount ) { + if( templ==templ_arg ){ + //Assert( templ_arg.getType()==sygusToBuiltinType( templ_arg_sygus_type ) ); + return templ_arg_sygus_type; + }else{ + tcount++; + std::set<Type> unres; + std::vector< CVC4::Datatype > datatypes; + std::stringstream ssd; + ssd << fun << "_templ_" << tcount; + std::string dbname = ssd.str(); + datatypes.push_back(Datatype(dbname)); + Node op; + std::vector< Type > argTypes; + if( templ.getNumChildren()==0 ){ + // TODO : can short circuit to this case when !TermUtil::containsTerm( templ, templ_arg ) + op = templ; + }else{ + Assert( templ.hasOperator() ); + op = templ.getOperator(); + // make constructor taking arguments types from children + for( unsigned i=0; i<templ.getNumChildren(); i++ ){ + //recursion depth bound by the depth of SyGuS template expressions (low) + TypeNode tnc = mkSygusTemplateTypeRec( templ[i], templ_arg, templ_arg_sygus_type, bvl, fun, tcount ); + argTypes.push_back( tnc.toType() ); + } + } + std::stringstream ssdc; + ssdc << fun << "_templ_cons_" << tcount; + std::string cname = ssdc.str(); + // we have a single sygus constructor that encodes the template + datatypes.back().addSygusConstructor( op.toExpr(), cname, argTypes ); + datatypes.back().setSygus( templ.getType().toType(), bvl.toExpr(), true, true ); + std::vector<DatatypeType> types = NodeManager::currentNM()->toExprManager()->mkMutualDatatypeTypes(datatypes, unres); + Assert( types.size()==1 ); + return TypeNode::fromType( types[0] ); + } +} + +TypeNode CegGrammarConstructor::mkSygusTemplateType( Node templ, Node templ_arg, TypeNode templ_arg_sygus_type, Node bvl, + const std::string& fun ) { + unsigned tcount = 0; + return mkSygusTemplateTypeRec( templ, templ_arg, templ_arg_sygus_type, bvl, fun, tcount ); +} + +}/* namespace CVC4::theory::quantifiers */ +}/* namespace CVC4::theory */ +}/* namespace CVC4 */ diff --git a/src/theory/quantifiers/sygus/sygus_grammar_cons.h b/src/theory/quantifiers/sygus/sygus_grammar_cons.h new file mode 100644 index 000000000..4e486f88f --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_grammar_cons.h @@ -0,0 +1,131 @@ +/********************* */ +/*! \file sygus_grammar_cons.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief class for constructing inductive datatypes that correspond to + ** grammars that encode syntactic restrictions for SyGuS. + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__SYGUS_GRAMMAR_CONS_H +#define __CVC4__THEORY__QUANTIFIERS__SYGUS_GRAMMAR_CONS_H + +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +class CegConjecture; + +/** utility for constructing datatypes that correspond to syntactic restrictions, +* and applying the deep embedding from Section 4 of Reynolds et al CAV 2015. +*/ +class CegGrammarConstructor +{ +public: + CegGrammarConstructor(QuantifiersEngine* qe, CegConjecture* p); + ~CegGrammarConstructor() {} + /** process + * This converts node q based on its deep embedding + * (Section 4 of Reynolds et al CAV 2015). + * The syntactic restrictions are associated with + * the functions-to-synthesize using the attribute + * SygusSynthGrammarAttribute. + * The arguments templates and template_args + * indicate templates for the function to synthesize, + * in particular the solution for the i^th function + * to synthesis must be of the form + * templates[i]{ templates_arg[i] -> t } + * for some t if !templates[i].isNull(). + */ + Node process(Node q, + std::map<Node, Node>& templates, + std::map<Node, Node>& templates_arg); + /** is the syntax restricted? */ + bool isSyntaxRestricted() { return d_is_syntax_restricted; } + /** does the syntax allow ITE expressions? */ + bool hasSyntaxITE() { return d_has_ite; } + /** make the default sygus datatype type corresponding to builtin type range + * bvl is the set of free variables to include in the grammar + * fun is for naming + * extra_cons is a set of extra constant symbols to include in the grammar + * term_irrelevant is a set of terms that should not be included in the + * grammar. + */ + static TypeNode mkSygusDefaultType( + TypeNode range, + Node bvl, + const std::string& fun, + std::map<TypeNode, std::vector<Node> >& extra_cons, + std::unordered_set<Node, NodeHashFunction>& term_irrelevant); + /** make the default sygus datatype type corresponding to builtin type range */ + static TypeNode mkSygusDefaultType(TypeNode range, + Node bvl, + const std::string& fun) + { + std::map<TypeNode, std::vector<Node> > extra_cons; + std::unordered_set<Node, NodeHashFunction> term_irrelevant; + return mkSygusDefaultType(range, bvl, fun, extra_cons, term_irrelevant); + } + /** make the sygus datatype type that encodes the solution space (lambda + * templ_arg. templ[templ_arg]) where templ_arg + * has syntactic restrictions encoded by sygus type templ_arg_sygus_type + * bvl is the set of free variables to include in the grammar + * fun is for naming + */ + static TypeNode mkSygusTemplateType( Node templ, Node templ_arg, TypeNode templ_arg_sygus_type, Node bvl, const std::string& fun ); +private: + /** reference to quantifier engine */ + QuantifiersEngine * d_qe; + /** parent conjecture + * This contains global information about the synthesis conjecture. + */ + CegConjecture* d_parent; + /** is the syntax restricted? */ + bool d_is_syntax_restricted; + /** does the syntax allow ITE expressions? */ + bool d_has_ite; + /** collect terms */ + void collectTerms( Node n, std::map< TypeNode, std::vector< Node > >& consts ); + /** convert node n based on deep embedding (Section 4 of Reynolds et al CAV 2015) */ + Node convertToEmbedding( Node n, std::map< Node, Node >& synth_fun_vars ); + //---------------- grammar construction + // helper for mkSygusDefaultGrammar (makes unresolved type for mutually recursive datatype construction) + static TypeNode mkUnresolvedType(const std::string& name, std::set<Type>& unres); + // make the builtin constants for type type that should be included in a sygus grammar + static void mkSygusConstantsForType( TypeNode type, std::vector<CVC4::Node>& ops ); + // collect the list of types that depend on type range + static void collectSygusGrammarTypesFor( TypeNode range, std::vector< TypeNode >& types, std::map< TypeNode, std::vector< DatatypeConstructorArg > >& sels ); + /** helper function for function mkSygusDefaultType + * Collects a set of mutually recursive datatypes "datatypes" corresponding to + * encoding type "range" to SyGuS. + * unres is used for the resulting call to mkMutualDatatypeTypes + */ + static void mkSygusDefaultGrammar( + TypeNode range, + Node bvl, + const std::string& fun, + std::map<TypeNode, std::vector<Node> >& extra_cons, + std::unordered_set<Node, NodeHashFunction>& term_irrelevant, + std::vector<CVC4::Datatype>& datatypes, + std::set<Type>& unres); + // helper function for mkSygusTemplateType + static TypeNode mkSygusTemplateTypeRec( Node templ, Node templ_arg, TypeNode templ_arg_sygus_type, Node bvl, + const std::string& fun, unsigned& tcount ); + //---------------- end grammar construction +}; + +} /* namespace CVC4::theory::quantifiers */ +} /* namespace CVC4::theory */ +} /* namespace CVC4 */ + +#endif diff --git a/src/theory/quantifiers/sygus/sygus_grammar_norm.cpp b/src/theory/quantifiers/sygus/sygus_grammar_norm.cpp new file mode 100644 index 000000000..73311b0bd --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_grammar_norm.cpp @@ -0,0 +1,492 @@ +/********************* */ +/*! \file sygus_grammar_norm.cpp + ** \verbatim + ** Top contributors (to current version): + ** Haniel Barbosa + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief implementation of class for for simplifying SyGuS grammars after they + ** are encoded into datatypes. + **/ + +#include "theory/quantifiers/sygus/sygus_grammar_norm.h" + +#include "expr/datatype.h" +#include "options/quantifiers_options.h" +#include "printer/sygus_print_callback.h" +#include "smt/smt_engine.h" +#include "smt/smt_engine_scope.h" +#include "theory/quantifiers/sygus/ce_guided_conjecture.h" +#include "theory/quantifiers/sygus/sygus_grammar_red.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_util.h" + +#include <numeric> // for std::iota + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +bool OpPosTrie::getOrMakeType(TypeNode tn, + TypeNode& unres_tn, + const std::vector<unsigned>& op_pos, + unsigned ind) +{ + if (ind == op_pos.size()) + { + /* Found type */ + if (!d_unres_tn.isNull()) + { + Trace("sygus-grammar-normalize-trie") + << "\tFound type " << d_unres_tn << "\n"; + unres_tn = d_unres_tn; + return true; + } + /* Creating unresolved type */ + std::stringstream ss; + ss << tn << "_"; + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + ss << "_" << std::to_string(op_pos[i]); + } + d_unres_tn = NodeManager::currentNM()->mkSort( + ss.str(), ExprManager::SORT_FLAG_PLACEHOLDER); + Trace("sygus-grammar-normalize-trie") + << "\tCreating type " << d_unres_tn << "\n"; + unres_tn = d_unres_tn; + return false; + } + /* Go to next node */ + return d_children[op_pos[ind]].getOrMakeType(tn, unres_tn, op_pos, ind + 1); +} + +void SygusGrammarNorm::TypeObject::addConsInfo(SygusGrammarNorm* sygus_norm, + const DatatypeConstructor& cons) +{ + Trace("sygus-grammar-normalize") << "...for " << cons.getName() << "\n"; + /* Recover the sygus operator to not lose reference to the original + * operator (NOT, ITE, etc) */ + Node exp_sop_n = Node::fromExpr( + smt::currentSmtEngine()->expandDefinitions(cons.getSygusOp())); + d_ops.push_back(Rewriter::rewrite(exp_sop_n)); + Trace("sygus-grammar-normalize-defs") + << "\tOriginal op: " << cons.getSygusOp() + << "\n\tExpanded one: " << exp_sop_n + << "\n\tRewritten one: " << d_ops.back() << "\n\n"; + d_cons_names.push_back(cons.getName()); + d_pc.push_back(cons.getSygusPrintCallback()); + d_weight.push_back(cons.getWeight()); + d_cons_args_t.push_back(std::vector<Type>()); + for (const DatatypeConstructorArg& arg : cons) + { + /* Collect unresolved type nodes corresponding to the typenode of the + * arguments */ + d_cons_args_t.back().push_back( + sygus_norm + ->normalizeSygusRec(TypeNode::fromType( + static_cast<SelectorType>(arg.getType()).getRangeType())) + .toType()); + } +} + +void SygusGrammarNorm::TypeObject::buildDatatype(SygusGrammarNorm* sygus_norm, + const Datatype& dt) +{ + /* Use the sygus type to not lose reference to the original types (Bool, + * Int, etc) */ + d_dt.setSygus(dt.getSygusType(), + sygus_norm->d_sygus_vars.toExpr(), + dt.getSygusAllowConst(), + dt.getSygusAllowAll()); + for (unsigned i = 0, size_d_ops = d_ops.size(); i < size_d_ops; ++i) + { + d_dt.addSygusConstructor(d_ops[i].toExpr(), + d_cons_names[i], + d_cons_args_t[i], + d_pc[i], + d_weight[i]); + } + Trace("sygus-grammar-normalize") << "...built datatype " << d_dt << " "; + /* Add to global accumulators */ + sygus_norm->d_dt_all.push_back(d_dt); + sygus_norm->d_unres_t_all.insert(d_unres_tn.toType()); + Trace("sygus-grammar-normalize") << "---------------------------------\n"; +} + +void SygusGrammarNorm::TransfDrop::buildType(SygusGrammarNorm* sygus_norm, + TypeObject& to, + const Datatype& dt, + std::vector<unsigned>& op_pos) +{ + std::vector<unsigned> difference; + std::set_difference(op_pos.begin(), + op_pos.end(), + d_drop_indices.begin(), + d_drop_indices.end(), + std::back_inserter(difference)); + op_pos = difference; +} + +/* TODO #1304: have more operators and types. Moreover, have more general ways + of finding kind of operator, e.g. if op is (\lambda xy. x + y) this + function should realize that it is chainable for integers */ +bool SygusGrammarNorm::TransfChain::isChainable(TypeNode tn, Node op) +{ + /* Checks whether operator occurs chainable for its type */ + if (tn.isInteger() && NodeManager::currentNM()->operatorToKind(op) == PLUS) + { + return true; + } + return false; +} + +/* TODO #1304: have more operators and types. Moreover, have more general ways + of finding kind of operator, e.g. if op is (\lambda xy. x + y) this + function should realize that it is chainable for integers */ +bool SygusGrammarNorm::TransfChain::isId(TypeNode tn, Node op, Node n) +{ + if (tn.isInteger() && NodeManager::currentNM()->operatorToKind(op) == PLUS + && n == TermUtil::mkTypeValue(tn, 0)) + { + return true; + } + return false; +} + +void SygusGrammarNorm::TransfChain::buildType(SygusGrammarNorm* sygus_norm, + TypeObject& to, + const Datatype& dt, + std::vector<unsigned>& op_pos) +{ + NodeManager* nm = NodeManager::currentNM(); + std::vector<unsigned> claimed(d_elem_pos); + claimed.push_back(d_chain_op_pos); + unsigned nb_op_pos = op_pos.size(); + /* TODO do this properly */ + /* Remove from op_pos the positions claimed by the transformation */ + std::sort(op_pos.begin(), op_pos.end()); + std::sort(claimed.begin(), claimed.end()); + std::vector<unsigned> difference; + std::set_difference(op_pos.begin(), + op_pos.end(), + claimed.begin(), + claimed.end(), + std::back_inserter(difference)); + op_pos = difference; + if (Trace.isOn("sygus-grammar-normalize-chain")) + { + Trace("sygus-grammar-normalize-chain") + << "OP at " << d_chain_op_pos << "\n" + << d_elem_pos.size() << " d_elem_pos: "; + for (unsigned i = 0, size = d_elem_pos.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize-chain") << d_elem_pos[i] << " "; + } + Trace("sygus-grammar-normalize-chain") + << "\n" + << op_pos.size() << " remaining op_pos: "; + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize-chain") << op_pos[i] << " "; + } + Trace("sygus-grammar-normalize-chain") << "\n"; + } + /* Build identity operator and empty callback */ + Node iden_op = + SygusGrammarNorm::getIdOp(TypeNode::fromType(dt.getSygusType())); + /* If all operators are claimed, create a monomial */ + if (nb_op_pos == d_elem_pos.size() + 1) + { + Trace("sygus-grammar-normalize-chain") + << "\tCreating id type for " << d_elem_pos.back() << "\n"; + /* creates type for element */ + std::vector<unsigned> tmp; + tmp.push_back(d_elem_pos.back()); + Type t = sygus_norm->normalizeSygusRec(to.d_tn, dt, tmp).toType(); + /* consumes element */ + d_elem_pos.pop_back(); + /* adds to Root: "type" */ + to.d_ops.push_back(iden_op); + to.d_cons_names.push_back("id"); + to.d_pc.push_back(printer::SygusEmptyPrintCallback::getEmptyPC()); + /* Identity operators should not increase the size of terms */ + to.d_weight.push_back(0); + to.d_cons_args_t.push_back(std::vector<Type>()); + to.d_cons_args_t.back().push_back(t); + Trace("sygus-grammar-normalize-chain") + << "\tAdding " << t << " to " << to.d_unres_tn << "\n"; + /* adds to Root: "type + Root" */ + to.d_ops.push_back(nm->operatorOf(PLUS)); + to.d_cons_names.push_back(kindToString(PLUS)); + to.d_pc.push_back(nullptr); + to.d_weight.push_back(-1); + to.d_cons_args_t.push_back(std::vector<Type>()); + to.d_cons_args_t.back().push_back(t); + to.d_cons_args_t.back().push_back(to.d_unres_tn.toType()); + Trace("sygus-grammar-normalize-chain") + << "\tAdding PLUS to " << to.d_unres_tn << " with arg types " + << to.d_unres_tn << " and " << t << "\n"; + } + /* In the initial case if not all operators claimed always creates a next */ + Assert(nb_op_pos != d_elem_pos.size() + 1 || d_elem_pos.size() > 1); + /* TODO #1304: consider case in which CHAIN op has different types than + to.d_tn */ + /* If no more elements to chain, finish */ + if (d_elem_pos.size() == 0) + { + return; + } + /* Creates a type do be added to root representing next step in the chain */ + /* Add + to elems */ + d_elem_pos.push_back(d_chain_op_pos); + if (Trace.isOn("sygus-grammar-normalize-chain")) + { + Trace("sygus-grammar-normalize-chain") + << "\tCreating type for next entry with sygus_ops "; + for (unsigned i = 0, size = d_elem_pos.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize-chain") + << dt[d_elem_pos[i]].getSygusOp() << " "; + } + Trace("sygus-grammar-normalize-chain") << "\n"; + } + /* adds to Root: (\lambda x. x ) Next */ + to.d_ops.push_back(iden_op); + to.d_cons_names.push_back("id_next"); + to.d_pc.push_back(printer::SygusEmptyPrintCallback::getEmptyPC()); + to.d_weight.push_back(0); + to.d_cons_args_t.push_back(std::vector<Type>()); + to.d_cons_args_t.back().push_back( + sygus_norm->normalizeSygusRec(to.d_tn, dt, d_elem_pos).toType()); +} + +std::map<TypeNode, Node> SygusGrammarNorm::d_tn_to_id = {}; + +/* Traverse the constructors of dt according to the positions in op_pos. Collect + * those that fit the kinds established by to_collect. Remove collected operator + * positions from op_pos. Accumulate collected positions in collected + * + * returns true if collected anything + */ +std::unique_ptr<SygusGrammarNorm::Transf> SygusGrammarNorm::inferTransf( + TypeNode tn, const Datatype& dt, const std::vector<unsigned>& op_pos) +{ + NodeManager* nm = NodeManager::currentNM(); + TypeNode sygus_tn = TypeNode::fromType(dt.getSygusType()); + Trace("sygus-gnorm") << "Infer transf for " << dt.getName() << "..." + << std::endl; + Trace("sygus-gnorm") << " #cons = " << op_pos.size() << " / " + << dt.getNumConstructors() << std::endl; + // look for redundant constructors to drop + if (options::sygusMinGrammar() && dt.getNumConstructors() == op_pos.size()) + { + SygusRedundantCons src; + src.initialize(d_qe, tn); + std::vector<unsigned> rindices; + src.getRedundant(rindices); + if (!rindices.empty()) + { + Trace("sygus-gnorm") << "...drop transf, " << rindices.size() << "/" + << op_pos.size() << " constructors." << std::endl; + Assert(rindices.size() < op_pos.size()); + return std::unique_ptr<Transf>(new TransfDrop(rindices)); + } + } + + // if normalization option is not enabled, we do not infer the transformations + // below + if (!options::sygusGrammarNorm()) + { + return nullptr; + } + + /* TODO #1304: step 1: look for singleton */ + /* step 2: look for chain */ + unsigned chain_op_pos = dt.getNumConstructors(); + std::vector<unsigned> elem_pos; + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + Assert(op_pos[i] < dt.getNumConstructors()); + Expr sop = dt[op_pos[i]].getSygusOp(); + /* Collects a chainable operator such as PLUS */ + if (sop.getKind() == BUILTIN + && TransfChain::isChainable(sygus_tn, Node::fromExpr(sop))) + { + Assert(nm->operatorToKind(Node::fromExpr(sop)) == PLUS); + /* TODO #1304: be robust for this case */ + /* For now only transforms applications whose arguments have the same type + * as the root */ + bool same_type_plus = true; + for (const DatatypeConstructorArg& arg : dt[op_pos[i]]) + { + if (TypeNode::fromType( + static_cast<SelectorType>(arg.getType()).getRangeType()) + != tn) + { + same_type_plus = false; + break; + } + } + if (!same_type_plus) + { + Trace("sygus-grammar-normalize-infer") + << "\tFor OP " << PLUS << " did not collecting sop " << sop + << " in position " << op_pos[i] << "\n"; + continue; + } + Assert(chain_op_pos == dt.getNumConstructors()); + Trace("sygus-grammar-normalize-infer") + << "\tCollecting chainable OP " << sop << " in position " << op_pos[i] + << "\n"; + chain_op_pos = op_pos[i]; + continue; + } + /* TODO #1304: check this for each operator */ + /* Collects elements that are not the identity (e.g. 0 is the id of PLUS) */ + if (!TransfChain::isId(sygus_tn, nm->operatorOf(PLUS), Node::fromExpr(sop))) + { + Trace("sygus-grammar-normalize-infer") + << "\tCollecting for NON_ID_ELEMS the sop " << sop + << " in position " << op_pos[i] << "\n"; + elem_pos.push_back(op_pos[i]); + } + } + /* Typenode admits a chain transformation for normalization */ + if (chain_op_pos != dt.getNumConstructors() && !elem_pos.empty()) + { + Trace("sygus-gnorm") << "...chain transf." << std::endl; + Trace("sygus-grammar-normalize-infer") + << "\tInfering chain transformation\n"; + return std::unique_ptr<Transf>(new TransfChain(chain_op_pos, elem_pos)); + } + return nullptr; +} + +TypeNode SygusGrammarNorm::normalizeSygusRec(TypeNode tn, + const Datatype& dt, + std::vector<unsigned>& op_pos) +{ + /* Corresponding type node to tn with the given operator positions. To be + * retrieved (if cached) or defined (otherwise) */ + TypeNode unres_tn; + if (Trace.isOn("sygus-grammar-normalize-trie")) + { + Trace("sygus-grammar-normalize-trie") + << "\tRecursing on " << tn << " with op_positions "; + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize-trie") << op_pos[i] << " "; + } + Trace("sygus-grammar-normalize-trie") << "\n"; + } + /* Checks if unresolved type already created (and returns) or creates it + * (and then proceeds to definition) */ + std::sort(op_pos.begin(), op_pos.end()); + if (d_tries[tn].getOrMakeType(tn, unres_tn, op_pos)) + { + if (Trace.isOn("sygus-grammar-normalize-trie")) + { + Trace("sygus-grammar-normalize-trie") + << "\tTypenode " << tn << " has already been normalized with op_pos "; + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize-trie") << op_pos[i] << " "; + } + Trace("sygus-grammar-normalize-trie") << " with tn " << unres_tn << "\n"; + } + return unres_tn; + } + if (Trace.isOn("sygus-grammar-normalize-trie")) + { + Trace("sygus-grammar-normalize-trie") + << "\tTypenode " << tn << " not yet normalized with op_pos "; + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize-trie") << op_pos[i] << " "; + } + Trace("sygus-grammar-normalize-trie") << "\n"; + } + /* Creates type object for normalization */ + TypeObject to(tn, unres_tn); + + /* Determine normalization transformation based on sygus type and given + * operators */ + std::unique_ptr<Transf> transformation = inferTransf(tn, dt, op_pos); + /* If a transformation was selected, apply it */ + if (transformation != nullptr) + { + transformation->buildType(this, to, dt, op_pos); + } + + /* Remaining operators are rebuilt as they are */ + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + Assert(op_pos[i] < dt.getNumConstructors()); + to.addConsInfo(this, dt[op_pos[i]]); + } + /* Build normalize datatype */ + if (Trace.isOn("sygus-grammar-normalize")) + { + Trace("sygus-grammar-normalize") << "\nFor positions "; + for (unsigned i = 0, size = op_pos.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize") << op_pos[i] << " "; + } + Trace("sygus-grammar-normalize") << " and datatype " << dt << " \n"; + } + to.buildDatatype(this, dt); + return to.d_unres_tn; +} + +TypeNode SygusGrammarNorm::normalizeSygusRec(TypeNode tn) +{ + /* Collect all operators for normalization */ + const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype(); + std::vector<unsigned> op_pos(dt.getNumConstructors()); + std::iota(op_pos.begin(), op_pos.end(), 0); + return normalizeSygusRec(tn, dt, op_pos); +} + +TypeNode SygusGrammarNorm::normalizeSygusType(TypeNode tn, Node sygus_vars) +{ + /* Normalize all types in tn */ + d_sygus_vars = sygus_vars; + normalizeSygusRec(tn); + /* Resolve created types */ + Assert(!d_dt_all.empty() && !d_unres_t_all.empty()); + if (Trace.isOn("sygus-grammar-normalize-build")) + { + Trace("sygus-grammar-normalize-build") + << "making mutual datatyes with datatypes \n"; + for (unsigned i = 0, size = d_dt_all.size(); i < size; ++i) + { + Trace("sygus-grammar-normalize-build") << d_dt_all[i]; + } + Trace("sygus-grammar-normalize-build") << " and unresolved types\n"; + for (const Type& unres_t : d_unres_t_all) + { + Trace("sygus-grammar-normalize-build") << unres_t << " "; + } + Trace("sygus-grammar-normalize-build") << "\n"; + } + Assert(d_dt_all.size() == d_unres_t_all.size()); + std::vector<DatatypeType> types = + NodeManager::currentNM()->toExprManager()->mkMutualDatatypeTypes( + d_dt_all, d_unres_t_all); + Assert(types.size() == d_dt_all.size()); + /* Clear accumulators */ + d_dt_all.clear(); + d_unres_t_all.clear(); + /* By construction the normalized type node will be the last one considered */ + return TypeNode::fromType(types.back()); +} + +} // namespace quantifiers +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/quantifiers/sygus/sygus_grammar_norm.h b/src/theory/quantifiers/sygus/sygus_grammar_norm.h new file mode 100644 index 000000000..f72a83e5a --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_grammar_norm.h @@ -0,0 +1,455 @@ +/********************* */ +/*! \file sygus_grammar_norm.h + ** \verbatim + ** Top contributors (to current version): + ** Haniel Barbosa + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief class for simplifying SyGuS grammars after they are encoded into + ** datatypes. + **/ +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__SYGUS_GRAMMAR_NORM_H +#define __CVC4__THEORY__QUANTIFIERS__SYGUS_GRAMMAR_NORM_H + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "expr/datatype.h" +#include "expr/node.h" +#include "expr/node_manager_attributes.h" // for VarNameAttr +#include "expr/type.h" +#include "expr/type_node.h" +#include "theory/quantifiers/term_util.h" +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +class SygusGrammarNorm; + +/** Operator position trie class + * + * This data structure stores an unresolved type corresponding to the + * normalization of a type. This unresolved type is indexed by the positions of + * the construtors of the datatype associated with the original type. The list + * of positions represent the operators, associated with the respective + * considered constructors, that were used for building the unresolved type. + * + * Example: + * + * Let A be a type defined by the grammar "A -> x | 0 | 1 | A + A". In its + * datatype representation the operator for "x" is in position 0, for "0" in + * position "1" and so on. Consider entries (T, [op_1, ..., op_n]) -> T' to + * represent that a type T is normalized with operators [op_1, ..., op_n] into + * the type T'. For entries + * + * (A, [x, 0, 1, +]) -> A1 + * (A, [x, 1, +]) -> A2 + * (A, [1, +]) -> A3 + * (A, [0]) -> AZ + * (A, [x]) -> AX + * (A, [1]) -> AO + * + * the OpPosTrie T we build for this type is : + * + * T[A] : + * T[A].d_children[0] : AX + * T[A].d_children[0].d_children[1] : + * T[A].d_children[0].d_children[1].d_children[2] : + * T[A].d_children[0].d_children[1].d_children[2].d_children[3] : A1 + * T[A].d_children[0].d_children[2] : + * T[A].d_children[0].d_children[2].d_children[3] : A2 + * T[A].d_children[1] : AZ + * T[A].d_children[2] : AO + * T[A].d_children[2].d_children[4] : A3 + * + * Nodes store the types built for the path of positions up to that point, if + * any. + */ +class OpPosTrie +{ + public: + /** type retrieval/addition + * + * if type indexed by the given operator positions is already in the trie then + * unres_t becomes the indexed type and true is returned. Otherwise a new type + * is created, indexed by the given positions, and assigned to unres_t, with + * false being returned. + */ + bool getOrMakeType(TypeNode tn, + TypeNode& unres_tn, + const std::vector<unsigned>& op_pos, + unsigned ind = 0); + /** clear all data from this trie */ + void clear() { d_children.clear(); } + + private: + /** the data (only set for the final node of an inserted path) */ + TypeNode d_unres_tn; + /* the children of the trie node */ + std::map<unsigned, OpPosTrie> d_children; +}; /* class OpPosTrie */ + +/** Utility for normalizing SyGuS grammars to avoid spurious enumerations + * + * Uses the datatype representation of a SyGuS grammar to identify entries that + * can normalized in order to have less possible enumerations. An example is + * with integer types, e.g.: + * + * Int -> x | y | Int + Int | 0 | 1 | ite(Bool, Int, Int) + * + * becomes + * + * Int0 -> IntZ | Int1 + * IntZ -> 0 + * Int1 -> IntX | IntX + Int1 | Int2 + * IntX -> x + * Int2 -> IntY | IntY + Int2 | Int3 + * IntY -> y + * Int3 -> IntO | IntO + Int3 | Int4 + * IntO -> 1 + * Int4 -> IntITE | IntITE + Int4 + * IntITE -> ite(Bool, Int0, Int0) + * + * TODO: #1304 normalize more complex grammars + * + * This class also performs more straightforward normalizations, such as + * expanding definitions of functions declared with a "define-fun" command. + * These lighweight transformations are always applied, independently of the + * normalization option being enabled. + */ +class SygusGrammarNorm +{ + public: + SygusGrammarNorm(QuantifiersEngine* qe) + : d_qe(qe), d_tds(d_qe->getTermDatabaseSygus()) + { + } + ~SygusGrammarNorm() {} + /** creates a normalized typenode from a given one. + * + * In a normalized typenode all typenodes it contains are normalized. + * Normalized typenodes can be structurally identicial to their original + * counterparts. + * + * sygus_vars are the input variables for the function to be synthesized, + * which are used as input for the built datatypes. + * + * This is the function that will resolve all types and datatypes built during + * normalization. This operation can only be performed after all types + * contained in "tn" have been normalized, since the resolution of datatypes + * depends on all types involved being defined. + */ + TypeNode normalizeSygusType(TypeNode tn, Node sygus_vars); + + /* Retrives, or, if none, creates, stores and returns, the node for the + * identity operator (\lambda x. x) for the given type node */ + static inline Node getIdOp(TypeNode tn) + { + auto it = d_tn_to_id.find(tn); + if (it == d_tn_to_id.end()) + { + std::vector<Node> vars = {NodeManager::currentNM()->mkBoundVar(tn)}; + Node n = NodeManager::currentNM()->mkNode( + kind::LAMBDA, + NodeManager::currentNM()->mkNode(kind::BOUND_VAR_LIST, vars), + vars.back()); + d_tn_to_id[tn] = n; + return n; + } + return it->second; + } + + private: + /** Keeps the necessary information for bulding a normalized type: + * + * the original typenode, from which the datatype representation can be + * extracted + * + * the operators, names, print callbacks and list of argument types for each + * constructor + * + * the unresolved type node used as placeholder for references of the yet to + * be built normalized type + * + * a datatype to represent the structure of the type node for the normalized + * type + */ + class TypeObject + { + public: + /* Stores the original type node and the unresolved placeholder. The + * datatype for the latter is created with the respective name. */ + TypeObject(TypeNode src_tn, TypeNode unres_tn) + : d_tn(src_tn), + d_unres_tn(unres_tn), + d_dt(Datatype(unres_tn.getAttribute(expr::VarNameAttr()))) + { + } + ~TypeObject() {} + + /** adds information in "cons" (operator, name, print callback, argument + * types) as it is into "to" + * + * A side effect of this procedure is to expand the definitions in the sygus + * operator of "cons" + * + * The types of the arguments of "cons" are recursively normalized + */ + void addConsInfo(SygusGrammarNorm* sygus_norm, + const DatatypeConstructor& cons); + + /** builds a datatype with the information in the type object + * + * "dt" is the datatype of the original typenode. It is necessary for + * retrieving ancillary information during the datatype building, such as + * its sygus type (e.g. Int) + * + * The built datatype and its unresolved type are saved in the global + * accumulators of "sygus_norm" + */ + void buildDatatype(SygusGrammarNorm* sygus_norm, const Datatype& dt); + + //---------- information stored from original type node + + /* The original typenode this TypeObject is built from */ + TypeNode d_tn; + + //---------- information to build normalized type node + + /* Operators for each constructor. */ + std::vector<Node> d_ops; + /* Names for each constructor. */ + std::vector<std::string> d_cons_names; + /* Print callbacks for each constructor */ + std::vector<std::shared_ptr<SygusPrintCallback>> d_pc; + /* Weights for each constructor */ + std::vector<int> d_weight; + /* List of argument types for each constructor */ + std::vector<std::vector<Type>> d_cons_args_t; + /* Unresolved type node placeholder */ + TypeNode d_unres_tn; + /* Datatype to represent type's structure */ + Datatype d_dt; + }; /* class TypeObject */ + + /** Transformation abstract class + * + * Classes extending this one will define specif transformationst for building + * normalized types based on applications of specific operators + */ + class Transf + { + public: + virtual ~Transf() {} + + /** abstract function for building normalized types + * + * Builds normalized types for the operators specifed by the positions in + * op_pos of constructors from dt. The built types are associated with the + * given type object and accumulated in the sygus_norm object, whose + * utilities for any extra necessary normalization. + */ + virtual void buildType(SygusGrammarNorm* sygus_norm, + TypeObject& to, + const Datatype& dt, + std::vector<unsigned>& op_pos) = 0; + }; /* class Transf */ + + /** Drop transformation class + * + * This class builds a type by dropping a set of redundant constructors, + * whose indices are given as input to the constructor of this class. + */ + class TransfDrop : public Transf + { + public: + TransfDrop(const std::vector<unsigned>& indices) : d_drop_indices(indices) + { + } + /** build type */ + void buildType(SygusGrammarNorm* sygus_norm, + TypeObject& to, + const Datatype& dt, + std::vector<unsigned>& op_pos) override; + + private: + std::vector<unsigned> d_drop_indices; + }; + + /** Chain transformation class + * + * Determines how to build normalized types by chaining the application of one + * of its operators. The resulting type should admit the same terms as the + * previous one modulo commutativity, associativity and identity of the + * neutral element. + * + * TODO: #1304: + * - define this transformation for more than just PLUS for Int. + * - improve the building such that elements that should not be entitled a + * "link in the chain" (such as 5 in opposition to variables and 1) do not get + * one + * - consider the case when operator is applied to different types, e.g.: + * A -> B + B | x; B -> 0 | 1 + * - consider the case in which in which the operator occurs nested in an + * argument type of itself, e.g.: + * A -> (B + B) + B | x; B -> 0 | 1 + */ + class TransfChain : public Transf + { + public: + TransfChain(unsigned chain_op_pos, const std::vector<unsigned>& elem_pos) + : d_chain_op_pos(chain_op_pos), d_elem_pos(elem_pos){}; + + /** builds types encoding a chain in which each link contains a repetition + * of the application of the chain operator over a non-identity element + * + * Example: when considering, over the integers, the operator "+" and the + * elemenst "1", "x" and "y", the built chain is e.g. + * + * x + ... + x + y + ... + y + 1 + ...+ 1 + * + * whose encoding in types would be e.g. + * + * A -> AX | AX + A | B + * AX -> x + * B -> BY | BY + B | C + * BY -> y + * C -> C1 | C1 + C + * C1 -> 1 + * + * ++++++++ + * + * The types composing links in the chain are built recursively by invoking + * sygus_norm, which caches results and handles the global normalization, on + * the operators not used in a given link, which will lead to recalling this + * transformation and so on until all operators originally given are + * considered. + */ + void buildType(SygusGrammarNorm* sygus_norm, + TypeObject& to, + const Datatype& dt, + std::vector<unsigned>& op_pos) override; + + /** Whether operator is chainable for the type (e.g. PLUS for Int) + * + * Since the map this function depends on cannot be built statically, this + * function first build maps the first time a type is checked. As a + * consequence the list of chainabel operators is hardcoded in the map + * building. + * + * TODO: #1304: Cover more types and operators, make this robust to more + * complex grammars + */ + static bool isChainable(TypeNode tn, Node op); + /* Whether n is the identity for the chain operator of the type (e.g. 1 is + * not the identity 0 for PLUS for Int) + * + * TODO: #1304: Cover more types, make this robust to more complex grammars + */ + static bool isId(TypeNode tn, Node op, Node n); + + private: + /* TODO #1304: this should admit more than one, as well as which elements + * are associated with which operator */ + /* Position of chain operator */ + unsigned d_chain_op_pos; + /* Positions (of constructors in the datatype whose type is being + * normalized) of elements the chain operator is applied to */ + std::vector<unsigned> d_elem_pos; + /** Specifies for each type node which are its chainable operators + * + * For example, for Int the map is {OP -> [+]} + * + * TODO #1304: consider more operators + */ + static std::map<TypeNode, std::vector<Kind>> d_chain_ops; + /** Specifies for each type node and chainable operator its identity + * + * For example, for Int and PLUS the map is {Int -> {+ -> 0}} + * + * TODO #1304: consider more operators + */ + static std::map<TypeNode, std::map<Kind, Node>> d_chain_op_id; + + }; /* class TransfChain */ + + /** reference to quantifier engine */ + QuantifiersEngine* d_qe; + /** sygus term database associated with this utility */ + TermDbSygus* d_tds; + /** List of variable inputs of function-to-synthesize. + * + * This information is needed in the construction of each datatype + * representation of type nodes contained in the type node being normalized + */ + TNode d_sygus_vars; + /* Datatypes to be resolved */ + std::vector<Datatype> d_dt_all; + /* Types to be resolved */ + std::set<Type> d_unres_t_all; + /* Associates type nodes with OpPosTries */ + std::map<TypeNode, OpPosTrie> d_tries; + /* Map of type nodes into their identity operators (\lambda x. x) */ + static std::map<TypeNode, Node> d_tn_to_id; + + /** recursively traverses a typenode normalizing all of its elements + * + * "tn" is the typenode to be normalized + * "dt" is its datatype representation + * "op_pos" is the list of positions of construtors of dt that are being + * considered for the normalization + * + * The result of normalizing tn with the respective constructors is cached + * with an OpPosTrie. New types and datatypes created during normalization are + * accumulated grobally to be later resolved. + * + * The normalization occurs following some inferred transformation based on + * the sygus type (e.g. Int) of tn, and the operators being considered. + * + * Example: Let A be the type node encoding the grammar + * + * Int -> x | y | Int + Int | 0 | 1 | ite(Bool, Int, Int) + * + * and assume all its datatype constructors are being used for + * normalization. The inferred normalization transformation will consider the + * non-zero elements {x, y, 1, ite(...)} and the operator {+} to build a chain + * of monomials, as seen above. The operator for "0" is rebuilt as is (the + * default behaviour of operators not selected for transformations). + * + * recursion depth is limited by the height of the types, which is small + */ + TypeNode normalizeSygusRec(TypeNode tn, + const Datatype& dt, + std::vector<unsigned>& op_pos); + + /** wrapper for the above function + * + * invoked when all operators of "tn" are to be considered for normalization + */ + TypeNode normalizeSygusRec(TypeNode tn); + + /** infers a transformation for normalizing dt when allowed to use the + * operators in the positions op_pos. + * + * TODO: #1304: Infer more complex transformations + */ + std::unique_ptr<Transf> inferTransf(TypeNode tn, + const Datatype& dt, + const std::vector<unsigned>& op_pos); +}; /* class SygusGrammarNorm */ + +} // namespace quantifiers +} // namespace theory +} // namespace CVC4 + +#endif diff --git a/src/theory/quantifiers/sygus/sygus_grammar_red.cpp b/src/theory/quantifiers/sygus/sygus_grammar_red.cpp new file mode 100644 index 000000000..939788e4d --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_grammar_red.cpp @@ -0,0 +1,136 @@ +/********************* */ +/*! \file sygus_grammar_red.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief Implementation of sygus_grammar_red + **/ + +#include "theory/quantifiers/sygus/sygus_grammar_red.h" + +#include "options/quantifiers_options.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_util.h" + +using namespace std; +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +void SygusRedundantCons::initialize(QuantifiersEngine* qe, TypeNode tn) +{ + Assert(qe != nullptr); + Trace("sygus-red") << "Compute redundant cons for " << tn << std::endl; + d_type = tn; + Assert(tn.isDatatype()); + TermDbSygus* tds = qe->getTermDatabaseSygus(); + tds->registerSygusType(tn); + const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype(); + Assert(dt.isSygus()); + TypeNode btn = TypeNode::fromType(dt.getSygusType()); + for (unsigned i = 0, ncons = dt.getNumConstructors(); i < ncons; i++) + { + Trace("sygus-red") << " Is " << dt[i].getName() << " a redundant operator?" + << std::endl; + std::map<int, Node> pre; + Node g = tds->mkGeneric(dt, i, pre); + Trace("sygus-red-debug") << " ...pre-rewrite : " << g << std::endl; + Assert(g.getNumChildren() == dt[i].getNumArgs()); + d_gen_terms[i] = g; + for (unsigned j = 0, nargs = dt[i].getNumArgs(); j < nargs; j++) + { + pre[j] = g[j]; + } + std::vector<Node> glist; + getGenericList(tds, dt, i, 0, pre, glist); + // call the extended rewriter + bool red = false; + for (const Node& gr : glist) + { + Trace("sygus-red-debug") << " ...variant : " << gr << std::endl; + std::map<Node, unsigned>::iterator itg = d_gen_cons.find(gr); + if (itg != d_gen_cons.end() && itg->second != i) + { + red = true; + Trace("sygus-red") << " ......redundant, since a variant of " << g + << " and " << d_gen_terms[itg->second] + << " both rewrite to " << gr << std::endl; + break; + } + else + { + d_gen_cons[gr] = i; + Trace("sygus-red") << " ......not redundant." << std::endl; + } + } + d_sygus_red_status.push_back(red ? 1 : 0); + } +} + +void SygusRedundantCons::getRedundant(std::vector<unsigned>& indices) +{ + const Datatype& dt = static_cast<DatatypeType>(d_type.toType()).getDatatype(); + for (unsigned i = 0, ncons = dt.getNumConstructors(); i < ncons; i++) + { + if (isRedundant(i)) + { + indices.push_back(i); + } + } +} + +bool SygusRedundantCons::isRedundant(unsigned i) +{ + Assert(i < d_sygus_red_status.size()); + return d_sygus_red_status[i] == 1; +} + +void SygusRedundantCons::getGenericList(TermDbSygus* tds, + const Datatype& dt, + unsigned c, + unsigned index, + std::map<int, Node>& pre, + std::vector<Node>& terms) +{ + if (index == dt[c].getNumArgs()) + { + Node gt = tds->mkGeneric(dt, c, pre); + gt = tds->getExtRewriter()->extendedRewrite(gt); + terms.push_back(gt); + return; + } + // with no swap + getGenericList(tds, dt, c, index + 1, pre, terms); + // swapping is exponential, only use for operators with small # args. + if (dt[c].getNumArgs() <= 5) + { + TypeNode atype = tds->getArgType(dt[c], index); + for (unsigned s = index + 1, nargs = dt[c].getNumArgs(); s < nargs; s++) + { + if (tds->getArgType(dt[c], s) == atype) + { + // swap s and index + Node tmp = pre[s]; + pre[s] = pre[index]; + pre[index] = tmp; + getGenericList(tds, dt, c, index + 1, pre, terms); + // revert + tmp = pre[s]; + pre[s] = pre[index]; + pre[index] = tmp; + } + } + } +} + +} /* CVC4::theory::quantifiers namespace */ +} /* CVC4::theory namespace */ +} /* CVC4 namespace */ diff --git a/src/theory/quantifiers/sygus/sygus_grammar_red.h b/src/theory/quantifiers/sygus/sygus_grammar_red.h new file mode 100644 index 000000000..d0484aa57 --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_grammar_red.h @@ -0,0 +1,119 @@ +/********************* */ +/*! \file sygus_grammar_red.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief sygus_grammar_red + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__SYGUS_GRAMMAR_RED_H +#define __CVC4__THEORY__QUANTIFIERS__SYGUS_GRAMMAR_RED_H + +#include <map> +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +/** SygusRedundantCons + * + * This class computes the subset of indices of the constructors of a sygus type + * that are redundant. To use this class, first call initialize( qe, tn ), + * where tn is a sygus tn. Then, use getRedundant and/or isRedundant to get the + * indicies of the constructors of tn that are redundant. + */ +class SygusRedundantCons +{ + public: + SygusRedundantCons() {} + ~SygusRedundantCons() {} + /** register type tn + * + * qe : pointer to the quantifiers engine, + * tn : the (sygus) type to compute redundant constructors for + */ + void initialize(QuantifiersEngine* qe, TypeNode tn); + /** Get the indices of the redundant constructors of the register type */ + void getRedundant(std::vector<unsigned>& indices); + /** + * This function returns true if the i^th constructor of the registered type + * is redundant. + */ + bool isRedundant(unsigned i); + + private: + /** the registered type */ + TypeNode d_type; + /** redundant status + * + * For each constructor, status indicating whether the constructor is + * redundant, where: + * + * 0 : not redundant, + * 1 : redundant since another constructor can be used to construct values for + * this constructor. + * + * For example, for grammar: + * A -> C > B | B < C | not D + * B -> x | y + * C -> 0 | 1 | C+C + * D -> B >= C + * If A is register with this class, then we store may store { 0, 1, 0 }, + * noting that the second constructor of A can be simulated with the first. + * Notice that the third constructor is not considered redundant. + */ + std::vector<int> d_sygus_red_status; + /** + * Map from constructor indices to the generic term for that constructor, + * where the generic term for a constructor is the (canonical) term returned + * by a call to TermDbSygus::mkGeneric. + */ + std::map<unsigned, Node> d_gen_terms; + /** + * Map from the rewritten form of generic terms for constructors of the + * registered type to their corresponding constructor index. + */ + std::map<Node, unsigned> d_gen_cons; + /** get generic list + * + * This function constructs all well-typed variants of a term of the form + * op( x1, ..., xn ) + * where op is the builtin operator for dt[c], and xi = pre[i] for i=1,...,n. + * + * It constructs a list of terms of the form g * sigma, where sigma + * is an automorphism on { x1...xn } such that for all xi -> xj in sigma, + * the type for arguments i and j of dt[c] are the same. We store this + * list of terms in terms. + * + * This function recurses on the arguments of g, index is the current argument + * we are processing, and pre stores the current arguments of + * + * For example, for a sygus grammar + * A -> and( A, A, B ) + * B -> false + * passing arguments such that g=and( x1, x2, x3 ) to this function will add: + * and( x1, x2, x3 ) and and( x2, x1, x3 ) + * to terms. + */ + void getGenericList(TermDbSygus* tds, + const Datatype& dt, + unsigned c, + unsigned index, + std::map<int, Node>& pre, + std::vector<Node>& terms); +}; + +} /* CVC4::theory::quantifiers namespace */ +} /* CVC4::theory namespace */ +} /* CVC4 namespace */ + +#endif /* __CVC4__THEORY__QUANTIFIERS__SYGUS_GRAMMAR_RED_H */ diff --git a/src/theory/quantifiers/sygus/sygus_invariance.cpp b/src/theory/quantifiers/sygus/sygus_invariance.cpp new file mode 100644 index 000000000..6b4c6488d --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_invariance.cpp @@ -0,0 +1,229 @@ +/********************* */ +/*! \file sygus_invariance.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief Implementation of techniques for sygus invariance tests. + **/ + +#include "theory/quantifiers/sygus/sygus_invariance.h" + +#include "theory/quantifiers/sygus/ce_guided_conjecture.h" +#include "theory/quantifiers/sygus/sygus_pbe.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" + +using namespace CVC4::kind; +using namespace std; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +void EvalSygusInvarianceTest::init(Node conj, Node var, Node res) +{ + d_conj = conj; + d_var = var; + d_result = res; +} + +Node EvalSygusInvarianceTest::doEvaluateWithUnfolding(TermDbSygus* tds, Node n) +{ + return tds->evaluateWithUnfolding(n, d_visited); +} + +bool EvalSygusInvarianceTest::invariant(TermDbSygus* tds, Node nvn, Node x) +{ + TNode tnvn = nvn; + Node conj_subs = d_conj.substitute(d_var, tnvn); + Node conj_subs_unfold = doEvaluateWithUnfolding(tds, conj_subs); + Trace("sygus-cref-eval2-debug") + << " ...check unfolding : " << conj_subs_unfold << std::endl; + Trace("sygus-cref-eval2-debug") << " ......from : " << conj_subs + << std::endl; + if (conj_subs_unfold == d_result) + { + Trace("sygus-cref-eval2") << "Evaluation min explain : " << conj_subs + << " still evaluates to " << d_result + << " regardless of "; + Trace("sygus-cref-eval2") << x << std::endl; + return true; + } + return false; +} + +void EquivSygusInvarianceTest::init( + TermDbSygus* tds, TypeNode tn, CegConjecture* aconj, Node e, Node bvr) +{ + // compute the current examples + d_bvr = bvr; + if (aconj->getPbe()->hasExamples(e)) + { + d_conj = aconj; + d_enum = e; + unsigned nex = aconj->getPbe()->getNumExamples(e); + for (unsigned i = 0; i < nex; i++) + { + d_exo.push_back(d_conj->getPbe()->evaluateBuiltin(tn, bvr, e, i)); + } + } +} + +bool EquivSygusInvarianceTest::invariant(TermDbSygus* tds, Node nvn, Node x) +{ + TypeNode tn = nvn.getType(); + Node nbv = tds->sygusToBuiltin(nvn, tn); + Node nbvr = tds->getExtRewriter()->extendedRewrite(nbv); + Trace("sygus-sb-mexp-debug") << " min-exp check : " << nbv << " -> " << nbvr + << std::endl; + bool exc_arg = false; + // equivalent / singular up to normalization + if (nbvr == d_bvr) + { + // gives the same result : then the explanation for the child is irrelevant + exc_arg = true; + Trace("sygus-sb-mexp") << "sb-min-exp : " << tds->sygusToBuiltin(nvn) + << " is rewritten to " << nbvr; + Trace("sygus-sb-mexp") << " regardless of the content of " + << tds->sygusToBuiltin(x) << std::endl; + } + else + { + if (nbvr.isVar()) + { + TypeNode xtn = x.getType(); + if (xtn == tn) + { + Node bx = tds->sygusToBuiltin(x, xtn); + Assert(bx.getType() == nbvr.getType()); + if (nbvr == bx) + { + Trace("sygus-sb-mexp") << "sb-min-exp : " << tds->sygusToBuiltin(nvn) + << " always rewrites to argument " << nbvr + << std::endl; + // rewrites to the variable : then the explanation of this is + // irrelevant as well + exc_arg = true; + d_bvr = nbvr; + } + } + } + } + // equivalent under examples + if (!exc_arg) + { + if (!d_enum.isNull()) + { + bool ex_equiv = true; + for (unsigned j = 0; j < d_exo.size(); j++) + { + Node nbvr_ex = d_conj->getPbe()->evaluateBuiltin(tn, nbvr, d_enum, j); + if (nbvr_ex != d_exo[j]) + { + ex_equiv = false; + break; + } + } + if (ex_equiv) + { + Trace("sygus-sb-mexp") << "sb-min-exp : " << tds->sygusToBuiltin(nvn); + Trace("sygus-sb-mexp") + << " is the same w.r.t. examples regardless of the content of " + << tds->sygusToBuiltin(x) << std::endl; + exc_arg = true; + } + } + } + return exc_arg; +} + +bool DivByZeroSygusInvarianceTest::invariant(TermDbSygus* tds, Node nvn, Node x) +{ + TypeNode tn = nvn.getType(); + Node nbv = tds->sygusToBuiltin(nvn, tn); + Node nbvr = tds->getExtRewriter()->extendedRewrite(nbv); + if (tds->involvesDivByZero(nbvr)) + { + Trace("sygus-sb-mexp") << "sb-min-exp : " << tds->sygusToBuiltin(nvn) + << " involves div-by-zero regardless of " + << tds->sygusToBuiltin(x) << std::endl; + return true; + } + return false; +} + +void NegContainsSygusInvarianceTest::init(CegConjecture* conj, + Node e, + std::vector<Node>& exo, + std::vector<unsigned>& ncind) +{ + if (conj->getPbe()->hasExamples(e)) + { + Assert(conj->getPbe()->getNumExamples(e) == exo.size()); + d_enum = e; + d_exo.insert(d_exo.end(), exo.begin(), exo.end()); + d_neg_con_indices.insert( + d_neg_con_indices.end(), ncind.begin(), ncind.end()); + d_conj = conj; + } +} + +bool NegContainsSygusInvarianceTest::invariant(TermDbSygus* tds, + Node nvn, + Node x) +{ + if (!d_enum.isNull()) + { + TypeNode tn = nvn.getType(); + Node nbv = tds->sygusToBuiltin(nvn, tn); + Node nbvr = tds->getExtRewriter()->extendedRewrite(nbv); + // if for any of the examples, it is not contained, then we can exclude + for (unsigned i = 0; i < d_neg_con_indices.size(); i++) + { + unsigned ii = d_neg_con_indices[i]; + Assert(ii < d_exo.size()); + Node nbvre = d_conj->getPbe()->evaluateBuiltin(tn, nbvr, d_enum, ii); + Node out = d_exo[ii]; + Node cont = + NodeManager::currentNM()->mkNode(kind::STRING_STRCTN, out, nbvre); + Trace("sygus-pbe-cterm-debug") << "Check: " << cont << std::endl; + Node contr = Rewriter::rewrite(cont); + if (contr == tds->d_false) + { + if (Trace.isOn("sygus-pbe-cterm")) + { + Trace("sygus-pbe-cterm") + << "PBE-cterm : enumerator : do not consider "; + Trace("sygus-pbe-cterm") << nbv << " for any " + << tds->sygusToBuiltin(x) << " since " + << std::endl; + Trace("sygus-pbe-cterm") << " PBE-cterm : for input example : "; + std::vector<Node> ex; + d_conj->getPbe()->getExample(d_enum, ii, ex); + for (unsigned j = 0; j < ex.size(); j++) + { + Trace("sygus-pbe-cterm") << ex[j] << " "; + } + Trace("sygus-pbe-cterm") << std::endl; + Trace("sygus-pbe-cterm") + << " PBE-cterm : this rewrites to : " << nbvre << std::endl; + Trace("sygus-pbe-cterm") + << " PBE-cterm : and is not in output : " << out << std::endl; + } + return true; + } + Trace("sygus-pbe-cterm-debug2") + << "...check failed, rewrites to : " << contr << std::endl; + } + } + return false; +} + +} /* CVC4::theory::quantifiers namespace */ +} /* CVC4::theory namespace */ +} /* CVC4 namespace */ diff --git a/src/theory/quantifiers/sygus/sygus_invariance.h b/src/theory/quantifiers/sygus/sygus_invariance.h new file mode 100644 index 000000000..a43e38719 --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_invariance.h @@ -0,0 +1,276 @@ +/********************* */ +/*! \file sygus_invariance.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief sygus invariance tests + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__SYGUS_INVARIANCE_H +#define __CVC4__THEORY__QUANTIFIERS__SYGUS_INVARIANCE_H + +#include <unordered_map> +#include <vector> + +#include "expr/node.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +class TermDbSygus; +class CegConjecture; + +/* SygusInvarianceTest +* +* This class is the standard interface for term generalization +* in SyGuS. Its interface is a single function is_variant, +* which is a virtual condition for SyGuS terms. +* +* The common use case of invariance tests is when constructing +* minimal explanations for refinement lemmas in the +* counterexample-guided inductive synthesis (CEGIS) loop. +* See sygus_explain.h for more details. +*/ +class SygusInvarianceTest +{ + public: + virtual ~SygusInvarianceTest() {} + + /** Is nvn invariant with respect to this test ? + * + * - nvn is the term to check whether it is invariant. + * - x is a variable such that the previous call to + * is_invariant (if any) was with term nvn_prev, and + * nvn is equal to nvn_prev with some subterm + * position replaced by x. This is typically used + * for debugging only. + */ + bool is_invariant(TermDbSygus* tds, Node nvn, Node x) + { + if (invariant(tds, nvn, x)) + { + d_update_nvn = nvn; + return true; + } + return false; + } + /** get updated term */ + Node getUpdatedTerm() { return d_update_nvn; } + /** set updated term */ + void setUpdatedTerm(Node n) { d_update_nvn = n; } + protected: + /** result of the node that satisfies this invariant */ + Node d_update_nvn; + /** check whether nvn[ x ] is invariant */ + virtual bool invariant(TermDbSygus* tds, Node nvn, Node x) = 0; +}; + +/** EquivSygusInvarianceTest +* +* This class tests whether a term evaluates via evaluation +* operators in the deep embedding (Section 4 of Reynolds +* et al. CAV 2015) to fixed term d_result. +* +* For example, consider a SyGuS evaluation function eval +* for a synthesis conjecture with arguments x and y. +* Notice that the term t = (mult x y) is such that: +* eval( t, 0, 1 ) ----> 0 +* This test is invariant on the content of the second +* argument of t, noting that: +* eval( (mult x _), 0, 1 ) ----> 0 +* as well, via a call to EvalSygusInvarianceTest::invariant. +* +* Another example, t = ite( gt( x, y ), x, y ) is such that: +* eval( t, 2, 3 ) ----> 3 +* This test is invariant on the second child of t, noting: +* eval( ite( gt( x, y ), _, y ), 2, 3 ) ----> 3 +*/ +class EvalSygusInvarianceTest : public SygusInvarianceTest +{ + public: + EvalSygusInvarianceTest() {} + + /** initialize this invariance test + * This sets d_conj/d_var/d_result, where + * we are checking whether: + * d_conj { d_var -> n } ----> d_result. + * for terms n. + */ + void init(Node conj, Node var, Node res); + + /** do evaluate with unfolding, using the cache of this class */ + Node doEvaluateWithUnfolding(TermDbSygus* tds, Node n); + + protected: + /** does d_conj{ d_var -> nvn } still rewrite to d_result? */ + bool invariant(TermDbSygus* tds, Node nvn, Node x); + + private: + /** the formula we are evaluating */ + Node d_conj; + /** the variable */ + TNode d_var; + /** the result of the evaluation */ + Node d_result; + /** cache of n -> the simplified form of eval( n ) */ + std::unordered_map<Node, Node, NodeHashFunction> d_visited; +}; + +/** EquivSygusInvarianceTest +* +* This class tests whether a builtin version of a +* sygus term is equivalent up to rewriting to a RHS value bvr. +* +* For example, +* +* ite( t>0, 0, 0 ) + s*0 ----> 0 +* +* This test is invariant on the condition t>0 and s, since: +* +* ite( _, 0, 0 ) + _*0 ----> 0 +* +* for any values of _. +* +* It also manages the case where the rewriting is invariant +* wrt a finite set of examples occurring in the conjecture. +* (EX1) : For example if our input examples are: +* (x,y,z) = (3,2,4), (5,2,6), (3,2,1) +* On these examples, we have: +* +* ite( x>y, z, 0) ---> 4,6,1 +* +* which is invariant on the second argument: +* +* ite( x>y, z, _) ---> 4,6,1 +* +* For details, see Reynolds et al SYNT 2017. +*/ +class EquivSygusInvarianceTest : public SygusInvarianceTest +{ + public: + EquivSygusInvarianceTest() : d_conj(nullptr) {} + + /** initialize this invariance test + * tn is the sygus type for e + * aconj/e are used for conjecture-specific symmetry breaking + * bvr is the builtin version of the right hand side of the rewrite that we + * are checking for invariance + */ + void init( + TermDbSygus* tds, TypeNode tn, CegConjecture* aconj, Node e, Node bvr); + + protected: + /** checks whether the analog of nvn still rewrites to d_bvr */ + bool invariant(TermDbSygus* tds, Node nvn, Node x); + + private: + /** the conjecture associated with the enumerator d_enum */ + CegConjecture* d_conj; + /** the enumerator associated with the term for which this test is for */ + Node d_enum; + /** the RHS of the evaluation */ + Node d_bvr; + /** the result of the examples + * In (EX1), this is (4,6,1) + */ + std::vector<Node> d_exo; +}; + +/** DivByZeroSygusInvarianceTest + * + * This class tests whether a sygus term involves + * division by zero. + * + * For example the test for: + * ( x + ( y/0 )*2 ) + * is invariant on the contents of _ below: + * ( _ + ( _/0 )*_ ) + */ +class DivByZeroSygusInvarianceTest : public SygusInvarianceTest +{ + public: + DivByZeroSygusInvarianceTest() {} + + protected: + /** checks whether nvn involves division by zero. */ + bool invariant(TermDbSygus* tds, Node nvn, Node x); +}; + +/** NegContainsSygusInvarianceTest +* +* This class is used to construct a minimal shape of a term that cannot +* be contained in at least one output of an I/O pair. +* +* Say our PBE conjecture is: +* +* exists f. +* f( "abc" ) = "abc abc" ^ +* f( "de" ) = "de de" +* +* Then, this class is used when there is a candidate solution t[x1] +* such that either: +* contains( "abc abc", t["abc"] ) ---> false or +* contains( "de de", t["de"] ) ---> false +* +* It is used to determine whether certain generalizations of t[x1] +* are still sufficient to falsify one of the above containments. +* +* For example: +* +* The test for str.++( x1, "d" ) is invariant on its first argument +* ...since contains( "abc abc", str.++( _, "d" ) ) ---> false +* The test for str.replace( "de", x1, "b" ) is invariant on its third argument +* ...since contains( "abc abc", str.replace( "de", "abc", _ ) ) ---> false +*/ +class NegContainsSygusInvarianceTest : public SygusInvarianceTest +{ + public: + NegContainsSygusInvarianceTest() : d_conj(nullptr) {} + + /** initialize this invariance test + * cpbe is the conjecture utility. + * e is the enumerator which we are reasoning about (associated with a synth + * fun). + * exo is the list of outputs of the PBE conjecture. + * ncind is the set of possible indices of the PBE conjecture to check + * invariance of non-containment. + * For example, in the above example, when t[x1] = "ab", then this + * has the index 1 since contains("de de", "ab") ---> false but not + * the index 0 since contains("abc abc","ab") ---> true. + */ + void init(CegConjecture* conj, + Node e, + std::vector<Node>& exo, + std::vector<unsigned>& ncind); + + protected: + /** checks if contains( out_i, nvn[in_i] ) --> false for some I/O pair i. */ + bool invariant(TermDbSygus* tds, Node nvn, Node x); + + private: + /** The enumerator whose value we are considering in this invariance test */ + Node d_enum; + /** The output examples for the enumerator */ + std::vector<Node> d_exo; + /** The set of I/O pair indices i such that + * contains( out_i, nvn[in_i] ) ---> false + */ + std::vector<unsigned> d_neg_con_indices; + /** reference to the conjecture associated with this test */ + CegConjecture* d_conj; +}; + +} /* CVC4::theory::quantifiers namespace */ +} /* CVC4::theory namespace */ +} /* CVC4 namespace */ + +#endif /* __CVC4__THEORY__QUANTIFIERS__SYGUS_INVARIANCE_H */ diff --git a/src/theory/quantifiers/sygus/sygus_pbe.cpp b/src/theory/quantifiers/sygus/sygus_pbe.cpp new file mode 100644 index 000000000..17c4c482d --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_pbe.cpp @@ -0,0 +1,2460 @@ +/********************* */ +/*! \file ce_guided_pbe.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2016 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief utility for processing programming by examples synthesis conjectures + ** + **/ +#include "theory/quantifiers/sygus/sygus_pbe.h" + +#include "expr/datatype.h" +#include "options/quantifiers_options.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_util.h" +#include "theory/datatypes/datatypes_rewriter.h" +#include "util/random.h" + +using namespace CVC4; +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +void indent( const char * c, int ind ) { + if( Trace.isOn(c) ){ + for( int i=0; i<ind; i++ ){ + Trace(c) << " "; + } + } +} + +void print_val( const char * c, std::vector< Node >& vals, bool pol = true ){ + if( Trace.isOn(c) ){ + for( unsigned i=0; i<vals.size(); i++ ){ + //Trace(c) << ( pol ? vals[i] : !vals[i] ); + Trace(c) << ( ( pol ? vals[i].getConst<bool>() : !vals[i].getConst<bool>() ) ? "1" : "0" ); + } + } +} + +std::ostream& operator<<(std::ostream& os, EnumRole r) +{ + switch(r){ + case enum_invalid: os << "INVALID"; break; + case enum_io: os << "IO"; break; + case enum_ite_condition: os << "CONDITION"; break; + case enum_concat_term: os << "CTERM"; break; + default: os << "enum_" << static_cast<unsigned>(r); break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, NodeRole r) +{ + switch (r) + { + case role_equal: os << "equal"; break; + case role_string_prefix: os << "string_prefix"; break; + case role_string_suffix: os << "string_suffix"; break; + case role_ite_condition: os << "ite_condition"; break; + default: os << "role_" << static_cast<unsigned>(r); break; + } + return os; +} + +EnumRole getEnumeratorRoleForNodeRole(NodeRole r) +{ + switch (r) + { + case role_equal: return enum_io; break; + case role_string_prefix: return enum_concat_term; break; + case role_string_suffix: return enum_concat_term; break; + case role_ite_condition: return enum_ite_condition; break; + default: break; + } + return enum_invalid; +} + +std::ostream& operator<<(std::ostream& os, StrategyType st) +{ + switch (st) + { + case strat_ITE: os << "ITE"; break; + case strat_CONCAT_PREFIX: os << "CONCAT_PREFIX"; break; + case strat_CONCAT_SUFFIX: os << "CONCAT_SUFFIX"; break; + case strat_ID: os << "ID"; break; + default: os << "strat_" << static_cast<unsigned>(st); break; + } + return os; +} + +CegConjecturePbe::CegConjecturePbe(QuantifiersEngine* qe, CegConjecture* p) + : d_qe(qe), + d_parent(p){ + d_tds = d_qe->getTermDatabaseSygus(); + d_true = NodeManager::currentNM()->mkConst(true); + d_false = NodeManager::currentNM()->mkConst(false); + d_is_pbe = false; +} + +CegConjecturePbe::~CegConjecturePbe() { + +} + +//--------------------------------- collecting finite input/output domain information + +void CegConjecturePbe::collectExamples( Node n, std::map< Node, bool >& visited, bool hasPol, bool pol ) { + if( visited.find( n )==visited.end() ){ + visited[n] = true; + Node neval; + Node n_output; + if( n.getKind()==APPLY_UF && n.getNumChildren()>0 ){ + neval = n; + if( hasPol ){ + n_output = !pol ? d_true : d_false; + } + }else if( n.getKind()==EQUAL && hasPol && !pol ){ + for( unsigned r=0; r<2; r++ ){ + if( n[r].getKind()==APPLY_UF && n[r].getNumChildren()>0 ){ + neval = n[r]; + if( n[1-r].isConst() ){ + n_output = n[1-r]; + } + } + } + } + if( !neval.isNull() ){ + if( neval.getKind()==APPLY_UF && neval.getNumChildren()>0 ){ + // is it an evaluation function? + if( d_examples.find( neval[0] )!=d_examples.end() ){ + std::map< Node, bool >::iterator itx = d_examples_invalid.find( neval[0] ); + if( itx==d_examples_invalid.end() ){ + //collect example + bool success = true; + std::vector< Node > ex; + for( unsigned j=1; j<neval.getNumChildren(); j++ ){ + if( !neval[j].isConst() ){ + success = false; + break; + }else{ + ex.push_back( neval[j] ); + } + } + if( success ){ + d_examples[neval[0]].push_back( ex ); + d_examples_out[neval[0]].push_back( n_output ); + d_examples_term[neval[0]].push_back( neval ); + if( n_output.isNull() ){ + d_examples_out_invalid[neval[0]] = true; + }else{ + Assert( n_output.isConst() ); + } + //finished processing this node + return; + }else{ + d_examples_invalid[neval[0]] = true; + d_examples_out_invalid[neval[0]] = true; + } + } + } + } + } + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + bool newHasPol; + bool newPol; + QuantPhaseReq::getPolarity( n, i, hasPol, pol, newHasPol, newPol ); + collectExamples( n[i], visited, newHasPol, newPol ); + } + } +} + +void CegConjecturePbe::initialize(Node n, + std::vector<Node>& candidates, + std::vector<Node>& lemmas) +{ + Trace("sygus-pbe") << "Initialize PBE : " << n << std::endl; + + for( unsigned i=0; i<candidates.size(); i++ ){ + Node v = candidates[i]; + d_examples[v].clear(); + d_examples_out[v].clear(); + d_examples_term[v].clear(); + } + + std::map< Node, bool > visited; + collectExamples( n, visited, true, true ); + + for( unsigned i=0; i<candidates.size(); i++ ){ + Node v = candidates[i]; + Trace("sygus-pbe") << " examples for " << v << " : "; + if( d_examples_invalid.find( v )!=d_examples_invalid.end() ){ + Trace("sygus-pbe") << "INVALID" << std::endl; + }else{ + Trace("sygus-pbe") << std::endl; + for( unsigned j=0; j<d_examples[v].size(); j++ ){ + Trace("sygus-pbe") << " "; + for( unsigned k=0; k<d_examples[v][j].size(); k++ ){ + Trace("sygus-pbe") << d_examples[v][j][k] << " "; + } + if( !d_examples_out[v][j].isNull() ){ + Trace("sygus-pbe") << " -> " << d_examples_out[v][j]; + } + Trace("sygus-pbe") << std::endl; + } + } + } + + //register candidates + if( options::sygusUnifCondSol() ){ + if( candidates.size()==1 ){// conditional solutions for multiple function conjectures TODO? + // collect a pool of types over which we will enumerate terms + Node c = candidates[0]; + //the candidate must be input/output examples + if( d_examples_out_invalid.find( c )==d_examples_out_invalid.end() ){ + Assert( d_examples.find( c )!=d_examples.end() ); + Trace("sygus-unif") << "It is input/output examples..." << std::endl; + TypeNode ctn = c.getType(); + d_cinfo[c].initialize( c ); + // collect the enumerator types / form the strategy + collectEnumeratorTypes(c, ctn, role_equal); + // if we have non-trivial strategies, then use pbe + if( d_cinfo[c].isNonTrivial() ){ + // static learning of redundant constructors + staticLearnRedundantOps( c, lemmas ); + d_is_pbe = true; + } + } + } + } + if( !d_is_pbe ){ + Trace("sygus-unif") << "Do not do PBE optimizations, register..." << std::endl; + for( unsigned i=0; i<candidates.size(); i++ ){ + d_qe->getTermDatabaseSygus()->registerEnumerator( + candidates[i], candidates[i], d_parent); + } + } +} + +Node CegConjecturePbe::PbeTrie::addPbeExample(TypeNode etn, Node e, Node b, + CegConjecturePbe* cpbe, + unsigned index, unsigned ntotal) { + if (index == ntotal) { + // lazy child holds the leaf data + if (d_lazy_child.isNull()) { + d_lazy_child = b; + } + return d_lazy_child; + } else { + std::vector<Node> ex; + if (d_children.empty()) { + if (d_lazy_child.isNull()) { + d_lazy_child = b; + return d_lazy_child; + } else { + // evaluate the lazy child + Assert(cpbe->d_examples.find(e) != cpbe->d_examples.end()); + Assert(index < cpbe->d_examples[e].size()); + ex = cpbe->d_examples[e][index]; + addPbeExampleEval(etn, e, d_lazy_child, ex, cpbe, index, ntotal); + Assert(!d_children.empty()); + d_lazy_child = Node::null(); + } + } else { + Assert(cpbe->d_examples.find(e) != cpbe->d_examples.end()); + Assert(index < cpbe->d_examples[e].size()); + ex = cpbe->d_examples[e][index]; + } + return addPbeExampleEval(etn, e, b, ex, cpbe, index, ntotal); + } +} + +Node CegConjecturePbe::PbeTrie::addPbeExampleEval(TypeNode etn, Node e, Node b, + std::vector<Node>& ex, + CegConjecturePbe* cpbe, + unsigned index, + unsigned ntotal) { + Node eb = cpbe->d_tds->evaluateBuiltin(etn, b, ex); + return d_children[eb].addPbeExample(etn, e, b, cpbe, index + 1, ntotal); +} + +bool CegConjecturePbe::hasExamples(Node e) { + if (isPbe()) { + e = d_tds->getSynthFunForEnumerator(e); + Assert(!e.isNull()); + std::map<Node, bool>::iterator itx = d_examples_invalid.find(e); + if (itx == d_examples_invalid.end()) { + return d_examples.find(e) != d_examples.end(); + } + } + return false; +} + +unsigned CegConjecturePbe::getNumExamples(Node e) { + e = d_tds->getSynthFunForEnumerator(e); + Assert(!e.isNull()); + std::map<Node, std::vector<std::vector<Node> > >::iterator it = + d_examples.find(e); + if (it != d_examples.end()) { + return it->second.size(); + } else { + return 0; + } +} + +void CegConjecturePbe::getExample(Node e, unsigned i, std::vector<Node>& ex) { + e = d_tds->getSynthFunForEnumerator(e); + Assert(!e.isNull()); + std::map<Node, std::vector<std::vector<Node> > >::iterator it = + d_examples.find(e); + if (it != d_examples.end()) { + Assert(i < it->second.size()); + ex.insert(ex.end(), it->second[i].begin(), it->second[i].end()); + } else { + Assert(false); + } +} + +Node CegConjecturePbe::getExampleOut(Node e, unsigned i) { + e = d_tds->getSynthFunForEnumerator(e); + Assert(!e.isNull()); + std::map<Node, std::vector<Node> >::iterator it = d_examples_out.find(e); + if (it != d_examples_out.end()) { + Assert(i < it->second.size()); + return it->second[i]; + } else { + Assert(false); + return Node::null(); + } +} + +Node CegConjecturePbe::addSearchVal(TypeNode tn, Node e, Node bvr) { + Assert(isPbe()); + Assert(!e.isNull()); + e = d_tds->getSynthFunForEnumerator(e); + Assert(!e.isNull()); + std::map<Node, bool>::iterator itx = d_examples_invalid.find(e); + if (itx == d_examples_invalid.end()) { + unsigned nex = d_examples[e].size(); + Node ret = d_pbe_trie[e][tn].addPbeExample(tn, e, bvr, this, 0, nex); + Assert(ret.getType() == bvr.getType()); + return ret; + } + return Node::null(); +} + +Node CegConjecturePbe::evaluateBuiltin(TypeNode tn, Node bn, Node e, + unsigned i) { + e = d_tds->getSynthFunForEnumerator(e); + Assert(!e.isNull()); + std::map<Node, bool>::iterator itx = d_examples_invalid.find(e); + if (itx == d_examples_invalid.end()) { + std::map<Node, std::vector<std::vector<Node> > >::iterator it = + d_examples.find(e); + if (it != d_examples.end()) { + Assert(i < it->second.size()); + return d_tds->evaluateBuiltin(tn, bn, it->second[i]); + } + } + return Rewriter::rewrite(bn); +} + +// ----------------------------- establishing enumeration types + +void CegConjecturePbe::registerEnumerator( + Node et, Node c, TypeNode tn, EnumRole enum_role, bool inSearch) +{ + if (d_einfo.find(et) == d_einfo.end()) + { + Trace("sygus-unif-debug") + << "...register " << et << " for " + << ((DatatypeType)tn.toType()).getDatatype().getName(); + Trace("sygus-unif-debug") << ", role = " << enum_role + << ", in search = " << inSearch << std::endl; + d_einfo[et].initialize(c, enum_role); + // if we are actually enumerating this (could be a compound node in the + // strategy) + if (inSearch) + { + std::map<TypeNode, Node>::iterator itn = + d_cinfo[c].d_search_enum.find(tn); + if (itn == d_cinfo[c].d_search_enum.end()) + { + // use this for the search + d_cinfo[c].d_search_enum[tn] = et; + d_cinfo[c].d_esym_list.push_back(et); + d_einfo[et].d_enum_slave.push_back(et); + // register measured term with database + d_qe->getTermDatabaseSygus()->registerEnumerator(et, c, d_parent, true); + d_einfo[et].d_active_guard = + d_qe->getTermDatabaseSygus()->getActiveGuardForEnumerator(et); + } + else + { + Trace("sygus-unif-debug") << "Make " << et << " a slave of " + << itn->second << std::endl; + d_einfo[itn->second].d_enum_slave.push_back(et); + } + } + } +} + +void CegConjecturePbe::collectEnumeratorTypes(Node e, + TypeNode tn, + NodeRole nrole) +{ + NodeManager* nm = NodeManager::currentNM(); + if (d_cinfo[e].d_tinfo.find(tn) == d_cinfo[e].d_tinfo.end()) + { + // register type + Trace("sygus-unif") << "Register enumerating type : " << tn << std::endl; + d_cinfo[e].initializeType( tn ); + } + EnumTypeInfo& eti = d_cinfo[e].d_tinfo[tn]; + std::map<NodeRole, StrategyNode>::iterator itsn = eti.d_snodes.find(nrole); + if (itsn != eti.d_snodes.end()) + { + // already initialized + return; + } + StrategyNode& snode = eti.d_snodes[nrole]; + + // get the enumerator for this + EnumRole erole = getEnumeratorRoleForNodeRole(nrole); + + Node ee; + std::map<EnumRole, Node>::iterator iten = eti.d_enum.find(erole); + if (iten == eti.d_enum.end()) + { + ee = nm->mkSkolem("ee", tn); + eti.d_enum[erole] = ee; + Trace("sygus-unif-debug") + << "...enumerator " << ee << " for " + << ((DatatypeType)tn.toType()).getDatatype().getName() + << ", role = " << erole << std::endl; + } + else + { + ee = iten->second; + } + + // roles that we do not recurse on + if (nrole == role_ite_condition) + { + Trace("sygus-unif-debug") << "...this register (non-io)" << std::endl; + registerEnumerator(ee, e, tn, erole, true); + return; + } + + // look at information on how we will construct solutions for this type + Assert(tn.isDatatype()); + const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype(); + Assert(dt.isSygus()); + + std::map<Node, std::vector<StrategyType> > cop_to_strat; + std::map<Node, unsigned> cop_to_cindex; + std::map<Node, std::map<unsigned, Node> > cop_to_child_templ; + std::map<Node, std::map<unsigned, Node> > cop_to_child_templ_arg; + std::map<Node, std::vector<unsigned> > cop_to_carg_list; + std::map<Node, std::vector<TypeNode> > cop_to_child_types; + std::map<Node, std::vector<Node> > cop_to_sks; + + // whether we will enumerate the current type + bool search_this = false; + for (unsigned j = 0, ncons = dt.getNumConstructors(); j < ncons; j++) + { + Node cop = Node::fromExpr(dt[j].getConstructor()); + Node op = Node::fromExpr(dt[j].getSygusOp()); + Trace("sygus-unif-debug") << "--- Infer strategy from " << cop + << " with sygus op " << op << "..." << std::endl; + + // expand the evaluation to see if this constuctor induces a strategy + std::vector<Node> utchildren; + utchildren.push_back(cop); + std::vector<Node> sks; + std::vector<TypeNode> sktns; + for (unsigned k = 0, nargs = dt[j].getNumArgs(); k < nargs; k++) + { + Type t = dt[j][k].getRangeType(); + TypeNode ttn = TypeNode::fromType(t); + Node kv = nm->mkSkolem("ut", ttn); + sks.push_back(kv); + cop_to_sks[cop].push_back(kv); + sktns.push_back(ttn); + utchildren.push_back(kv); + } + Node ut = nm->mkNode(APPLY_CONSTRUCTOR, utchildren); + std::vector<Node> echildren; + echildren.push_back(Node::fromExpr(dt.getSygusEvaluationFunc())); + echildren.push_back(ut); + Node sbvl = Node::fromExpr(dt.getSygusVarList()); + for (const Node& sbv : sbvl) + { + echildren.push_back(sbv); + } + Node eut = nm->mkNode(APPLY_UF, echildren); + Trace("sygus-unif-debug2") << " Test evaluation of " << eut << "..." + << std::endl; + eut = d_qe->getTermDatabaseSygus()->unfold(eut); + Trace("sygus-unif-debug2") << " ...got " << eut; + Trace("sygus-unif-debug2") << ", type : " << eut.getType() << std::endl; + + // candidate strategy + if (eut.getKind() == ITE) + { + cop_to_strat[cop].push_back(strat_ITE); + } + else if (eut.getKind() == STRING_CONCAT) + { + if (nrole != role_string_suffix) + { + cop_to_strat[cop].push_back(strat_CONCAT_PREFIX); + } + if (nrole != role_string_prefix) + { + cop_to_strat[cop].push_back(strat_CONCAT_SUFFIX); + } + } + else if (dt[j].isSygusIdFunc()) + { + cop_to_strat[cop].push_back(strat_ID); + } + + // the kinds for which there is a strategy + if (cop_to_strat.find(cop) != cop_to_strat.end()) + { + // infer an injection from the arguments of the datatype + std::map<unsigned, unsigned> templ_injection; + std::vector<Node> vs; + std::vector<Node> ss; + std::map<Node, unsigned> templ_var_index; + for (unsigned k = 0, sksize = sks.size(); k < sksize; k++) + { + Assert(sks[k].getType().isDatatype()); + const Datatype& cdt = + static_cast<DatatypeType>(sks[k].getType().toType()).getDatatype(); + echildren[0] = Node::fromExpr(cdt.getSygusEvaluationFunc()); + echildren[1] = sks[k]; + Trace("sygus-unif-debug2") << "...set eval dt to " << sks[k] + << std::endl; + Node esk = nm->mkNode(APPLY_UF, echildren); + vs.push_back(esk); + Node tvar = nm->mkSkolem("templ", esk.getType()); + templ_var_index[tvar] = k; + Trace("sygus-unif-debug2") << "* template inference : looking for " + << tvar << " for arg " << k << std::endl; + ss.push_back(tvar); + Trace("sygus-unif-debug2") << "* substitute : " << esk << " -> " << tvar + << std::endl; + } + eut = eut.substitute(vs.begin(), vs.end(), ss.begin(), ss.end()); + Trace("sygus-unif-debug2") << "Constructor " << j << ", base term is " + << eut << std::endl; + std::map<unsigned, Node> test_args; + if (dt[j].isSygusIdFunc()) + { + test_args[0] = eut; + } + else + { + for (unsigned k = 0, size = eut.getNumChildren(); k < size; k++) + { + test_args[k] = eut[k]; + } + } + + // TODO : prefix grouping prefix/suffix + bool isAssoc = TermUtil::isAssoc(eut.getKind()); + Trace("sygus-unif-debug2") << eut.getKind() << " isAssoc = " << isAssoc + << std::endl; + std::map<unsigned, std::vector<unsigned> > assoc_combine; + std::vector<unsigned> assoc_waiting; + int assoc_last_valid_index = -1; + for (std::pair<const unsigned, Node>& ta : test_args) + { + unsigned k = ta.first; + Node eut_c = ta.second; + // success if we can find a injection from args to sygus args + if (!inferTemplate(k, eut_c, templ_var_index, templ_injection)) + { + Trace("sygus-unif-debug") + << "...fail: could not find injection (range)." << std::endl; + cop_to_strat.erase(cop); + break; + } + std::map<unsigned, unsigned>::iterator itti = templ_injection.find(k); + if (itti != templ_injection.end()) + { + // if associative, combine arguments if it is the same variable + if (isAssoc && assoc_last_valid_index >= 0 + && itti->second == templ_injection[assoc_last_valid_index]) + { + templ_injection.erase(k); + assoc_combine[assoc_last_valid_index].push_back(k); + } + else + { + assoc_last_valid_index = (int)k; + if (!assoc_waiting.empty()) + { + assoc_combine[k].insert(assoc_combine[k].end(), + assoc_waiting.begin(), + assoc_waiting.end()); + assoc_waiting.clear(); + } + assoc_combine[k].push_back(k); + } + } + else + { + // a ground argument + if (!isAssoc) + { + Trace("sygus-unif-debug") + << "...fail: could not find injection (functional)." + << std::endl; + cop_to_strat.erase(cop); + break; + } + else + { + if (assoc_last_valid_index >= 0) + { + assoc_combine[assoc_last_valid_index].push_back(k); + } + else + { + assoc_waiting.push_back(k); + } + } + } + } + if (cop_to_strat.find(cop) != cop_to_strat.end()) + { + // construct the templates + if (!assoc_waiting.empty()) + { + // could not find a way to fit some arguments into injection + cop_to_strat.erase(cop); + } + else + { + for (std::pair<const unsigned, Node>& ta : test_args) + { + unsigned k = ta.first; + Trace("sygus-unif-debug2") << "- processing argument " << k << "..." + << std::endl; + if (templ_injection.find(k) != templ_injection.end()) + { + unsigned sk_index = templ_injection[k]; + if (std::find(cop_to_carg_list[cop].begin(), + cop_to_carg_list[cop].end(), + sk_index) + == cop_to_carg_list[cop].end()) + { + cop_to_carg_list[cop].push_back(sk_index); + }else{ + Trace("sygus-unif-debug") << "...fail: duplicate argument used" + << std::endl; + cop_to_strat.erase(cop); + break; + } + // also store the template information, if necessary + Node teut; + if (isAssoc) + { + std::vector<unsigned>& ac = assoc_combine[k]; + Assert(!ac.empty()); + std::vector<Node> children; + for (unsigned ack = 0, size_ac = ac.size(); ack < size_ac; + ack++) + { + children.push_back(eut[ac[ack]]); + } + teut = children.size() == 1 + ? children[0] + : nm->mkNode(eut.getKind(), children); + teut = Rewriter::rewrite(teut); + } + else + { + teut = ta.second; + } + + if (!teut.isVar()) + { + cop_to_child_templ[cop][k] = teut; + cop_to_child_templ_arg[cop][k] = ss[sk_index]; + Trace("sygus-unif-debug") + << " Arg " << k << " (template : " << teut << " arg " + << ss[sk_index] << "), index " << sk_index << std::endl; + } + else + { + Trace("sygus-unif-debug") << " Arg " << k << ", index " + << sk_index << std::endl; + Assert(teut == ss[sk_index]); + } + } + else + { + Assert(isAssoc); + } + } + } + } + } + if (cop_to_strat.find(cop) == cop_to_strat.end()) + { + Trace("sygus-unif") << "...constructor " << cop + << " does not correspond to a strategy." << std::endl; + search_this = true; + } + else + { + Trace("sygus-unif") << "-> constructor " << cop + << " matches strategy for " << eut.getKind() << "..." + << std::endl; + // collect children types + for (unsigned k = 0, size = cop_to_carg_list[cop].size(); k < size; k++) + { + TypeNode tn = sktns[cop_to_carg_list[cop][k]]; + Trace("sygus-unif-debug") + << " Child type " << k << " : " + << static_cast<DatatypeType>(tn.toType()).getDatatype().getName() + << std::endl; + cop_to_child_types[cop].push_back(tn); + } + } + } + + // check whether we should also enumerate the current type + Trace("sygus-unif-debug2") << " register this enumerator..." << std::endl; + registerEnumerator(ee, e, tn, erole, search_this); + + if (cop_to_strat.empty()) + { + Trace("sygus-unif") << "...consider " << dt.getName() << " a basic type" + << std::endl; + } + else + { + for (std::pair<const Node, std::vector<StrategyType> >& cstr : cop_to_strat) + { + Node cop = cstr.first; + Trace("sygus-unif-debug") << "Constructor " << cop << " has " + << cstr.second.size() << " strategies..." + << std::endl; + for (unsigned s = 0, ssize = cstr.second.size(); s < ssize; s++) + { + EnumTypeInfoStrat* cons_strat = new EnumTypeInfoStrat; + StrategyType strat = cstr.second[s]; + + cons_strat->d_this = strat; + cons_strat->d_cons = cop; + Trace("sygus-unif-debug") << "Process strategy #" << s + << " for operator : " << cop << " : " << strat + << std::endl; + Assert(cop_to_child_types.find(cop) != cop_to_child_types.end()); + std::vector<TypeNode>& childTypes = cop_to_child_types[cop]; + Assert(cop_to_carg_list.find(cop) != cop_to_carg_list.end()); + std::vector<unsigned>& cargList = cop_to_carg_list[cop]; + + std::vector<Node> sol_templ_children; + sol_templ_children.resize(cop_to_sks[cop].size()); + + for (unsigned j = 0, csize = childTypes.size(); j < csize; j++) + { + // calculate if we should allocate a new enumerator : should be true + // if we have a new role + NodeRole nrole_c = nrole; + if (strat == strat_ITE) + { + if (j == 0) + { + nrole_c = role_ite_condition; + } + } + else if (strat == strat_CONCAT_PREFIX) + { + if ((j + 1) < childTypes.size()) + { + nrole_c = role_string_prefix; + } + } + else if (strat == strat_CONCAT_SUFFIX) + { + if (j > 0) + { + nrole_c = role_string_suffix; + } + } + // in all other cases, role is same as parent + + // register the child type + TypeNode ct = childTypes[j]; + Node csk = cop_to_sks[cop][cargList[j]]; + cons_strat->d_sol_templ_args.push_back(csk); + sol_templ_children[cargList[j]] = csk; + + EnumRole erole_c = getEnumeratorRoleForNodeRole(nrole_c); + // make the enumerator + Node et; + if (cop_to_child_templ[cop].find(j) != cop_to_child_templ[cop].end()) + { + // it is templated, allocate a fresh variable + et = nm->mkSkolem("et", ct); + Trace("sygus-unif-debug") + << "...enumerate " << et << " of type " + << ((DatatypeType)ct.toType()).getDatatype().getName(); + Trace("sygus-unif-debug") + << " for arg " << j << " of " + << ((DatatypeType)tn.toType()).getDatatype().getName() + << std::endl; + registerEnumerator(et, e, ct, erole_c, true); + d_einfo[et].d_template = cop_to_child_templ[cop][j]; + d_einfo[et].d_template_arg = cop_to_child_templ_arg[cop][j]; + Assert(!d_einfo[et].d_template.isNull()); + Assert(!d_einfo[et].d_template_arg.isNull()); + } + else + { + Trace("sygus-unif-debug") + << "...child type enumerate " + << ((DatatypeType)ct.toType()).getDatatype().getName() + << ", node role = " << nrole_c << std::endl; + collectEnumeratorTypes(e, ct, nrole_c); + // otherwise use the previous + Assert(d_cinfo[e].d_tinfo[ct].d_enum.find(erole_c) + != d_cinfo[e].d_tinfo[ct].d_enum.end()); + et = d_cinfo[e].d_tinfo[ct].d_enum[erole_c]; + } + Trace("sygus-unif-debug") << "Register child enumerator " << et + << ", arg " << j << " of " << cop + << ", role = " << erole_c << std::endl; + Assert(!et.isNull()); + cons_strat->d_cenum.push_back(std::pair<Node, NodeRole>(et, nrole_c)); + } + // children that are unused in the strategy can be arbitrary + for (unsigned j = 0, stsize = sol_templ_children.size(); j < stsize; + j++) + { + if (sol_templ_children[j].isNull()) + { + sol_templ_children[j] = cop_to_sks[cop][j].getType().mkGroundTerm(); + } + } + sol_templ_children.insert(sol_templ_children.begin(), cop); + cons_strat->d_sol_templ = + nm->mkNode(APPLY_CONSTRUCTOR, sol_templ_children); + if (strat == strat_CONCAT_SUFFIX) + { + std::reverse(cons_strat->d_cenum.begin(), cons_strat->d_cenum.end()); + std::reverse(cons_strat->d_sol_templ_args.begin(), + cons_strat->d_sol_templ_args.end()); + } + if (Trace.isOn("sygus-unif")) + { + Trace("sygus-unif") << "Initialized strategy " << strat; + Trace("sygus-unif") << " for " << ((DatatypeType)tn.toType()).getDatatype().getName() << ", operator " << cop; + Trace("sygus-unif") << ", #children = " << cons_strat->d_cenum.size() + << ", solution template = (lambda ( "; + for (const Node& targ : cons_strat->d_sol_templ_args) + { + Trace("sygus-unif") << targ << " "; + } + Trace("sygus-unif") << ") " << cons_strat->d_sol_templ << ")"; + Trace("sygus-unif") << std::endl; + } + // make the strategy + snode.d_strats.push_back(cons_strat); + } + } + } +} + +bool CegConjecturePbe::inferTemplate( unsigned k, Node n, std::map< Node, unsigned >& templ_var_index, std::map< unsigned, unsigned >& templ_injection ){ + if( n.getNumChildren()==0 ){ + std::map< Node, unsigned >::iterator itt = templ_var_index.find( n ); + if( itt!=templ_var_index.end() ){ + unsigned kk = itt->second; + std::map< unsigned, unsigned >::iterator itti = templ_injection.find( k ); + if( itti==templ_injection.end() ){ + Trace("sygus-unif-debug") << "...set template injection " << k << " -> " << kk << std::endl; + templ_injection[k] = kk; + }else if( itti->second!=kk ){ + // two distinct variables in this term, we fail + return false; + } + } + return true; + }else{ + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + if( !inferTemplate( k, n[i], templ_var_index, templ_injection ) ){ + return false; + } + } + } + return true; +} + +void CegConjecturePbe::staticLearnRedundantOps( Node c, std::vector< Node >& lemmas ) { + for( unsigned i=0; i<d_cinfo[c].d_esym_list.size(); i++ ){ + Node e = d_cinfo[c].d_esym_list[i]; + std::map< Node, EnumInfo >::iterator itn = d_einfo.find( e ); + Assert( itn!=d_einfo.end() ); + // see if there is anything we can eliminate + Trace("sygus-unif") << "* Search enumerator #" << i << " : type " << ((DatatypeType)e.getType().toType()).getDatatype().getName() << " : "; + Trace("sygus-unif") << e << " has " << itn->second.d_enum_slave.size() << " slaves:" << std::endl; + for( unsigned j=0; j<itn->second.d_enum_slave.size(); j++ ){ + Node es = itn->second.d_enum_slave[j]; + std::map< Node, EnumInfo >::iterator itns = d_einfo.find( es ); + Assert( itns!=d_einfo.end() ); + Trace("sygus-unif") << " " << es << ", role = " << itns->second.getRole() + << std::endl; + } + } + Trace("sygus-unif") << std::endl; + Trace("sygus-unif") << "Strategy for candidate " << c << " is : " << std::endl; + std::map<Node, std::map<NodeRole, bool> > visited; + std::map<Node, std::map<unsigned, bool> > needs_cons; + staticLearnRedundantOps(c, + d_cinfo[c].getRootEnumerator(), + role_equal, + visited, + needs_cons, + 0, + false); + // now, check the needs_cons map + for (std::pair<const Node, std::map<unsigned, bool> >& nce : needs_cons) + { + Node em = nce.first; + const Datatype& dt = + static_cast<DatatypeType>(em.getType().toType()).getDatatype(); + for (std::pair<const unsigned, bool>& nc : nce.second) + { + Assert(nc.first < dt.getNumConstructors()); + if (!nc.second) + { + Node tst = + datatypes::DatatypesRewriter::mkTester(em, nc.first, dt).negate(); + if (std::find(lemmas.begin(), lemmas.end(), tst) == lemmas.end()) + { + Trace("sygus-unif") << "...can exclude based on : " << tst + << std::endl; + lemmas.push_back(tst); + } + } + } + } +} + +void CegConjecturePbe::staticLearnRedundantOps( + Node c, + Node e, + NodeRole nrole, + std::map<Node, std::map<NodeRole, bool> >& visited, + std::map<Node, std::map<unsigned, bool> >& needs_cons, + int ind, + bool isCond) +{ + std::map< Node, EnumInfo >::iterator itn = d_einfo.find( e ); + Assert( itn!=d_einfo.end() ); + + if (visited[e].find(nrole) == visited[e].end() + || (isCond && !itn->second.isConditional())) + { + visited[e][nrole] = true; + // if conditional + if (isCond) + { + itn->second.setConditional(); + } + indent("sygus-unif", ind); + Trace("sygus-unif") << e << " :: node role : " << nrole; + Trace("sygus-unif") + << ", type : " + << ((DatatypeType)e.getType().toType()).getDatatype().getName(); + if (isCond) + { + Trace("sygus-unif") << ", conditional"; + } + Trace("sygus-unif") << ", enum role : " << itn->second.getRole(); + + if( itn->second.isTemplated() ){ + Trace("sygus-unif") << ", templated : (lambda " + << itn->second.d_template_arg << " " + << itn->second.d_template << ")" << std::endl; + }else{ + Trace("sygus-unif") << std::endl; + TypeNode etn = e.getType(); + + // enumerator type info + std::map< TypeNode, EnumTypeInfo >::iterator itt = d_cinfo[c].d_tinfo.find( etn ); + Assert( itt!=d_cinfo[c].d_tinfo.end() ); + EnumTypeInfo& tinfo = itt->second; + + // strategy info + std::map<NodeRole, StrategyNode>::iterator itsn = + tinfo.d_snodes.find(nrole); + Assert(itsn != tinfo.d_snodes.end()); + StrategyNode& snode = itsn->second; + + if (snode.d_strats.empty()) + { + return; + } + std::map<unsigned, bool> needs_cons_curr; + // various strategies + for (unsigned j = 0, size = snode.d_strats.size(); j < size; j++) + { + EnumTypeInfoStrat* etis = snode.d_strats[j]; + StrategyType strat = etis->d_this; + bool newIsCond = isCond || strat == strat_ITE; + indent("sygus-unif", ind + 1); + Trace("sygus-unif") << "Strategy : " << strat + << ", from cons : " << etis->d_cons << std::endl; + int cindex = Datatype::indexOf(etis->d_cons.toExpr()); + Assert(cindex != -1); + needs_cons_curr[static_cast<unsigned>(cindex)] = false; + for (std::pair<Node, NodeRole>& cec : etis->d_cenum) + { + // recurse + staticLearnRedundantOps(c, + cec.first, + cec.second, + visited, + needs_cons, + ind + 2, + newIsCond); + } + } + // get the master enumerator for the type of this enumerator + std::map<TypeNode, Node>::iterator itse = + d_cinfo[c].d_search_enum.find(etn); + if (itse == d_cinfo[c].d_search_enum.end()) + { + return; + } + Node em = itse->second; + Assert(!em.isNull()); + // get the current datatype + const Datatype& dt = + static_cast<DatatypeType>(etn.toType()).getDatatype(); + // all constructors that are not a part of a strategy are needed + for (unsigned j = 0, size = dt.getNumConstructors(); j < size; j++) + { + if (needs_cons_curr.find(j) == needs_cons_curr.end()) + { + needs_cons_curr[j] = true; + } + } + // update the constructors that the master enumerator needs + if (needs_cons.find(em) == needs_cons.end()) + { + needs_cons[em] = needs_cons_curr; + } + else + { + for (unsigned j = 0, size = dt.getNumConstructors(); j < size; j++) + { + needs_cons[em][j] = needs_cons[em][j] || needs_cons_curr[j]; + } + } + } + }else{ + indent("sygus-unif", ind); + Trace("sygus-unif") << e << " :: node role : " << nrole << std::endl; + } +} + +// ------------------------------------------- solution construction from enumeration + +void CegConjecturePbe::getCandidateList( std::vector< Node >& candidates, std::vector< Node >& clist ) { + Valuation& valuation = d_qe->getValuation(); + for( unsigned i=0; i<candidates.size(); i++ ){ + Node v = candidates[i]; + std::map< Node, CandidateInfo >::iterator it = d_cinfo.find( v ); + if( it!=d_cinfo.end() ){ + for( unsigned j=0; j<it->second.d_esym_list.size(); j++ ){ + Node e = it->second.d_esym_list[j]; + std::map< Node, EnumInfo >::iterator it = d_einfo.find( e ); + Assert( it != d_einfo.end() ); + Node gstatus = valuation.getSatValue(it->second.d_active_guard); + if (!gstatus.isNull() && gstatus.getConst<bool>()) + { + clist.push_back( e ); + } + } + } + } +} + +bool CegConjecturePbe::constructCandidates( std::vector< Node >& enums, std::vector< Node >& enum_values, + std::vector< Node >& candidates, std::vector< Node >& candidate_values, + std::vector< Node >& lems ) { + Assert( enums.size()==enum_values.size() ); + if( !enums.empty() ){ + unsigned min_term_size = 0; + std::vector< unsigned > enum_consider; + Trace("sygus-pbe-enum") << "Register new enumerated values : " << std::endl; + for( unsigned i=0; i<enums.size(); i++ ){ + Trace("sygus-pbe-enum") << " " << enums[i] << " -> " << enum_values[i] << std::endl; + unsigned sz = d_tds->getSygusTermSize( enum_values[i] ); + if( i==0 || sz<min_term_size ){ + enum_consider.clear(); + min_term_size = sz; + enum_consider.push_back( i ); + }else if( sz==min_term_size ){ + enum_consider.push_back( i ); + } + } + // only consider the enumerators that are at minimum size (for fairness) + Trace("sygus-pbe-enum") << "...register " << enum_consider.size() << " / " << enums.size() << std::endl; + for( unsigned i=0; i<enum_consider.size(); i++ ){ + unsigned j = enum_consider[i]; + addEnumeratedValue( enums[j], enum_values[j], lems ); + } + } + for( unsigned i=0; i<candidates.size(); i++ ){ + Node c = candidates[i]; + //build decision tree for candidate + Node vc = constructSolution( c ); + if( vc.isNull() ){ + return false; + }else{ + candidate_values.push_back( vc ); + } + } + return true; +} + +void CegConjecturePbe::addEnumeratedValue( Node x, Node v, std::vector< Node >& lems ) { + std::map< Node, EnumInfo >::iterator it = d_einfo.find( x ); + Assert( it != d_einfo.end() ); + Node gstatus = d_qe->getValuation().getSatValue(it->second.d_active_guard); + if (gstatus.isNull() || !gstatus.getConst<bool>()) + { + Trace("sygus-pbe-enum-debug") << " ...guard is inactive." << std::endl; + return; + } + Assert( + std::find(it->second.d_enum_vals.begin(), it->second.d_enum_vals.end(), v) + == it->second.d_enum_vals.end()); + Node c = it->second.d_parent_candidate; + // The explanation for why the current value should be excluded in future + // iterations. + Node exp_exc; + if (d_examples_out_invalid.find(c) == d_examples_out_invalid.end()) + { + std::map<Node, CandidateInfo>::iterator itc = d_cinfo.find(c); + Assert(itc != d_cinfo.end()); + TypeNode xtn = x.getType(); + Node bv = d_tds->sygusToBuiltin(v, xtn); + std::map<Node, std::vector<std::vector<Node> > >::iterator itx = + d_examples.find(c); + std::map<Node, std::vector<Node> >::iterator itxo = d_examples_out.find(c); + Assert(itx != d_examples.end()); + Assert(itxo != d_examples_out.end()); + Assert(itx->second.size() == itxo->second.size()); + std::vector<Node> base_results; + // compte the results + for (unsigned j = 0, size = itx->second.size(); j < size; j++) + { + Node res = d_tds->evaluateBuiltin(xtn, bv, itx->second[j]); + Trace("sygus-pbe-enum-debug") + << "...got res = " << res << " from " << bv << std::endl; + base_results.push_back(res); + } + // is it excluded for domain-specific reason? + std::vector<Node> exp_exc_vec; + if (getExplanationForEnumeratorExclude( + c, x, v, base_results, it->second, exp_exc_vec)) + { + Assert(!exp_exc_vec.empty()); + exp_exc = exp_exc_vec.size() == 1 + ? exp_exc_vec[0] + : NodeManager::currentNM()->mkNode(AND, exp_exc_vec); + Trace("sygus-pbe-enum") + << " ...fail : term is excluded (domain-specific)" << std::endl; + } + else + { + // notify all slaves + Assert( !it->second.d_enum_slave.empty() ); + //explanation for why this value should be excluded + for( unsigned s=0; s<it->second.d_enum_slave.size(); s++ ){ + Node xs = it->second.d_enum_slave[s]; + std::map< Node, EnumInfo >::iterator itv = d_einfo.find( xs ); + Assert( itv!=d_einfo.end() ); + Trace("sygus-pbe-enum") << "Process " << xs << " from " << s << std::endl; + //bool prevIsCover = false; + if (itv->second.getRole() == enum_io) + { + Trace("sygus-pbe-enum") << " IO-Eval of "; + //prevIsCover = itv->second.isFeasible(); + }else{ + Trace("sygus-pbe-enum") << "Evaluation of "; + } + Trace("sygus-pbe-enum") << xs << " : "; + //evaluate all input/output examples + std::vector< Node > results; + Node templ = itv->second.d_template; + TNode templ_var = itv->second.d_template_arg; + std::map< Node, bool > cond_vals; + for (unsigned j = 0, size = base_results.size(); j < size; j++) + { + Node res = base_results[j]; + Assert( res.isConst() ); + if( !templ.isNull() ){ + TNode tres = res; + res = templ.substitute( templ_var, res ); + res = Rewriter::rewrite( res ); + Assert( res.isConst() ); + } + Node resb; + if (itv->second.getRole() == enum_io) + { + Node out = itxo->second[j]; + Assert( out.isConst() ); + resb = res==out ? d_true : d_false; + }else{ + resb = res; + } + cond_vals[resb] = true; + results.push_back( resb ); + if( Trace.isOn("sygus-pbe-enum") ){ + if( resb.getType().isBoolean() ){ + Trace("sygus-pbe-enum") << ( resb==d_true ? "1" : "0" ); + }else{ + Trace("sygus-pbe-enum") << "?"; + } + } + } + bool keep = false; + if (itv->second.getRole() == enum_io) + { + // latter is the degenerate case of no examples + if (cond_vals.find(d_true) != cond_vals.end() || cond_vals.empty()) + { + //check subsumbed/subsuming + std::vector< Node > subsume; + if( cond_vals.find( d_false )==cond_vals.end() ){ + // it is the entire solution, we are done + Trace("sygus-pbe-enum") << " ...success, full solution added to PBE pool : " << d_tds->sygusToBuiltin( v ) << std::endl; + if( !itv->second.isSolved() ){ + itv->second.setSolved( v ); + // it subsumes everything + itv->second.d_term_trie.clear(); + itv->second.d_term_trie.addTerm( this, v, results, true, subsume ); + } + keep = true; + }else{ + Node val = itv->second.d_term_trie.addTerm( this, v, results, true, subsume ); + if( val==v ){ + Trace("sygus-pbe-enum") << " ...success"; + if( !subsume.empty() ){ + itv->second.d_enum_subsume.insert( itv->second.d_enum_subsume.end(), subsume.begin(), subsume.end() ); + Trace("sygus-pbe-enum") << " and subsumed " << subsume.size() << " terms"; + } + Trace("sygus-pbe-enum") << "! add to PBE pool : " << d_tds->sygusToBuiltin( v ) << std::endl; + keep = true; + }else{ + Assert( subsume.empty() ); + Trace("sygus-pbe-enum") << " ...fail : subsumed" << std::endl; + } + } + }else{ + Trace("sygus-pbe-enum") << " ...fail : it does not satisfy examples." << std::endl; + } + }else{ + // must be unique up to examples + Node val = itv->second.d_term_trie.addCond(this, v, results, true); + if (val == v) + { + Trace("sygus-pbe-enum") << " ...success! add to PBE pool : " + << d_tds->sygusToBuiltin(v) << std::endl; + keep = true; + }else{ + Trace("sygus-pbe-enum") + << " ...fail : term is not unique" << std::endl; + } + itc->second.d_cond_count++; + } + if( keep ){ + // notify the parent to retry the build of PBE + itc->second.d_check_sol = true; + itv->second.addEnumValue( this, v, results ); + } + } + } + }else{ + Trace("sygus-pbe-enum-debug") + << " ...examples do not have output." << std::endl; + } + // exclude this value on subsequent iterations + Node g = it->second.d_active_guard; + if (exp_exc.isNull()) + { + // if we did not already explain why this should be excluded, use default + exp_exc = d_tds->getExplain()->getExplanationForConstantEquality(x, v); + } + Node exlem = + NodeManager::currentNM()->mkNode(OR, g.negate(), exp_exc.negate()); + Trace("sygus-pbe-enum-lemma") + << "CegConjecturePbe : enumeration exclude lemma : " << exlem + << std::endl; + lems.push_back(exlem); +} + +bool CegConjecturePbe::useStrContainsEnumeratorExclude(Node x, EnumInfo& ei) +{ + TypeNode xbt = d_tds->sygusToBuiltinType(x.getType()); + if (xbt.isString()) + { + std::map<Node, bool>::iterator itx = d_use_str_contains_eexc.find(x); + if (itx != d_use_str_contains_eexc.end()) + { + return itx->second; + } + Trace("sygus-pbe-enum-debug") + << "Is " << x << " is str.contains exclusion?" << std::endl; + d_use_str_contains_eexc[x] = true; + for (const Node& sn : ei.d_enum_slave) + { + std::map<Node, EnumInfo>::iterator itv = d_einfo.find(sn); + EnumRole er = itv->second.getRole(); + if (er != enum_io && er != enum_concat_term) + { + Trace("sygus-pbe-enum-debug") << " incompatible slave : " << sn + << ", role = " << er << std::endl; + d_use_str_contains_eexc[x] = false; + return false; + } + if (itv->second.isConditional()) + { + Trace("sygus-pbe-enum-debug") + << " conditional slave : " << sn << std::endl; + d_use_str_contains_eexc[x] = false; + return false; + } + } + Trace("sygus-pbe-enum-debug") + << "...can use str.contains exclusion." << std::endl; + return d_use_str_contains_eexc[x]; + } + return false; +} + +bool CegConjecturePbe::getExplanationForEnumeratorExclude( + Node c, + Node x, + Node v, + std::vector<Node>& results, + EnumInfo& ei, + std::vector<Node>& exp) +{ + if (useStrContainsEnumeratorExclude(x, ei)) + { + NodeManager* nm = NodeManager::currentNM(); + // This check whether the example evaluates to something that is larger than + // the output for some input/output pair. If so, then this term is never + // useful. We generalize its explanation below. + + if (Trace.isOn("sygus-pbe-cterm-debug")) + { + Trace("sygus-pbe-enum") << std::endl; + } + // check if all examples had longer length that the output + std::map<Node, std::vector<Node> >::iterator itxo = d_examples_out.find(c); + Assert(itxo != d_examples_out.end()); + Assert(itxo->second.size() == results.size()); + Trace("sygus-pbe-cterm-debug") + << "Check enumerator exclusion for " << x << " -> " + << d_tds->sygusToBuiltin(v) << " based on str.contains." << std::endl; + std::vector<unsigned> cmp_indices; + for (unsigned i = 0, size = results.size(); i < size; i++) + { + Assert(results[i].isConst()); + Assert(itxo->second[i].isConst()); + Trace("sygus-pbe-cterm-debug") + << " " << results[i] << " <> " << itxo->second[i]; + Node cont = nm->mkNode(STRING_STRCTN, itxo->second[i], results[i]); + Node contr = Rewriter::rewrite(cont); + if (contr == d_false) + { + cmp_indices.push_back(i); + Trace("sygus-pbe-cterm-debug") << "...not contained." << std::endl; + } + else + { + Trace("sygus-pbe-cterm-debug") << "...contained." << std::endl; + } + } + if (!cmp_indices.empty()) + { + // we check invariance with respect to a negative contains test + NegContainsSygusInvarianceTest ncset; + ncset.init(d_parent, x, itxo->second, cmp_indices); + // construct the generalized explanation + d_tds->getExplain()->getExplanationFor(x, v, exp, ncset); + Trace("sygus-pbe-cterm") + << "PBE-cterm : enumerator exclude " << d_tds->sygusToBuiltin(v) + << " due to negative containment." << std::endl; + return true; + } + } + return false; +} + + + +void CegConjecturePbe::EnumInfo::addEnumValue( CegConjecturePbe * pbe, Node v, std::vector< Node >& results ) { + d_enum_val_to_index[v] = d_enum_vals.size(); + d_enum_vals.push_back( v ); + d_enum_vals_res.push_back( results ); + /* + if( getRole()==enum_io ){ + // compute + if( d_enum_total.empty() ){ + d_enum_total = results; + }else if( !d_enum_total_true ){ + d_enum_total_true = true; + Assert( d_enum_total.size()==results.size() ); + for( unsigned i=0; i<results.size(); i++ ){ + if( d_enum_total[i]==pbe->d_true || results[i]==pbe->d_true ){ + d_enum_total[i] = pbe->d_true; + }else{ + d_enum_total[i] = pbe->d_false; + d_enum_total_true = false; + } + } + } + } + */ +} + +void CegConjecturePbe::EnumInfo::initialize(Node c, EnumRole role) +{ + d_parent_candidate = c; + d_role = role; +} + +void CegConjecturePbe::EnumInfo::setSolved( Node slv ) { + d_enum_solved = slv; + //d_enum_total_true = true; +} + +void CegConjecturePbe::CandidateInfo::initialize( Node c ) { + d_this_candidate = c; + d_root = c.getType(); +} + +void CegConjecturePbe::CandidateInfo::initializeType( TypeNode tn ) { + d_tinfo[tn].d_this_type = tn; + d_tinfo[tn].d_parent = this; +} + +Node CegConjecturePbe::CandidateInfo::getRootEnumerator() { + std::map<EnumRole, Node>::iterator it = d_tinfo[d_root].d_enum.find(enum_io); + Assert( it!=d_tinfo[d_root].d_enum.end() ); + return it->second; +} + +bool CegConjecturePbe::CandidateInfo::isNonTrivial() { + //TODO + return true; +} + +// status : 0 : exact, -1 : vals is subset, 1 : vals is superset +Node CegConjecturePbe::SubsumeTrie::addTermInternal( CegConjecturePbe * pbe, Node t, std::vector< Node >& vals, bool pol, + std::vector< Node >& subsumed, bool spol, IndexFilter * f, + unsigned index, int status, bool checkExistsOnly, bool checkSubsume ) { + if( index==vals.size() ){ + if( status==0 ){ + // set the term if checkExistsOnly = false + if( d_term.isNull() && !checkExistsOnly ){ + d_term = t; + } + }else if( status==1 ){ + Assert( checkSubsume ); + // found a subsumed term + if( !d_term.isNull() ){ + subsumed.push_back( d_term ); + if( !checkExistsOnly ){ + // remove it if checkExistsOnly = false + d_term = Node::null(); + } + } + }else{ + Assert( !checkExistsOnly && checkSubsume ); + } + return d_term; + }else{ + // the current value + Assert( pol || ( vals[index].isConst() && vals[index].getType().isBoolean() ) ); + Node cv = pol ? vals[index] : ( vals[index]==pbe->d_true ? pbe->d_false : pbe->d_true ); + // if checkExistsOnly = false, check if the current value is subsumed if checkSubsume = true, if so, don't add + if( !checkExistsOnly && checkSubsume ){ + std::vector< bool > check_subsumed_by; + if( status==0 ){ + if( cv==pbe->d_false ){ + check_subsumed_by.push_back( spol ); + } + }else if( status==-1 ){ + check_subsumed_by.push_back( spol ); + if( cv==pbe->d_false ){ + check_subsumed_by.push_back( !spol ); + } + } + // check for subsumed nodes + for( unsigned i=0; i<check_subsumed_by.size(); i++ ){ + Node csval = check_subsumed_by[i] ? pbe->d_true : pbe->d_false; + // check if subsumed + std::map< Node, SubsumeTrie >::iterator itc = d_children.find( csval ); + if( itc!=d_children.end() ){ + unsigned next_index = f ? f->next( index ) : index+1; + Node ret = itc->second.addTermInternal( pbe, t, vals, pol, subsumed, spol, f, next_index, -1, checkExistsOnly, checkSubsume ); + // ret subsumes t + if( !ret.isNull() ){ + return ret; + } + } + } + } + Node ret; + std::vector< bool > check_subsume; + if( status==0 ){ + unsigned next_index = f ? f->next( index ) : index+1; + if( checkExistsOnly ){ + std::map< Node, SubsumeTrie >::iterator itc = d_children.find( cv ); + if( itc!=d_children.end() ){ + ret = itc->second.addTermInternal( pbe, t, vals, pol, subsumed, spol, f, next_index, 0, checkExistsOnly, checkSubsume ); + } + }else{ + Assert( spol ); + ret = d_children[cv].addTermInternal( pbe, t, vals, pol, subsumed, spol, f, next_index, 0, checkExistsOnly, checkSubsume ); + if( ret!=t ){ + // we were subsumed by ret, return + return ret; + } + } + if( checkSubsume ){ + // check for subsuming + if( cv==pbe->d_true ){ + check_subsume.push_back( !spol ); + } + } + }else if( status==1 ){ + Assert( checkSubsume ); + check_subsume.push_back( !spol ); + if( cv==pbe->d_true ){ + check_subsume.push_back( spol ); + } + } + if( checkSubsume ){ + // check for subsumed terms + for( unsigned i=0; i<check_subsume.size(); i++ ){ + Node csval = check_subsume[i] ? pbe->d_true : pbe->d_false; + std::map< Node, SubsumeTrie >::iterator itc = d_children.find( csval ); + if( itc!=d_children.end() ){ + unsigned next_index = f ? f->next( index ) : index+1; + itc->second.addTermInternal( pbe, t, vals, pol, subsumed, spol, f, next_index, 1, checkExistsOnly, checkSubsume ); + // clean up + if( itc->second.isEmpty() ){ + Assert( !checkExistsOnly ); + d_children.erase( csval ); + } + } + } + } + return ret; + } +} + +Node CegConjecturePbe::SubsumeTrie::addTerm( CegConjecturePbe * pbe, Node t, std::vector< Node >& vals, bool pol, std::vector< Node >& subsumed, IndexFilter * f ) { + unsigned start_index = f ? f->start() : 0; + return addTermInternal( pbe, t, vals, pol, subsumed, true, f, start_index, 0, false, true ); +} + +Node CegConjecturePbe::SubsumeTrie::addCond( CegConjecturePbe * pbe, Node c, std::vector< Node >& vals, bool pol, IndexFilter * f ) { + unsigned start_index = f ? f->start() : 0; + std::vector< Node > subsumed; + return addTermInternal( pbe, c, vals, pol, subsumed, true, f, start_index, 0, false, false ); +} + +void CegConjecturePbe::SubsumeTrie::getSubsumed( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::vector< Node >& subsumed, IndexFilter * f ){ + unsigned start_index = f ? f->start() : 0; + addTermInternal( pbe, Node::null(), vals, pol, subsumed, true, f, start_index, 1, true, true ); +} + +void CegConjecturePbe::SubsumeTrie::getSubsumedBy( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::vector< Node >& subsumed_by, IndexFilter * f ){ + // flip polarities + unsigned start_index = f ? f->start() : 0; + addTermInternal( pbe, Node::null(), vals, !pol, subsumed_by, false, f, start_index, 1, true, true ); +} + +void CegConjecturePbe::SubsumeTrie::getLeavesInternal( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::map< int, std::vector< Node > >& v, + IndexFilter * f, unsigned index, int status ) { + if( index==vals.size() ){ + Assert( !d_term.isNull() ); + Assert( std::find( v[status].begin(), v[status].end(), d_term )==v[status].end() ); + v[status].push_back( d_term ); + }else{ + Assert( vals[index].isConst() && vals[index].getType().isBoolean() ); + // filter should be for cv + Assert( f==NULL || vals[index]==( pol ? pbe->d_true : pbe->d_false ) ); + for( std::map< Node, SubsumeTrie >::iterator it = d_children.begin(); it != d_children.end(); ++it ){ + int new_status = status; + // if the current value is true + if( vals[index]==( pol ? pbe->d_true : pbe->d_false ) ){ + if( status!=0 ){ + new_status = ( it->first == pbe->d_true ? 1 : -1 ); + if( status!=-2 && new_status!=status ){ + new_status = 0; + } + } + } + unsigned next_index = f ? f->next( index ) : index+1; + it->second.getLeavesInternal( pbe, vals, pol, v, f, next_index, new_status ); + } + } +} + +void CegConjecturePbe::SubsumeTrie::getLeaves( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::map< int, std::vector< Node > >& v, IndexFilter * f ) { + unsigned start_index = f ? f->start() : 0; + getLeavesInternal( pbe, vals, pol, v, f, start_index, -2 ); +} + +void CegConjecturePbe::IndexFilter::mk( std::vector< Node >& vals, bool pol ) { + Trace("sygus-pbe-debug") << "Make for : "; + print_val( "sygus-pbe-debug", vals, pol ); + Trace("sygus-pbe-debug") << std::endl; + Node poln = NodeManager::currentNM()->mkConst( pol ); + + unsigned curr_index = 0; + while( curr_index<vals.size() && vals[curr_index]!=poln ){ + curr_index++; + } + d_next[0] = curr_index; + Trace("sygus-pbe-debug") << "0 -> " << curr_index << std::endl; + unsigned i = curr_index; + while( i<vals.size() ){ + while( i<vals.size() && vals[i]!=poln ){ + i++; + } + i++; + d_next[curr_index+1] = i; + Trace("sygus-pbe-debug") << curr_index+1 << " -> " << i << std::endl; + curr_index = i; + } + + // verify it is correct + unsigned j = start(); + for( unsigned k=0; k<j; k++ ){ + AlwaysAssert( vals[k]!=poln ); + } + Trace("sygus-pbe-debug") << "...start : " << j << std::endl; + unsigned counter = 0; + while( j<vals.size() ){ + Trace("sygus-pbe-debug") << "...at : " << j << std::endl; + AlwaysAssert( vals[j]==poln ); + unsigned jj = next( j ); + AlwaysAssert( jj>j ); + for( unsigned k=(j+1); k<jj; k++ ){ + AlwaysAssert( vals[k]!=poln ); + } + AlwaysAssert( counter<=vals.size() ); + counter++; + j = jj; + } + + +} + +unsigned CegConjecturePbe::IndexFilter::start() { + std::map< unsigned, unsigned >::iterator it = d_next.find( 0 ); + if( it==d_next.end() ){ + return 0; + }else{ + return it->second; + } +} + +unsigned CegConjecturePbe::IndexFilter::next( unsigned i ) { + std::map< unsigned, unsigned >::iterator it = d_next.find( i+1 ); + if( it==d_next.end() ){ + return i+1; + }else{ + return it->second; + } +} + +bool CegConjecturePbe::IndexFilter::isEq( std::vector< Node >& vals, Node v ) { + unsigned index = start(); + while( index<vals.size() ){ + if( vals[index]!=v ){ + return false; + } + index = next( index ); + } + return true; +} + +Node CegConjecturePbe::constructSolution( Node c ){ + std::map< Node, CandidateInfo >::iterator itc = d_cinfo.find( c ); + Assert( itc!=d_cinfo.end() ); + if( !itc->second.d_solution.isNull() ){ + // already has a solution + return itc->second.d_solution; + }else{ + // only check if an enumerator updated + if( itc->second.d_check_sol ){ + Trace("sygus-pbe") << "Construct solution, #iterations = " << itc->second.d_cond_count << std::endl; + itc->second.d_check_sol = false; + // try multiple times if we have done multiple conditions, due to non-determinism + Node vc; + for( unsigned i=0; i<=itc->second.d_cond_count; i++ ){ + Trace("sygus-pbe-dt") << "ConstructPBE for candidate: " << c << std::endl; + Node e = itc->second.getRootEnumerator(); + UnifContext x; + x.initialize( this, c ); + Node vcc = constructSolution(c, e, role_equal, x, 1); + if( !vcc.isNull() ){ + if( vc.isNull() || ( !vc.isNull() && d_tds->getSygusTermSize( vcc )<d_tds->getSygusTermSize( vc ) ) ){ + Trace("sygus-pbe") << "**** PBE SOLVED : " << c << " = " << vcc << std::endl; + Trace("sygus-pbe") << "...solved at iteration " << i << std::endl; + vc = vcc; + } + } + } + if( !vc.isNull() ){ + itc->second.d_solution = vc; + return vc; + } + Trace("sygus-pbe") << "...failed to solve." << std::endl; + } + return Node::null(); + } +} + +Node CegConjecturePbe::constructBestSolvedTerm( std::vector< Node >& solved, UnifContext& x ){ + Assert( !solved.empty() ); + // TODO + return solved[0]; +} + +Node CegConjecturePbe::constructBestStringSolvedTerm( std::vector< Node >& solved, UnifContext& x ) { + Assert( !solved.empty() ); + // TODO + return solved[0]; +} + +Node CegConjecturePbe::constructBestSolvedConditional( std::vector< Node >& solved, UnifContext& x ){ + Assert( !solved.empty() ); + // TODO + return solved[0]; +} + +Node CegConjecturePbe::constructBestConditional( std::vector< Node >& conds, UnifContext& x ) { + Assert( !conds.empty() ); + // TODO + double r = Random::getRandom().pickDouble(0.0, 1.0); + unsigned cindex = r*conds.size(); + if( cindex>conds.size() ){ + cindex = conds.size() - 1; + } + return conds[cindex]; +} + +Node CegConjecturePbe::constructBestStringToConcat( std::vector< Node > strs, + std::map< Node, unsigned > total_inc, + std::map< Node, std::vector< unsigned > > incr, + UnifContext& x ) { + Assert( !strs.empty() ); + std::random_shuffle(strs.begin(), strs.end()); + // prefer one that has incremented by more than 0 + for (const Node& ns : strs) + { + if (total_inc[ns] > 0) + { + return ns; + } + } + return strs[0]; +} + +Node CegConjecturePbe::constructSolution( + Node c, Node e, NodeRole nrole, UnifContext& x, int ind) +{ + TypeNode etn = e.getType(); + if (Trace.isOn("sygus-pbe-dt-debug")) + { + indent("sygus-pbe-dt-debug", ind); + Trace("sygus-pbe-dt-debug") << "ConstructPBE: (" << e << ", " << nrole + << ") for type " << etn << " in context "; + print_val("sygus-pbe-dt-debug", x.d_vals); + if (x.d_has_string_pos != role_invalid) + { + Trace("sygus-pbe-dt-debug") << ", string context [" << x.d_has_string_pos; + for (unsigned i = 0, size = x.d_str_pos.size(); i < size; i++) + { + Trace("sygus-pbe-dt-debug") << " " << x.d_str_pos[i]; + } + Trace("sygus-pbe-dt-debug") << "]"; + } + Trace("sygus-pbe-dt-debug") << std::endl; + } + // enumerator type info + std::map<TypeNode, EnumTypeInfo>::iterator itt = d_cinfo[c].d_tinfo.find(etn); + Assert(itt != d_cinfo[c].d_tinfo.end()); + EnumTypeInfo& tinfo = itt->second; + + // get the enumerator information + std::map< Node, EnumInfo >::iterator itn = d_einfo.find( e ); + Assert( itn!=d_einfo.end() ); + EnumInfo& einfo = itn->second; + + Node ret_dt; + if (nrole == role_equal) + { + if (!x.isReturnValueModified()) + { + if (einfo.isSolved()) + { + // this type has a complete solution + ret_dt = einfo.getSolved(); + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") << "return PBE: success : solved " + << d_tds->sygusToBuiltin(ret_dt) << std::endl; + Assert(!ret_dt.isNull()); + } + else + { + // could be conditionally solved + std::vector<Node> subsumed_by; + einfo.d_term_trie.getSubsumedBy(this, x.d_vals, true, subsumed_by); + if (!subsumed_by.empty()) + { + ret_dt = constructBestSolvedTerm(subsumed_by, x); + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") << "return PBE: success : conditionally solved" + << d_tds->sygusToBuiltin(ret_dt) << std::endl; + } + else + { + indent("sygus-pbe-dt-debug", ind); + Trace("sygus-pbe-dt-debug") + << " ...not currently conditionally solved." << std::endl; + } + } + } + if (ret_dt.isNull()) + { + if (d_tds->sygusToBuiltinType(e.getType()).isString()) + { + // check if a current value that closes all examples + // get the term enumerator for this type + bool success = true; + std::map<Node, EnumInfo>::iterator itet; + std::map<EnumRole, Node>::iterator itnt = + tinfo.d_enum.find(enum_concat_term); + if( itnt != itt->second.d_enum.end() ){ + Node et = itnt->second; + itet = d_einfo.find( et ); + Assert(itet != d_einfo.end()); + }else{ + success = false; + } + if (success) + { + // get the current examples + std::map<Node, std::vector<Node> >::iterator itx = + d_examples_out.find(c); + Assert(itx != d_examples_out.end()); + std::vector<String> ex_vals; + x.getCurrentStrings(this, itx->second, ex_vals); + Assert(itn->second.d_enum_vals.size() + == itn->second.d_enum_vals_res.size()); + + // test each example in the term enumerator for the type + std::vector<Node> str_solved; + for (unsigned i = 0, size = itet->second.d_enum_vals.size(); i < size; + i++) + { + if (x.isStringSolved( + this, ex_vals, itet->second.d_enum_vals_res[i])) + { + str_solved.push_back(itet->second.d_enum_vals[i]); + } + } + if (!str_solved.empty()) + { + ret_dt = constructBestStringSolvedTerm(str_solved, x); + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") << "return PBE: success : string solved " + << d_tds->sygusToBuiltin(ret_dt) << std::endl; + } + else + { + indent("sygus-pbe-dt-debug", ind); + Trace("sygus-pbe-dt-debug") << " ...not currently string solved." + << std::endl; + } + } + } + } + } + else if (nrole == role_string_prefix || nrole == role_string_suffix) + { + // check if each return value is a prefix/suffix of all open examples + if (!x.isReturnValueModified() || x.d_has_string_pos == nrole) + { + std::map<Node, std::vector<unsigned> > incr; + bool isPrefix = nrole == role_string_prefix; + std::map<Node, unsigned> total_inc; + std::vector<Node> inc_strs; + std::map<Node, std::vector<Node> >::iterator itx = d_examples_out.find(c); + Assert(itx != d_examples_out.end()); + // make the value of the examples + std::vector<String> ex_vals; + x.getCurrentStrings(this, itx->second, ex_vals); + if (Trace.isOn("sygus-pbe-dt-debug")) + { + indent("sygus-pbe-dt-debug", ind); + Trace("sygus-pbe-dt-debug") << "current strings : " << std::endl; + for (unsigned i = 0, size = ex_vals.size(); i < size; i++) + { + indent("sygus-pbe-dt-debug", ind + 1); + Trace("sygus-pbe-dt-debug") << ex_vals[i] << std::endl; + } + } + + // check if there is a value for which is a prefix/suffix of all active + // examples + Assert(einfo.d_enum_vals.size() == einfo.d_enum_vals_res.size()); + + for (unsigned i = 0, size = einfo.d_enum_vals.size(); i < size; i++) + { + Node val_t = einfo.d_enum_vals[i]; + indent("sygus-pbe-dt-debug", ind); + Trace("sygus-pbe-dt-debug") << "increment string values : " << val_t + << " : "; + Assert(einfo.d_enum_vals_res[i].size() == itx->second.size()); + unsigned tot = 0; + bool exsuccess = x.getStringIncrement(this, + isPrefix, + ex_vals, + einfo.d_enum_vals_res[i], + incr[val_t], + tot); + if (!exsuccess) + { + incr.erase(val_t); + Trace("sygus-pbe-dt-debug") << "...fail" << std::endl; + } + else + { + total_inc[val_t] = tot; + inc_strs.push_back(val_t); + Trace("sygus-pbe-dt-debug") << "...success, total increment = " << tot + << std::endl; + } + } + + if (!incr.empty()) + { + ret_dt = constructBestStringToConcat(inc_strs, total_inc, incr, x); + Assert(!ret_dt.isNull()); + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") << "PBE: CONCAT strategy : choose " + << (isPrefix ? "pre" : "suf") << "fix value " + << d_tds->sygusToBuiltin(ret_dt) << std::endl; + // update the context + bool ret = x.updateStringPosition(this, incr[ret_dt]); + AlwaysAssert(ret == (total_inc[ret_dt] > 0)); + x.d_has_string_pos = nrole; + }else{ + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") << "PBE: failed CONCAT strategy, no values are " + << (isPrefix ? "pre" : "suf") + << "fix of all examples." << std::endl; + } + } + else + { + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") + << "PBE: failed CONCAT strategy, prefix/suffix mismatch." + << std::endl; + } + } + if (ret_dt.isNull() && !einfo.isTemplated()) + { + // we will try a single strategy + EnumTypeInfoStrat* etis = nullptr; + std::map<NodeRole, StrategyNode>::iterator itsn = + tinfo.d_snodes.find(nrole); + if (itsn != tinfo.d_snodes.end()) + { + // strategy info + StrategyNode& snode = itsn->second; + if (x.d_visit_role[e].find(nrole) == x.d_visit_role[e].end()) + { + x.d_visit_role[e][nrole] = true; + // try a random strategy + if (snode.d_strats.size() > 1) + { + std::random_shuffle(snode.d_strats.begin(), snode.d_strats.end()); + } + // get an eligible strategy index + unsigned sindex = 0; + while (sindex < snode.d_strats.size() + && !x.isValidStrategy(snode.d_strats[sindex])) + { + sindex++; + } + // if we found a eligible strategy + if (sindex < snode.d_strats.size()) + { + etis = snode.d_strats[sindex]; + } + } + } + if (etis != nullptr) + { + StrategyType strat = etis->d_this; + indent("sygus-pbe-dt", ind + 1); + Trace("sygus-pbe-dt") << "...try STRATEGY " << strat << "..." + << std::endl; + + std::map<unsigned, Node> look_ahead_solved_children; + std::vector<Node> dt_children_cons; + bool success = true; + + // for ITE + Node split_cond_enum; + int split_cond_res_index = -1; + + for (unsigned sc = 0, size = etis->d_cenum.size(); sc < size; sc++) + { + indent("sygus-pbe-dt", ind + 1); + Trace("sygus-pbe-dt") << "construct PBE child #" << sc << "..." + << std::endl; + Node rec_c; + std::map<unsigned, Node>::iterator itla = + look_ahead_solved_children.find(sc); + if (itla != look_ahead_solved_children.end()) + { + rec_c = itla->second; + indent("sygus-pbe-dt-debug", ind + 1); + Trace("sygus-pbe-dt-debug") << "ConstructPBE: look ahead solved : " + << d_tds->sygusToBuiltin(rec_c) + << std::endl; + } + else + { + std::pair<Node, NodeRole>& cenum = etis->d_cenum[sc]; + + // update the context + std::vector<Node> prev; + if (strat == strat_ITE && sc > 0) + { + std::map<Node, EnumInfo>::iterator itnc = + d_einfo.find(split_cond_enum); + Assert(itnc != d_einfo.end()); + Assert(split_cond_res_index >= 0); + Assert(split_cond_res_index + < (int)itnc->second.d_enum_vals_res.size()); + prev = x.d_vals; + bool ret = x.updateContext( + this, + itnc->second.d_enum_vals_res[split_cond_res_index], + sc == 1); + AlwaysAssert(ret); + } + + // recurse + if (strat == strat_ITE && sc == 0) + { + Node ce = cenum.first; + + // register the condition enumerator + std::map<Node, EnumInfo>::iterator itnc = d_einfo.find(ce); + Assert(itnc != d_einfo.end()); + EnumInfo& einfo_child = itnc->second; + + // only used if the return value is not modified + if (!x.isReturnValueModified()) + { + if (x.d_uinfo.find(ce) == x.d_uinfo.end()) + { + Trace("sygus-pbe-dt-debug2") + << " reg : PBE: Look for direct solutions for conditional " + "enumerator " + << ce << " ... " << std::endl; + Assert(einfo_child.d_enum_vals.size() + == einfo_child.d_enum_vals_res.size()); + for (unsigned i = 1; i <= 2; i++) + { + std::pair<Node, NodeRole>& te_pair = etis->d_cenum[i]; + Node te = te_pair.first; + std::map<Node, EnumInfo>::iterator itnt = d_einfo.find(te); + Assert(itnt != d_einfo.end()); + bool branch_pol = (i == 1); + // for each condition, get terms that satisfy it in this + // branch + for (unsigned k = 0, size = einfo_child.d_enum_vals.size(); + k < size; + k++) + { + Node cond = einfo_child.d_enum_vals[k]; + std::vector<Node> solved; + itnt->second.d_term_trie.getSubsumedBy( + this, + einfo_child.d_enum_vals_res[k], + branch_pol, + solved); + Trace("sygus-pbe-dt-debug2") + << " reg : PBE: " << d_tds->sygusToBuiltin(cond) + << " has " << solved.size() << " solutions in branch " + << i << std::endl; + if (!solved.empty()) + { + Node slv = constructBestSolvedTerm(solved, x); + Trace("sygus-pbe-dt-debug2") + << " reg : PBE: ..." << d_tds->sygusToBuiltin(slv) + << " is a solution under branch " << i; + Trace("sygus-pbe-dt-debug2") + << " of condition " << d_tds->sygusToBuiltin(cond) + << std::endl; + x.d_uinfo[ce].d_look_ahead_sols[cond][i] = slv; + } + } + } + } + } + + // get the conditionals in the current context : they must be + // distinguishable + std::map<int, std::vector<Node> > possible_cond; + std::map<Node, int> solved_cond; // stores branch + einfo_child.d_term_trie.getLeaves( + this, x.d_vals, true, possible_cond); + + std::map<int, std::vector<Node> >::iterator itpc = + possible_cond.find(0); + if (itpc != possible_cond.end()) + { + if (Trace.isOn("sygus-pbe-dt-debug")) + { + indent("sygus-pbe-dt-debug", ind + 1); + Trace("sygus-pbe-dt-debug") + << "PBE : We have " << itpc->second.size() + << " distinguishable conditionals:" << std::endl; + for (Node& cond : itpc->second) + { + indent("sygus-pbe-dt-debug", ind + 2); + Trace("sygus-pbe-dt-debug") << d_tds->sygusToBuiltin(cond) + << std::endl; + } + } + + // static look ahead conditional : choose conditionals that have + // solved terms in at least one branch + // only applicable if we have not modified the return value + std::map<int, std::vector<Node> > solved_cond; + if (!x.isReturnValueModified()) + { + Assert(x.d_uinfo.find(ce) != x.d_uinfo.end()); + int solve_max = 0; + for (Node& cond : itpc->second) + { + std::map<Node, std::map<unsigned, Node> >::iterator itla = + x.d_uinfo[ce].d_look_ahead_sols.find(cond); + if (itla != x.d_uinfo[ce].d_look_ahead_sols.end()) + { + int nsolved = itla->second.size(); + solve_max = nsolved > solve_max ? nsolved : solve_max; + solved_cond[nsolved].push_back(cond); + } + } + int n = solve_max; + while (n > 0) + { + if (!solved_cond[n].empty()) + { + rec_c = constructBestSolvedConditional(solved_cond[n], x); + indent("sygus-pbe-dt", ind + 1); + Trace("sygus-pbe-dt") + << "PBE: ITE strategy : choose solved conditional " + << d_tds->sygusToBuiltin(rec_c) << " with " << n + << " solved children..." << std::endl; + std::map<Node, std::map<unsigned, Node> >::iterator itla = + x.d_uinfo[ce].d_look_ahead_sols.find(rec_c); + Assert(itla != x.d_uinfo[ce].d_look_ahead_sols.end()); + for (std::pair<const unsigned, Node>& las : itla->second) + { + look_ahead_solved_children[las.first] = las.second; + } + break; + } + n--; + } + } + + // otherwise, guess a conditional + if (rec_c.isNull()) + { + rec_c = constructBestConditional(itpc->second, x); + Assert(!rec_c.isNull()); + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") + << "PBE: ITE strategy : choose random conditional " + << d_tds->sygusToBuiltin(rec_c) << std::endl; + } + } + else + { + // TODO (#1250) : degenerate case where children have different + // types? + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") << "return PBE: failed ITE strategy, " + "cannot find a distinguishable condition" + << std::endl; + } + if( !rec_c.isNull() ){ + Assert(einfo_child.d_enum_val_to_index.find(rec_c) + != einfo_child.d_enum_val_to_index.end()); + split_cond_res_index = einfo_child.d_enum_val_to_index[rec_c]; + split_cond_enum = ce; + Assert(split_cond_res_index >= 0); + Assert(split_cond_res_index + < (int)einfo_child.d_enum_vals_res.size()); + } + } + else + { + rec_c = constructSolution(c, cenum.first, cenum.second, x, ind + 2); + } + + // undo update the context + if (strat == strat_ITE && sc > 0) + { + x.d_vals = prev; + } + } + if (!rec_c.isNull()) + { + dt_children_cons.push_back(rec_c); + } + else + { + success = false; + break; + } + } + if (success) + { + Assert(dt_children_cons.size() == etis->d_sol_templ_args.size()); + // ret_dt = NodeManager::currentNM()->mkNode( APPLY_CONSTRUCTOR, + // dt_children ); + ret_dt = etis->d_sol_templ; + ret_dt = ret_dt.substitute(etis->d_sol_templ_args.begin(), + etis->d_sol_templ_args.end(), + dt_children_cons.begin(), + dt_children_cons.end()); + indent("sygus-pbe-dt-debug", ind); + Trace("sygus-pbe-dt-debug") + << "PBE: success : constructed for strategy " << strat << std::endl; + }else{ + indent("sygus-pbe-dt-debug", ind); + Trace("sygus-pbe-dt-debug") << "PBE: failed for strategy " << strat + << std::endl; + } + } + } + + if( !ret_dt.isNull() ){ + Assert( ret_dt.getType()==e.getType() ); + } + indent("sygus-pbe-dt", ind); + Trace("sygus-pbe-dt") << "ConstructPBE: returned " << ret_dt << std::endl; + return ret_dt; +} + +bool CegConjecturePbe::UnifContext::updateContext( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol ) { + Assert( d_vals.size()==vals.size() ); + bool changed = false; + Node poln = pol ? pbe->d_true : pbe->d_false; + for( unsigned i=0; i<vals.size(); i++ ){ + if( vals[i]!=poln ){ + if( d_vals[i]==pbe->d_true ){ + d_vals[i] = pbe->d_false; + changed = true; + } + } + } + if (changed) + { + d_visit_role.clear(); + } + return changed; +} + +bool CegConjecturePbe::UnifContext::updateStringPosition( CegConjecturePbe * pbe, std::vector< unsigned >& pos ) { + Assert( pos.size()==d_str_pos.size() ); + bool changed = false; + for( unsigned i=0; i<pos.size(); i++ ){ + if( pos[i]>0 ){ + d_str_pos[i] += pos[i]; + changed = true; + } + } + if (changed) + { + d_visit_role.clear(); + } + return changed; +} + +bool CegConjecturePbe::UnifContext::isReturnValueModified() { + if (d_has_string_pos != role_invalid) + { + return true; + } + return false; +} + +bool CegConjecturePbe::UnifContext::isValidStrategy(EnumTypeInfoStrat* etis) +{ + StrategyType st = etis->d_this; + if (d_has_string_pos == role_string_prefix && st == strat_CONCAT_SUFFIX) + { + return false; + } + if (d_has_string_pos == role_string_suffix && st == strat_CONCAT_PREFIX) + { + return false; + } + return true; +} + +void CegConjecturePbe::UnifContext::initialize( CegConjecturePbe * pbe, Node c ) { + Assert( d_vals.empty() ); + Assert( d_str_pos.empty() ); + + // initialize with #examples + Assert( pbe->d_examples.find( c )!=pbe->d_examples.end() ); + unsigned sz = pbe->d_examples[c].size(); + for( unsigned i=0; i<sz; i++ ){ + d_vals.push_back( pbe->d_true ); + } + + if( !pbe->d_examples_out[c].empty() ){ + // output type of the examples + TypeNode exotn = pbe->d_examples_out[c][0].getType(); + + if( exotn.isString() ){ + for( unsigned i=0; i<sz; i++ ){ + d_str_pos.push_back( 0 ); + } + } + } + d_visit_role.clear(); +} + +void CegConjecturePbe::UnifContext::getCurrentStrings( + CegConjecturePbe* pbe, + const std::vector<Node>& vals, + std::vector<String>& ex_vals) +{ + bool isPrefix = d_has_string_pos == role_string_prefix; + String dummy; + for( unsigned i=0; i<vals.size(); i++ ){ + if( d_vals[i]==pbe->d_true ){ + Assert( vals[i].isConst() ); + unsigned pos_value = d_str_pos[i]; + if( pos_value>0 ){ + Assert(d_has_string_pos != role_invalid); + String s = vals[i].getConst<String>(); + Assert( pos_value<=s.size() ); + ex_vals.push_back( isPrefix ? s.suffix( s.size()-pos_value ) : + s.prefix( s.size()-pos_value ) ); + }else{ + ex_vals.push_back( vals[i].getConst<String>() ); + } + }else{ + // irrelevant, add dummy + ex_vals.push_back( dummy ); + } + } +} + +bool CegConjecturePbe::UnifContext::getStringIncrement( + CegConjecturePbe* pbe, + bool isPrefix, + const std::vector<String>& ex_vals, + const std::vector<Node>& vals, + std::vector<unsigned>& inc, + unsigned& tot) +{ + for( unsigned j=0; j<vals.size(); j++ ){ + unsigned ival = 0; + if( d_vals[j]==pbe->d_true ){ + // example is active in this context + Assert( vals[j].isConst() ); + String mystr = vals[j].getConst<String>(); + ival = mystr.size(); + if( mystr.size()<=ex_vals[j].size() ){ + if( !( isPrefix ? ex_vals[j].strncmp(mystr, ival) : ex_vals[j].rstrncmp(mystr, ival) ) ){ + Trace("sygus-pbe-dt-debug") << "X"; + return false; + } + }else{ + Trace("sygus-pbe-dt-debug") << "X"; + return false; + } + } + Trace("sygus-pbe-dt-debug") << ival; + tot += ival; + inc.push_back( ival ); + } + return true; +} +bool CegConjecturePbe::UnifContext::isStringSolved( + CegConjecturePbe* pbe, + const std::vector<String>& ex_vals, + const std::vector<Node>& vals) +{ + for( unsigned j=0; j<vals.size(); j++ ){ + if( d_vals[j]==pbe->d_true ){ + // example is active in this context + Assert( vals[j].isConst() ); + String mystr = vals[j].getConst<String>(); + if( ex_vals[j]!=mystr ){ + return false; + } + } + } + return true; +} + +CegConjecturePbe::StrategyNode::~StrategyNode() +{ + for (unsigned j = 0, size = d_strats.size(); j < size; j++) + { + delete d_strats[j]; + } + d_strats.clear(); +} +} +} +} diff --git a/src/theory/quantifiers/sygus/sygus_pbe.h b/src/theory/quantifiers/sygus/sygus_pbe.h new file mode 100644 index 000000000..ce1f2bf5e --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_pbe.h @@ -0,0 +1,802 @@ +/********************* */ +/*! \file ce_guided_pbe.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2016 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief utility for processing programming by examples synthesis conjectures + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_PBE_H +#define __CVC4__THEORY__QUANTIFIERS__CE_GUIDED_PBE_H + +#include "context/cdhashmap.h" +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +/** roles for enumerators + * + * This indicates the role of an enumerator that is allocated by approaches + * for synthesis-by-unification (see details below). + * io : the enumerator should enumerate values that are overall solutions + * for the function-to-synthesize, + * ite_condition : the enumerator should enumerate values that are useful + * in ite conditions in the ITE strategy, + * concat_term : the enumerator should enumerate values that are used as + * components of string concatenation solutions. + */ +enum EnumRole +{ + enum_invalid, + enum_io, + enum_ite_condition, + enum_concat_term, +}; +std::ostream& operator<<(std::ostream& os, EnumRole r); + +/** roles for strategy nodes + * + * This indicates the role of a strategy node, which is a subprocedure of + * CegConjecturePbe::constructSolution (see details below). + * equal : the node constructed must be equal to the overall solution for + * the function-to-synthesize, + * string_prefix/suffix : the node constructed must be a prefix/suffix + * of the function-to-synthesize, + * ite_condition : the node constructed must be a condition that makes some + * active input examples true and some input examples false. + */ +enum NodeRole +{ + role_invalid, + role_equal, + role_string_prefix, + role_string_suffix, + role_ite_condition, +}; +std::ostream& operator<<(std::ostream& os, NodeRole r); + +/** enumerator role for node role */ +EnumRole getEnumeratorRoleForNodeRole(NodeRole r); + +/** strategy types + * + * This indicates a strategy for synthesis-by-unification (see details below). + * ITE : strategy for constructing if-then-else solutions via decision + * tree learning techniques, + * CONCAT_PREFIX/SUFFIX : strategy for constructing string concatenation + * solutions via a divide and conquer approach, + * ID : identity strategy used for calling strategies on child type through + * an identity function. + */ +enum StrategyType +{ + strat_INVALID, + strat_ITE, + strat_CONCAT_PREFIX, + strat_CONCAT_SUFFIX, + strat_ID, +}; +std::ostream& operator<<(std::ostream& os, StrategyType st); + +class CegConjecture; + +/** CegConjecturePbe +* +* This class implements optimizations that target synthesis conjectures +* that are in Programming-By-Examples (PBE) form. +* +* [EX#1] An example of a synthesis conjecture in PBE form is : +* exists f. forall x. +* ( x = 0 => f( x ) = 2 ) ^ ( x = 5 => f( x ) = 7 ) ^ ( x = 6 => f( x ) = 8 ) +* +* We say that the above conjecture has I/O examples (0)->2, (5)->7, (6)->8. +* +* Internally, this class does the following for SyGuS inputs: +* +* (1) Infers whether the input conjecture is in PBE form or not. +* (2) Based on this information and on the syntactic restrictions, it +* devises a strategy for enumerating terms and construction solutions, +* which is inspired by Alur et al. "Scaling Enumerative Program Synthesis +* via Divide and Conquer" TACAS 2017. In particular, it may consider +* strategies for constructing decision trees when the grammar permits ITEs +* and a strategy for divide-and-conquer string synthesis when the grammar +* permits string concatenation. This is stored in a set of data structures +* within d_cinfo. +* (3) It makes (possibly multiple) calls to +* TermDatabaseSygus::registerMeasuredTerm(...) based +* on the strategy, which inform the rest of the system to enumerate values +* of particular types in the grammar through use of fresh variables which +* we call "enumerators". +* +* Points (1)-(3) happen within a call to CegConjecturePbe::initialize(...). +* +* Notice that each enumerator is associated with a single +* function-to-synthesize, but a function-to-sythesize may be mapped to multiple +* enumerators. Some public functions of this class expect an enumerator as +* input, which we map to a function-to-synthesize via +* TermDatabaseSygus::getSynthFunFor(e). +* +* An enumerator is initially "active" but may become inactive if the enumeration +* exhausts all possible values in the datatype corresponding to syntactic +* restrictions for it. The search may continue unless all enumerators become +* inactive. +* +* (4) During search, the extension of quantifier-free datatypes procedure for +* SyGuS datatypes may ask this class whether current candidates can be +* discarded based on +* inferring when two candidate solutions are equivalent up to examples. +* For example, the candidate solutions: +* f = \x ite( x<0, x+1, x ) and f = \x x +* are equivalent up to examples on the above conjecture, since they have the +* same value on the points x = 0,5,6. Hence, we need only consider one of +* them. The interface for querying this is +* CegConjecturePbe::addSearchVal(...). +* For details, see Reynolds et al. SYNT 2017. +* +* (5) When the extension of quantifier-free datatypes procedure for SyGuS +* datatypes terminates with a model, the parent of this class calls +* CegConjecturePbe::getCandidateList(...), where this class returns the list +* of active enumerators. +* (6) The parent class subsequently calls +* CegConjecturePbe::constructValues(...), which +* informs this class that new values have been enumerated for active +* enumerators, as indicated by the current model. This call also requests +* that based on these +* newly enumerated values, whether this class is now able to construct a +* solution based on the high-level strategy (stored in d_c_info). +* +* This class is not designed to work in incremental mode, since there is no way +* to specify incremental problems in SyguS. +*/ +class CegConjecturePbe { + public: + CegConjecturePbe(QuantifiersEngine* qe, CegConjecture* p); + ~CegConjecturePbe(); + + /** initialize this class + * + * n is the "base instantiation" of the deep-embedding version of + * the synthesis conjecture under "candidates". + * (see CegConjecture::d_base_inst) + * + * This function may add lemmas to the vector lemmas corresponding + * to initial lemmas regarding static analysis of enumerators it + * introduced. For example, we may say that the top-level symbol + * of an enumerator is not ITE if it is being used to construct + * return values for decision trees. + */ + void initialize(Node n, + std::vector<Node>& candidates, + std::vector<Node>& lemmas); + /** get candidate list + * Adds all active enumerators associated with functions-to-synthesize in + * candidates to clist. + */ + void getCandidateList(std::vector<Node>& candidates, + std::vector<Node>& clist); + /** construct candidates + * (1) Indicates that the list of enumerators in "enums" currently have model + * values "enum_values". + * (2) Asks whether based on these new enumerated values, we can construct a + * solution for + * the functions-to-synthesize in "candidates". If so, this function + * returns "true" and + * adds solutions for candidates into "candidate_values". + * During this class, this class may add auxiliary lemmas to "lems", which the + * caller should send on the output channel via lemma(...). + */ + bool constructCandidates(std::vector<Node>& enums, + std::vector<Node>& enum_values, + std::vector<Node>& candidates, + std::vector<Node>& candidate_values, + std::vector<Node>& lems); + /** is PBE enabled for any enumerator? */ + bool isPbe() { return d_is_pbe; } + /** is the enumerator e associated with I/O example pairs? */ + bool hasExamples(Node e); + /** get number of I/O example pairs for enumerator e */ + unsigned getNumExamples(Node e); + /** get the input arguments for i^th I/O example for e, which is added to the + * vector ex */ + void getExample(Node e, unsigned i, std::vector<Node>& ex); + /** get the output value of the i^th I/O example for enumerator e */ + Node getExampleOut(Node e, unsigned i); + + /** add the search val + * This function is called by the extension of quantifier-free datatypes + * procedure for SyGuS datatypes when we are considering a value of + * enumerator e of sygus type tn whose analog in the signature of builtin + * theory is bvr. + * + * For example, bvr = x + 1 when e is the datatype value Plus( x(), One() ) and + * tn is a sygus datatype that encodes a subsignature of the integers. + * + * This returns either: + * - A SyGuS term whose analog is equivalent to bvr up to examples + * In the above example, + * it may return a term t of the form Plus( One(), x() ), such that this + * function was previously called with t as input. + * - e, indicating that no previous terms are equivalent to e up to examples. + */ + Node addSearchVal(TypeNode tn, Node e, Node bvr); + /** evaluate builtin + * This returns the evaluation of bn on the i^th example for the + * function-to-synthesis + * associated with enumerator e. If there are not at least i examples, it + * returns the rewritten form of bn. + * For example, if bn = x+5, e is an enumerator for f in the above example + * [EX#1], then + * evaluateBuiltin( tn, bn, e, 0 ) = 7 + * evaluateBuiltin( tn, bn, e, 1 ) = 9 + * evaluateBuiltin( tn, bn, e, 2 ) = 10 + */ + Node evaluateBuiltin(TypeNode tn, Node bn, Node e, unsigned i); + + private: + /** quantifiers engine associated with this class */ + QuantifiersEngine* d_qe; + /** sygus term database of d_qe */ + quantifiers::TermDbSygus * d_tds; + /** true and false nodes */ + Node d_true; + Node d_false; + /** A reference to the conjecture that owns this class. */ + CegConjecture* d_parent; + /** is this a PBE conjecture for any function? */ + bool d_is_pbe; + /** for each candidate variable f (a function-to-synthesize), whether the + * conjecture is purely PBE for that variable + * In other words, all occurrences of f are guarded by equalities that + * constraint its arguments to constants. + */ + std::map< Node, bool > d_examples_invalid; + /** for each candidate variable (function-to-synthesize), whether the + * conjecture is purely PBE for that variable. + * An example of a conjecture for which d_examples_invalid is false but + * d_examples_out_invalid is true is: + * exists f. forall x. ( x = 0 => f( x ) > 2 ) + * another example is: + * exists f. forall x. ( ( x = 0 => f( x ) = 2 ) V ( x = 3 => f( x ) = 3 ) ) + * since the formula is not a conjunction (the example values are not + * entailed). + * However, the domain of f in both cases is finite, which can be used for + * search space pruning. + */ + std::map< Node, bool > d_examples_out_invalid; + /** for each candidate variable (function-to-synthesize), input of I/O + * examples */ + std::map< Node, std::vector< std::vector< Node > > > d_examples; + /** for each candidate variable (function-to-synthesize), output of I/O + * examples */ + std::map< Node, std::vector< Node > > d_examples_out; + /** the list of example terms + * For the example [EX#1] above, this is f( 0 ), f( 5 ), f( 6 ) + */ + std::map< Node, std::vector< Node > > d_examples_term; + /** collect the PBE examples in n + * This is called on the input conjecture, and will populate the above vectors. + * hasPol/pol denote the polarity of n in the conjecture. + */ + void collectExamples( Node n, std::map< Node, bool >& visited, bool hasPol, bool pol ); + + //--------------------------------- PBE search values + /** this class is an index of candidate solutions for PBE synthesis */ + class PbeTrie { + public: + PbeTrie() {} + ~PbeTrie() {} + Node d_lazy_child; + std::map<Node, PbeTrie> d_children; + void clear() { d_children.clear(); } + Node addPbeExample(TypeNode etn, Node e, Node b, CegConjecturePbe* cpbe, + unsigned index, unsigned ntotal); + + private: + Node addPbeExampleEval(TypeNode etn, Node e, Node b, std::vector<Node>& ex, + CegConjecturePbe* cpbe, unsigned index, + unsigned ntotal); + }; + /** trie of candidate solutions tried + * This stores information for each (enumerator, type), + * where type is a type in the grammar of the space of solutions for a subterm + * of e. This is used for symmetry breaking in quantifier-free reasoning + * about SyGuS datatypes. + */ + std::map<Node, std::map<TypeNode, PbeTrie> > d_pbe_trie; + //--------------------------------- end PBE search values + + // -------------------------------- decision tree learning + // index filter + class IndexFilter { + public: + IndexFilter(){} + void mk( std::vector< Node >& vals, bool pol = true ); + std::map< unsigned, unsigned > d_next; + unsigned start(); + unsigned next( unsigned i ); + void clear() { d_next.clear(); } + bool isEq( std::vector< Node >& vs, Node v ); + }; + // subsumption trie + class SubsumeTrie { + public: + SubsumeTrie(){} + // adds term to the trie, removes based on subsumption + Node addTerm( CegConjecturePbe * pbe, Node t, std::vector< Node >& vals, bool pol, std::vector< Node >& subsumed, IndexFilter * f = NULL ); + // adds condition to the trie (does not do subsumption) + Node addCond( CegConjecturePbe * pbe, Node c, std::vector< Node >& vals, bool pol, IndexFilter * f = NULL ); + // returns the set of terms that are subsets of vals + void getSubsumed( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::vector< Node >& subsumed, IndexFilter * f = NULL ); + // returns the set of terms that are supersets of vals + void getSubsumedBy( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::vector< Node >& subsumed_by, IndexFilter * f = NULL ); + // v[-1,1,0] -> children always false, always true, both + void getLeaves( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::map< int, std::vector< Node > >& v, IndexFilter * f = NULL ); + /** is this trie empty? */ + bool isEmpty() { return d_term.isNull() && d_children.empty(); } + /** clear this trie */ + void clear() { + d_term = Node::null(); + d_children.clear(); + } + + private: + /** the term at this node */ + Node d_term; + /** the children nodes of this trie */ + std::map<Node, SubsumeTrie> d_children; + /** helper function for above functions */ + Node addTermInternal(CegConjecturePbe* pbe, + Node t, + std::vector<Node>& vals, + bool pol, + std::vector<Node>& subsumed, + bool spol, + IndexFilter* f, + unsigned index, + int status, + bool checkExistsOnly, + bool checkSubsume); + /** helper function for above functions */ + void getLeavesInternal(CegConjecturePbe* pbe, + std::vector<Node>& vals, + bool pol, + std::map<int, std::vector<Node> >& v, + IndexFilter* f, + unsigned index, + int status); + }; + // -------------------------------- end decision tree learning + + //------------------------------ representation of a enumeration strategy + + /** information about an enumerator + * + * We say an enumerator is a master enumerator if it is the variable that + * we use to enumerate values for its sort. Master enumerators may have + * (possibly multiple) slave enumerators, stored in d_enum_slave, + */ + class EnumInfo { + public: + EnumInfo() : d_role(enum_io), d_is_conditional(false) {} + /** initialize this class + * c is the parent function-to-synthesize + * role is the "role" the enumerator plays in the high-level strategy, + * which is one of enum_* above. + */ + void initialize(Node c, EnumRole role); + /** is this enumerator associated with a template? */ + bool isTemplated() { return !d_template.isNull(); } + /** set conditional + * + * This flag is set to true if this enumerator may not apply to all + * input/output examples. For example, if this enumerator is used + * as an output value beneath a conditional in an instance of strat_ITE, + * then this enumerator is conditional. + */ + void setConditional() { d_is_conditional = true; } + /** is conditional */ + bool isConditional() { return d_is_conditional; } + void addEnumValue(CegConjecturePbe* pbe, + Node v, + std::vector<Node>& results); + void setSolved(Node slv); + bool isSolved() { return !d_enum_solved.isNull(); } + Node getSolved() { return d_enum_solved; } + EnumRole getRole() { return d_role; } + Node d_parent_candidate; + // for template + Node d_template; + Node d_template_arg; + + Node d_active_guard; + std::vector<Node> d_enum_slave; + /** values we have enumerated */ + std::vector<Node> d_enum_vals; + /** + * This either stores the values of f( I ) for inputs + * or the value of f( I ) = O if d_role==enum_io + */ + std::vector<std::vector<Node> > d_enum_vals_res; + std::vector<Node> d_enum_subsume; + std::map<Node, unsigned> d_enum_val_to_index; + SubsumeTrie d_term_trie; + + private: + /** + * Whether an enumerated value for this conjecture has solved the entire + * conjecture. + */ + Node d_enum_solved; + /** the role of this enumerator (one of enum_* above). */ + EnumRole d_role; + /** is this enumerator conditional */ + bool d_is_conditional; + }; + /** maps enumerators to the information above */ + std::map< Node, EnumInfo > d_einfo; + + class CandidateInfo; + + /** represents a strategy for a SyGuS datatype type + * + * This represents a possible strategy to apply when processing a strategy + * node in constructSolution. When applying the strategy represented by this + * class, we may make recursive calls to the children of the strategy, + * given in d_cenum. If all recursive calls to constructSolution are + * successful, say: + * constructSolution( c, d_cenum[1], ... ) = t1, + * ..., + * constructSolution( c, d_cenum[n], ... ) = tn, + * Then, the solution returned by this strategy is + * d_sol_templ * { d_sol_templ_args -> (t1,...,tn) } + */ + class EnumTypeInfoStrat { + public: + /** the type of strategy this represents */ + StrategyType d_this; + /** the sygus datatype constructor that induced this strategy + * + * For example, this may be a sygus datatype whose sygus operator is ITE, + * if the strategy type above is strat_ITE. + */ + Node d_cons; + /** children of this strategy */ + std::vector<std::pair<Node, NodeRole> > d_cenum; + /** the arguments for the (templated) solution */ + std::vector<Node> d_sol_templ_args; + /** the template for the solution */ + Node d_sol_templ; + }; + + /** represents a node in the strategy graph + * + * It contains a list of possible strategies which are tried during calls + * to constructSolution. + */ + class StrategyNode + { + public: + StrategyNode() {} + ~StrategyNode(); + /** the set of strategies to try at this node in the strategy graph */ + std::vector<EnumTypeInfoStrat*> d_strats; + }; + + /** stores enumerators and strategies for a SyGuS datatype type */ + class EnumTypeInfo { + public: + EnumTypeInfo() : d_parent( NULL ){} + /** the parent candidate info (see below) */ + CandidateInfo * d_parent; + /** the type that this information is for */ + TypeNode d_this_type; + /** map from enum roles to enumerators for this type */ + std::map<EnumRole, Node> d_enum; + /** map from node roles to strategy nodes */ + std::map<NodeRole, StrategyNode> d_snodes; + }; + + /** stores strategy and enumeration information for a function-to-synthesize + */ + class CandidateInfo { + public: + CandidateInfo() : d_check_sol( false ), d_cond_count( 0 ){} + Node d_this_candidate; + /** + * The root sygus datatype for the function-to-synthesize, + * which encodes the overall syntactic restrictions on the space + * of solutions. + */ + TypeNode d_root; + /** Info for sygus datatype type occurring in a field of d_root */ + std::map< TypeNode, EnumTypeInfo > d_tinfo; + /** list of all enumerators for the function-to-synthesize */ + std::vector< Node > d_esym_list; + /** + * Maps sygus datatypes to their search enumerator. This is the (single) + * enumerator of that type that we enumerate values for. + */ + std::map< TypeNode, Node > d_search_enum; + bool d_check_sol; + unsigned d_cond_count; + Node d_solution; + void initialize( Node c ); + void initializeType( TypeNode tn ); + Node getRootEnumerator(); + bool isNonTrivial(); + }; + /** maps a function-to-synthesize to the above information */ + std::map< Node, CandidateInfo > d_cinfo; + + //------------------------------ representation of an enumeration strategy + /** add enumerated value + * + * We have enumerated the value v for x. This function adds x->v to the + * relevant data structures that are used for strategy-specific construction + * of solutions when necessary, and returns a set of lemmas, which are added + * to the input argument lems. These lemmas are used to rule out models where + * x = v, to force that a new value is enumerated for x. + */ + void addEnumeratedValue( Node x, Node v, std::vector< Node >& lems ); + /** domain-specific enumerator exclusion techniques + * + * Returns true if the value v for x can be excluded based on a + * domain-specific exclusion technique like the ones below. + * + * c : the candidate variable that x is enumerating for, + * results : the values of v under the input examples of c, + * ei : the enumerator information for x, + * exp : if this function returns true, then exp contains a (possibly + * generalize) explanation for why v can be excluded. + */ + bool getExplanationForEnumeratorExclude( Node c, Node x, Node v, std::vector< Node >& results, EnumInfo& ei, std::vector< Node >& exp ); + /** returns true if we can exlude values of x based on negative str.contains + * + * Values v for x may be excluded if we realize that the value of v under the + * substitution for some input example will never be contained in some output + * example. For details on this technique, see NegContainsSygusInvarianceTest + * in sygus_invariance.h. + * + * This function depends on whether x is being used to enumerate values + * for any node that is conditional in the strategy graph. For example, + * nodes that are children of ITE strategy nodes are conditional. If any node + * is conditional, then this function returns false. + */ + bool useStrContainsEnumeratorExclude(Node x, EnumInfo& ei); + /** cache for the above function */ + std::map<Node, bool> d_use_str_contains_eexc; + + //------------------------------ strategy registration + /** collect enumerator types + * + * This builds the strategy for enumerated values of type tn for the given + * role of nrole, for solutions to function-to-synthesize c. + */ + void collectEnumeratorTypes(Node c, TypeNode tn, NodeRole nrole); + /** register enumerator + * + * This registers that et is an enumerator for function-to-synthesize c + * of type tn, having enumerator role enum_role. + * + * inSearch is whether we will enumerate values based on this enumerator. + * A strategy node is represented by a (enumerator, node role) pair. Hence, + * we may use enumerators for which this flag is false to represent strategy + * nodes that have child strategies. + */ + void registerEnumerator( + Node et, Node c, TypeNode tn, EnumRole enum_role, bool inSearch); + /** infer template */ + bool inferTemplate(unsigned k, + Node n, + std::map<Node, unsigned>& templ_var_index, + std::map<unsigned, unsigned>& templ_injection); + /** static learn redundant operators + * + * This learns static lemmas for pruning enumerative space based on the + * strategy for the function-to-synthesize c, and stores these into lemmas. + */ + void staticLearnRedundantOps(Node c, std::vector<Node>& lemmas); + /** helper for static learn redundant operators + * + * (e, nrole) specify the strategy node in the graph we are currently + * analyzing, visited stores the nodes we have already visited. + * + * This method builds the mapping needs_cons, which maps (master) enumerators + * to a map from the constructors that it needs. + * + * ind is the depth in the strategy graph we are at (for debugging). + * + * isCond is whether the current enumerator is conditional (beneath a + * conditional of an strat_ITE strategy). + */ + void staticLearnRedundantOps( + Node c, + Node e, + NodeRole nrole, + std::map<Node, std::map<NodeRole, bool> >& visited, + std::map<Node, std::map<unsigned, bool> >& needs_cons, + int ind, + bool isCond); + //------------------------------ end strategy registration + + //------------------------------ constructing solutions + class UnifContext { + public: + UnifContext() : d_has_string_pos(role_invalid) {} + /** this intiializes this context for function-to-synthesize c */ + void initialize(CegConjecturePbe* pbe, Node c); + + //----------for ITE strategy + /** the value of the context conditional + * + * This stores a list of Boolean constants that is the same length of the + * number of input/output example pairs we are considering. For each i, + * if d_vals[i] = true, i/o pair #i is active according to this context + * if d_vals[i] = false, i/o pair #i is inactive according to this context + */ + std::vector<Node> d_vals; + /** update the examples + * + * if pol=true, this method updates d_vals to d_vals & vals + * if pol=false, this method updates d_vals to d_vals & ( ~vals ) + */ + bool updateContext(CegConjecturePbe* pbe, std::vector<Node>& vals, bool pol); + //----------end for ITE strategy + + //----------for CONCAT strategies + /** the position in the strings + * + * For each i/o example pair, this stores the length of the current solution + * for the input of the pair, where the solution for that input is a prefix + * or + * suffix of the output of the pair. For example, if our i/o pairs are: + * f( "abcd" ) = "abcdcd" + * f( "aa" ) = "aacd" + * If the solution we have currently constructed is str.++( x1, "c", ... ), + * then d_str_pos = ( 5, 3 ), where notice that + * str.++( "abc", "c" ) is a prefix of "abcdcd" and + * str.++( "aa", "c" ) is a prefix of "aacd". + */ + std::vector<unsigned> d_str_pos; + /** has string position + * + * Whether the solution positions indicate a prefix or suffix of the output + * examples. If this is role_invalid, then we have not updated the string + * position. + */ + NodeRole d_has_string_pos; + /** update the string examples + * + * This method updates d_str_pos to d_str_pos + pos. + */ + bool updateStringPosition(CegConjecturePbe* pbe, std::vector<unsigned>& pos); + /** get current strings + * + * This returns the prefix/suffix of the string constants stored in vals + * of size d_str_pos, and stores the result in ex_vals. For example, if vals + * is (abcdcd", "aacde") and d_str_pos = ( 5, 3 ), then we add + * "d" and "de" to ex_vals. + */ + void getCurrentStrings(CegConjecturePbe* pbe, + const std::vector<Node>& vals, + std::vector<String>& ex_vals); + /** get string increment + * + * If this method returns true, then inc and tot are updated such that + * for all active indices i, + * vals[i] is a prefix (or suffix if isPrefix=false) of ex_vals[i], and + * inc[i] = str.len(vals[i]) + * for all inactive indices i, inc[i] = 0 + * We set tot to the sum of inc[i] for i=1,...,n. This indicates the total + * number of characters incremented across all examples. + */ + bool getStringIncrement(CegConjecturePbe* pbe, + bool isPrefix, + const std::vector<String>& ex_vals, + const std::vector<Node>& vals, + std::vector<unsigned>& inc, + unsigned& tot); + /** returns true if ex_vals[i] = vals[i] for all active indices i. */ + bool isStringSolved(CegConjecturePbe* pbe, + const std::vector<String>& ex_vals, + const std::vector<Node>& vals); + //----------end for CONCAT strategies + + /** is return value modified? + * + * This returns true if we are currently in a state where the return value + * of the solution has been modified, e.g. by a previous node that solved + * for a prefix. + */ + bool isReturnValueModified(); + /** returns true if argument is valid strategy in this context */ + bool isValidStrategy(EnumTypeInfoStrat* etis); + /** visited role + * + * This is the current set of enumerator/node role pairs we are currently + * visiting. This set is cleared when the context is updated. + */ + std::map<Node, std::map<NodeRole, bool> > d_visit_role; + + /** unif context enumerator information */ + class UEnumInfo + { + public: + UEnumInfo() {} + /** map from conditions and branch positions to a solved node + * + * For example, if we have: + * f( 1 ) = 2 ^ f( 3 ) = 4 ^ f( -1 ) = 1 + * Then, valid entries in this map is: + * d_look_ahead_sols[x>0][1] = x+1 + * d_look_ahead_sols[x>0][2] = 1 + * For the first entry, notice that for all input examples such that x>0 + * evaluates to true, which are (1) and (3), we have that their output + * values for x+1 under the substitution that maps x to the input value, + * resulting in 2 and 4, are equal to the output value for the respective + * pairs. + */ + std::map<Node, std::map<unsigned, Node> > d_look_ahead_sols; + }; + /** map from enumerators to the above info class */ + std::map< Node, UEnumInfo > d_uinfo; + }; + + /** construct solution + * + * This method tries to construct a solution for function-to-synthesize c + * based on the strategy stored for c in d_cinfo, which may include + * synthesis-by-unification approaches for ite and string concatenation terms. + * These approaches include the work of Alur et al. TACAS 2017. + * If it cannot construct a solution, it returns the null node. + */ + Node constructSolution( Node c ); + /** helper function for construct solution. + * + * Construct a solution based on enumerator e for function-to-synthesize c + * with node role nrole in context x. + * + * ind is the term depth of the context (for debugging). + */ + Node constructSolution( + Node c, Node e, NodeRole nrole, UnifContext& x, int ind); + /** Heuristically choose the best solved term from solved in context x, + * currently return the first. */ + Node constructBestSolvedTerm( std::vector< Node >& solved, UnifContext& x ); + /** Heuristically choose the best solved string term from solved in context + * x, currently return the first. */ + Node constructBestStringSolvedTerm( std::vector< Node >& solved, UnifContext& x ); + /** Heuristically choose the best solved conditional term from solved in + * context x, currently random */ + Node constructBestSolvedConditional( std::vector< Node >& solved, UnifContext& x ); + /** Heuristically choose the best conditional term from conds in context x, + * currently random */ + Node constructBestConditional( std::vector< Node >& conds, UnifContext& x ); + /** Heuristically choose the best string to concatenate from strs to the + * solution in context x, currently random + * incr stores the vector of indices that are incremented by this solution in + * example outputs. + * total_inc[x] is the sum of incr[x] for each x in strs. + */ + Node constructBestStringToConcat( std::vector< Node > strs, + std::map< Node, unsigned > total_inc, + std::map< Node, std::vector< unsigned > > incr, + UnifContext& x ); + //------------------------------ end constructing solutions +}; + +}/* namespace CVC4::theory::quantifiers */ +}/* namespace CVC4::theory */ +}/* namespace CVC4 */ + +#endif diff --git a/src/theory/quantifiers/sygus/sygus_process_conj.cpp b/src/theory/quantifiers/sygus/sygus_process_conj.cpp new file mode 100644 index 000000000..a961c9780 --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_process_conj.cpp @@ -0,0 +1,798 @@ +/********************* */ +/*! \file sygus_process_conj.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief Implementation of techniqures for static preprocessing and analysis + ** of sygus conjectures. + **/ +#include "theory/quantifiers/sygus/sygus_process_conj.h" + +#include <stack> + +#include "expr/datatype.h" +#include "theory/quantifiers/sygus/term_database_sygus.h" +#include "theory/quantifiers/term_util.h" + +using namespace CVC4::kind; +using namespace std; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +void CegConjectureProcessFun::init(Node f) +{ + d_synth_fun = f; + Assert(f.getType().isFunction()); + + // initialize the arguments + std::unordered_map<TypeNode, unsigned, TypeNodeHashFunction> + type_to_init_deq_id; + std::vector<Type> argTypes = + static_cast<FunctionType>(f.getType().toType()).getArgTypes(); + for (unsigned j = 0; j < argTypes.size(); j++) + { + TypeNode atn = TypeNode::fromType(argTypes[j]); + std::stringstream ss; + ss << "a" << j; + Node k = NodeManager::currentNM()->mkBoundVar(ss.str(), atn); + d_arg_vars.push_back(k); + d_arg_var_num[k] = j; + d_arg_props.push_back(CegConjectureProcessArg()); + } +} + +bool CegConjectureProcessFun::checkMatch( + Node cn, Node n, std::unordered_map<unsigned, Node>& n_arg_map) +{ + std::vector<Node> vars; + std::vector<Node> subs; + for (std::unordered_map<unsigned, Node>::iterator it = n_arg_map.begin(); + it != n_arg_map.end(); + ++it) + { + Assert(it->first < d_arg_vars.size()); + Assert( + it->second.getType().isComparableTo(d_arg_vars[it->first].getType())); + vars.push_back(d_arg_vars[it->first]); + subs.push_back(it->second); + } + Node cn_subs = + cn.substitute(vars.begin(), vars.end(), subs.begin(), subs.end()); + cn_subs = Rewriter::rewrite(cn_subs); + Assert(Rewriter::rewrite(n) == n); + return cn_subs == n; +} + +bool CegConjectureProcessFun::isArgVar(Node n, unsigned& arg_index) +{ + if (n.isVar()) + { + std::unordered_map<Node, unsigned, NodeHashFunction>::iterator ita = + d_arg_var_num.find(n); + if (ita != d_arg_var_num.end()) + { + arg_index = ita->second; + return true; + } + } + return false; +} + +Node CegConjectureProcessFun::inferDefinition( + Node n, + std::unordered_map<Node, unsigned, NodeHashFunction>& term_to_arg_carry, + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction>& free_vars) +{ + std::unordered_map<TNode, Node, TNodeHashFunction> visited; + std::unordered_map<TNode, Node, TNodeHashFunction>::iterator it; + std::stack<TNode> visit; + TNode cur; + visit.push(n); + do + { + cur = visit.top(); + visit.pop(); + it = visited.find(cur); + if (it == visited.end()) + { + // if it is ground, we can use it + if (free_vars[cur].empty()) + { + visited[cur] = cur; + } + else + { + // if it is term used by another argument, use it + std::unordered_map<Node, unsigned, NodeHashFunction>::iterator itt = + term_to_arg_carry.find(cur); + if (itt != term_to_arg_carry.end()) + { + visited[cur] = d_arg_vars[itt->second]; + } + else if (cur.getNumChildren() > 0) + { + // try constructing children + visited[cur] = Node::null(); + visit.push(cur); + for (unsigned i = 0; i < cur.getNumChildren(); i++) + { + visit.push(cur[i]); + } + } + else + { + return Node::null(); + } + } + } + else if (it->second.isNull()) + { + Node ret = cur; + bool childChanged = false; + std::vector<Node> children; + if (cur.getMetaKind() == kind::metakind::PARAMETERIZED) + { + children.push_back(cur.getOperator()); + } + for (unsigned i = 0; i < cur.getNumChildren(); i++) + { + it = visited.find(cur[i]); + Assert(it != visited.end()); + Assert(!it->second.isNull()); + childChanged = childChanged || cur[i] != it->second; + children.push_back(it->second); + } + if (childChanged) + { + ret = NodeManager::currentNM()->mkNode(cur.getKind(), children); + } + visited[cur] = ret; + } + } while (!visit.empty()); + Assert(visited.find(n) != visited.end()); + Assert(!visited.find(n)->second.isNull()); + return visited[n]; +} + +unsigned CegConjectureProcessFun::assignRelevantDef(Node def, + std::vector<unsigned>& args) +{ + unsigned id = 0; + if (def.isNull()) + { + // prefer one that already has a definition, if one exists + for (unsigned j = 0; j < args.size(); j++) + { + unsigned i = args[j]; + if (!d_arg_props[i].d_template.isNull()) + { + id = j; + break; + } + } + } + unsigned rid = args[id]; + // for merging previously equivalent definitions + std::unordered_map<Node, unsigned, NodeHashFunction> prev_defs; + for (unsigned j = 0; j < args.size(); j++) + { + unsigned i = args[j]; + Trace("sygus-process-arg-deps") << " ...processed arg #" << i; + if (!d_arg_props[i].d_template.isNull()) + { + if (d_arg_props[i].d_template == def) + { + // definition was consistent + } + else + { + Node t = d_arg_props[i].d_template; + std::unordered_map<Node, unsigned, NodeHashFunction>::iterator itt = + prev_defs.find(t); + if (itt != prev_defs.end()) + { + // merge previously equivalent definitions + d_arg_props[i].d_template = d_arg_vars[itt->second]; + Trace("sygus-process-arg-deps") + << " (merged equivalent def from argument "; + Trace("sygus-process-arg-deps") << itt->second << ")." << std::endl; + } + else + { + // store this as previous + prev_defs[t] = i; + // now must be relevant + d_arg_props[i].d_relevant = true; + Trace("sygus-process-arg-deps") + << " (marked relevant, overwrite definition)." << std::endl; + } + } + } + else + { + if (def.isNull()) + { + if (i != rid) + { + // marked as relevant, but template can be set equal to master + d_arg_props[i].d_template = d_arg_vars[rid]; + Trace("sygus-process-arg-deps") << " (new definition, map to master " + << d_arg_vars[rid] << ")." + << std::endl; + } + else + { + d_arg_props[i].d_relevant = true; + Trace("sygus-process-arg-deps") << " (marked relevant)." << std::endl; + } + } + else + { + // has new definition + d_arg_props[i].d_template = def; + Trace("sygus-process-arg-deps") << " (new definition " << def << ")." + << std::endl; + } + } + } + return rid; +} + +void CegConjectureProcessFun::processTerms( + std::vector<Node>& ns, + std::vector<Node>& ks, + Node nf, + std::unordered_set<Node, NodeHashFunction>& synth_fv, + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction>& free_vars) +{ + Assert(ns.size() == ks.size()); + Trace("sygus-process-arg-deps") << "Process " << ns.size() + << " applications of " << d_synth_fun << "..." + << std::endl; + + // get the relevant variables + // relevant variables are those that appear in the body of the conjunction + std::unordered_set<Node, NodeHashFunction> rlv_vars; + Assert(free_vars.find(nf) != free_vars.end()); + rlv_vars = free_vars[nf]; + + // get the single occurrence variables + // single occurrence variables are those that appear in only one position, + // as an argument to the function-to-synthesize. + std::unordered_map<Node, bool, NodeHashFunction> single_occ_variables; + for (unsigned index = 0; index < ns.size(); index++) + { + Node n = ns[index]; + for (unsigned i = 0; i < n.getNumChildren(); i++) + { + Node nn = n[i]; + if (nn.isVar()) + { + std::unordered_map<Node, bool, NodeHashFunction>::iterator its = + single_occ_variables.find(nn); + if (its == single_occ_variables.end()) + { + // only irrelevant variables + single_occ_variables[nn] = rlv_vars.find(nn) == rlv_vars.end(); + } + else + { + single_occ_variables[nn] = false; + } + } + else + { + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction>::iterator itf = free_vars.find(nn); + Assert(itf != free_vars.end()); + for (std::unordered_set<Node, NodeHashFunction>::iterator itfv = + itf->second.begin(); + itfv != itf->second.end(); + ++itfv) + { + single_occ_variables[*itfv] = false; + } + } + } + } + + // update constant argument information + for (unsigned index = 0; index < ns.size(); index++) + { + Node n = ns[index]; + Trace("sygus-process-arg-deps") + << " Analyze argument information for application #" << index << ": " + << n << std::endl; + + // in the following, we say an argument a "carries" a term t if + // the function to synthesize would use argument a to construct + // the term t in its definition. + + // map that assumes all arguments carry their respective term + std::unordered_map<unsigned, Node> n_arg_map; + // terms to the argument that is carrying it. + // the arguments in the range of this map must be marked as relevant. + std::unordered_map<Node, unsigned, NodeHashFunction> term_to_arg_carry; + // map of terms to (unprocessed) arguments where it occurs + std::unordered_map<Node, std::vector<unsigned>, NodeHashFunction> + term_to_args; + + // initialize + for (unsigned a = 0; a < n.getNumChildren(); a++) + { + n_arg_map[a] = n[a]; + } + + for (unsigned a = 0; a < n.getNumChildren(); a++) + { + bool processed = false; + if (d_arg_props[a].d_relevant) + { + // we can assume all relevant arguments carry their terms + processed = true; + Trace("sygus-process-arg-deps") << " ...processed arg #" << a + << " (already relevant)." << std::endl; + if (term_to_arg_carry.find(n[a]) == term_to_arg_carry.end()) + { + Trace("sygus-process-arg-deps") << " carry " << n[a] + << " by argument #" << a << std::endl; + term_to_arg_carry[n[a]] = a; + } + } + else + { + // first, check if single occurrence variable + // check if an irrelevant variable + if (n[a].isVar() && synth_fv.find(n[a]) != synth_fv.end()) + { + Assert(single_occ_variables.find(n[a]) != single_occ_variables.end()); + // may be able to make this more precise? + // check if a single-occurrence variable + if (single_occ_variables[n[a]]) + { + // if we do not already have a template definition, or the + // template is a single occurrence variable + if (d_arg_props[a].d_template.isNull() + || d_arg_props[a].d_var_single_occ) + { + processed = true; + Trace("sygus-process-arg-deps") << " ...processed arg #" << a; + Trace("sygus-process-arg-deps") + << " (single occurrence variable "; + Trace("sygus-process-arg-deps") << n[a] << ")." << std::endl; + d_arg_props[a].d_var_single_occ = true; + d_arg_props[a].d_template = n[a]; + } + } + } + if (!processed && !d_arg_props[a].d_template.isNull() + && !d_arg_props[a].d_var_single_occ) + { + // argument already has a definition, see if it is maintained + if (checkMatch(d_arg_props[a].d_template, n[a], n_arg_map)) + { + processed = true; + Trace("sygus-process-arg-deps") << " ...processed arg #" << a; + Trace("sygus-process-arg-deps") << " (consistent definition " + << n[a]; + Trace("sygus-process-arg-deps") + << " with " << d_arg_props[a].d_template << ")." << std::endl; + } + } + } + if (!processed) + { + // otherwise, add it to the list of arguments for this term + term_to_args[n[a]].push_back(a); + } + } + + Trace("sygus-process-arg-deps") << " Look at argument terms..." + << std::endl; + + // list of all arguments + std::vector<Node> arg_list; + // now look at the terms for unprocessed arguments + for (std::unordered_map<Node, std::vector<unsigned>, NodeHashFunction>:: + iterator it = term_to_args.begin(); + it != term_to_args.end(); + ++it) + { + Node nn = it->first; + arg_list.push_back(nn); + if (Trace.isOn("sygus-process-arg-deps")) + { + Trace("sygus-process-arg-deps") << " argument " << nn; + Trace("sygus-process-arg-deps") << " (" << it->second.size() + << " positions)"; + // check the status of this term + if (nn.isVar() && synth_fv.find(nn) != synth_fv.end()) + { + // is it relevant? + if (rlv_vars.find(nn) != rlv_vars.end()) + { + Trace("sygus-process-arg-deps") << " is a relevant variable." + << std::endl; + } + else + { + Trace("sygus-process-arg-deps") << " is an irrelevant variable." + << std::endl; + } + } + else + { + // this can be more precise + Trace("sygus-process-arg-deps") << " is a relevant term." + << std::endl; + } + } + } + + unsigned arg_list_counter = 0; + // sort arg_list by term size? + + while (arg_list_counter < arg_list.size()) + { + Node infer_def_t; + do + { + infer_def_t = Node::null(); + // see if we can infer a definition + for (std::unordered_map<Node, std::vector<unsigned>, NodeHashFunction>:: + iterator it = term_to_args.begin(); + it != term_to_args.end(); + ++it) + { + Node def = inferDefinition(it->first, term_to_arg_carry, free_vars); + if (!def.isNull()) + { + Trace("sygus-process-arg-deps") << " *** Inferred definition " + << def << " for " << it->first + << std::endl; + // assign to each argument + assignRelevantDef(def, it->second); + // term_to_arg_carry[it->first] = rid; + infer_def_t = it->first; + break; + } + } + if (!infer_def_t.isNull()) + { + term_to_args.erase(infer_def_t); + } + } while (!infer_def_t.isNull()); + + // decide to make an argument relevant + bool success = false; + while (arg_list_counter < arg_list.size() && !success) + { + Node curr = arg_list[arg_list_counter]; + std::unordered_map<Node, std::vector<unsigned>, NodeHashFunction>:: + iterator it = term_to_args.find(curr); + if (it != term_to_args.end()) + { + Trace("sygus-process-arg-deps") << " *** Decide relevant " << curr + << std::endl; + // assign relevant to each + Node null_def; + unsigned rid = assignRelevantDef(null_def, it->second); + term_to_arg_carry[curr] = rid; + Trace("sygus-process-arg-deps") + << " carry " << curr << " by argument #" << rid << std::endl; + term_to_args.erase(curr); + success = true; + } + arg_list_counter++; + } + } + } +} + +bool CegConjectureProcessFun::isArgRelevant(unsigned i) +{ + return d_arg_props[i].d_relevant; +} + +void CegConjectureProcessFun::getIrrelevantArgs( + std::unordered_set<unsigned>& args) +{ + for (unsigned i = 0; i < d_arg_vars.size(); i++) + { + if (!d_arg_props[i].d_relevant) + { + args.insert(i); + } + } +} + +CegConjectureProcess::CegConjectureProcess(QuantifiersEngine* qe) {} +CegConjectureProcess::~CegConjectureProcess() {} +Node CegConjectureProcess::preSimplify(Node q) +{ + Trace("sygus-process") << "Pre-simplify conjecture : " << q << std::endl; + return q; +} + +Node CegConjectureProcess::postSimplify(Node q) +{ + Trace("sygus-process") << "Post-simplify conjecture : " << q << std::endl; + Assert(q.getKind() == FORALL); + + // initialize the information about each function to synthesize + for (unsigned i = 0; i < q[0].getNumChildren(); i++) + { + Node f = q[0][i]; + if (f.getType().isFunction()) + { + d_sf_info[f].init(f); + } + } + + // get the base on the conjecture + Node base = q[1]; + std::unordered_set<Node, NodeHashFunction> synth_fv; + if (base.getKind() == NOT && base[0].getKind() == FORALL) + { + for (unsigned j = 0; j < base[0][0].getNumChildren(); j++) + { + synth_fv.insert(base[0][0][j]); + } + base = base[0][1]; + } + std::vector<Node> conjuncts; + getComponentVector(AND, base, conjuncts); + + // process the conjunctions + for (std::map<Node, CegConjectureProcessFun>::iterator it = d_sf_info.begin(); + it != d_sf_info.end(); + ++it) + { + Node f = it->first; + for (unsigned i = 0; i < conjuncts.size(); i++) + { + processConjunct(conjuncts[i], f, synth_fv); + } + } + + return q; +} + +void CegConjectureProcess::initialize(Node n, std::vector<Node>& candidates) +{ + if (Trace.isOn("sygus-process")) + { + Trace("sygus-process") << "Process conjecture : " << n + << " with candidates: " << std::endl; + for (unsigned i = 0; i < candidates.size(); i++) + { + Trace("sygus-process") << " " << candidates[i] << std::endl; + } + } +} + +bool CegConjectureProcess::isArgRelevant(Node f, unsigned i) +{ + std::map<Node, CegConjectureProcessFun>::iterator its = d_sf_info.find(f); + if (its != d_sf_info.end()) + { + return its->second.isArgRelevant(i); + } + Assert(false); + return true; +} + +bool CegConjectureProcess::getIrrelevantArgs(Node f, + std::unordered_set<unsigned>& args) +{ + std::map<Node, CegConjectureProcessFun>::iterator its = d_sf_info.find(f); + if (its != d_sf_info.end()) + { + its->second.getIrrelevantArgs(args); + return true; + } + return false; +} + +void CegConjectureProcess::processConjunct( + Node n, Node f, std::unordered_set<Node, NodeHashFunction>& synth_fv) +{ + Trace("sygus-process-arg-deps") << "Process conjunct: " << std::endl; + Trace("sygus-process-arg-deps") << " " << n << " for synth fun " << f + << "..." << std::endl; + + // first, flatten the conjunct + // make a copy of free variables since we may add new ones + std::unordered_set<Node, NodeHashFunction> synth_fv_n = synth_fv; + std::unordered_map<Node, Node, NodeHashFunction> defs; + Node nf = flatten(n, f, synth_fv_n, defs); + + Trace("sygus-process-arg-deps") << "Flattened to: " << std::endl; + Trace("sygus-process-arg-deps") << " " << nf << std::endl; + + // get free variables in nf + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction> + free_vars; + getFreeVariables(nf, synth_fv_n, free_vars); + // get free variables in each application + std::vector<Node> ns; + std::vector<Node> ks; + for (std::unordered_map<Node, Node, NodeHashFunction>::iterator it = + defs.begin(); + it != defs.end(); + ++it) + { + getFreeVariables(it->second, synth_fv_n, free_vars); + ns.push_back(it->second); + ks.push_back(it->first); + } + + // process the applications of synthesis functions + if (!ns.empty()) + { + std::map<Node, CegConjectureProcessFun>::iterator its = d_sf_info.find(f); + if (its != d_sf_info.end()) + { + its->second.processTerms(ns, ks, nf, synth_fv_n, free_vars); + } + } +} + +Node CegConjectureProcess::CegConjectureProcess::flatten( + Node n, + Node f, + std::unordered_set<Node, NodeHashFunction>& synth_fv, + std::unordered_map<Node, Node, NodeHashFunction>& defs) +{ + std::unordered_map<Node, Node, NodeHashFunction> visited; + std::unordered_map<Node, Node, NodeHashFunction>::iterator it; + std::stack<Node> visit; + Node cur; + visit.push(n); + do + { + cur = visit.top(); + visit.pop(); + it = visited.find(cur); + + if (it == visited.end()) + { + visited[cur] = Node::null(); + visit.push(cur); + for (unsigned i = 0; i < cur.getNumChildren(); i++) + { + visit.push(cur[i]); + } + } + else if (it->second.isNull()) + { + Node ret = cur; + bool childChanged = false; + std::vector<Node> children; + if (cur.getMetaKind() == kind::metakind::PARAMETERIZED) + { + children.push_back(cur.getOperator()); + } + for (unsigned i = 0; i < cur.getNumChildren(); i++) + { + it = visited.find(cur[i]); + Assert(it != visited.end()); + Assert(!it->second.isNull()); + childChanged = childChanged || cur[i] != it->second; + children.push_back(it->second); + } + if (childChanged) + { + ret = NodeManager::currentNM()->mkNode(cur.getKind(), children); + } + // is it the function to synthesize? + if (cur.getKind() == APPLY_UF && cur.getOperator() == f) + { + // if so, flatten + Node k = NodeManager::currentNM()->mkBoundVar("vf", cur.getType()); + defs[k] = ret; + ret = k; + synth_fv.insert(k); + } + // post-rewrite + visited[cur] = ret; + } + } while (!visit.empty()); + Assert(visited.find(n) != visited.end()); + Assert(!visited.find(n)->second.isNull()); + return visited[n]; +} + +void CegConjectureProcess::getFreeVariables( + Node n, + std::unordered_set<Node, NodeHashFunction>& synth_fv, + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction>& free_vars) +{ + // first must compute free variables in each subterm of n, + // as well as contains_synth_fun + std::unordered_map<Node, bool, NodeHashFunction> visited; + std::unordered_map<Node, bool, NodeHashFunction>::iterator it; + std::stack<Node> visit; + Node cur; + visit.push(n); + do + { + cur = visit.top(); + visit.pop(); + it = visited.find(cur); + + if (it == visited.end()) + { + visited[cur] = false; + visit.push(cur); + for (unsigned i = 0; i < cur.getNumChildren(); i++) + { + visit.push(cur[i]); + } + } + else if (!it->second) + { + free_vars[cur].clear(); + if (synth_fv.find(cur) != synth_fv.end()) + { + // it is a free variable + free_vars[cur].insert(cur); + } + else + { + // otherwise, carry the free variables from the children + for (unsigned i = 0; i < cur.getNumChildren(); i++) + { + free_vars[cur].insert(free_vars[cur[i]].begin(), + free_vars[cur[i]].end()); + } + } + visited[cur] = true; + } + } while (!visit.empty()); +} + +Node CegConjectureProcess::getSymmetryBreakingPredicate( + Node x, Node e, TypeNode tn, unsigned tindex, unsigned depth) +{ + return Node::null(); +} + +void CegConjectureProcess::debugPrint(const char* c) {} +void CegConjectureProcess::getComponentVector(Kind k, + Node n, + std::vector<Node>& args) +{ + if (n.getKind() == k) + { + for (unsigned i = 0; i < n.getNumChildren(); i++) + { + args.push_back(n[i]); + } + } + else + { + args.push_back(n); + } +} + +} /* namespace CVC4::theory::quantifiers */ +} /* namespace CVC4::theory */ +} /* namespace CVC4 */ diff --git a/src/theory/quantifiers/sygus/sygus_process_conj.h b/src/theory/quantifiers/sygus/sygus_process_conj.h new file mode 100644 index 000000000..0b9a25532 --- /dev/null +++ b/src/theory/quantifiers/sygus/sygus_process_conj.h @@ -0,0 +1,365 @@ +/********************* */ +/*! \file sygus_process_conj.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief Techniqures for static preprocessing and analysis of + ** sygus conjectures. + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__SYGUS_PROCESS_CONJ_H +#define __CVC4__THEORY__QUANTIFIERS__SYGUS_PROCESS_CONJ_H + +#include <map> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "expr/node.h" +#include "expr/type_node.h" +#include "theory/quantifiers_engine.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +/** This file contains techniques that compute + * argument relevancy for synthesis functions + * + * Let F be a synthesis conjecture of the form: + * exists f. forall X. P( f, X ) + * + * The classes below compute whether certain arguments of + * the function-to-synthesize f are irrelevant. + * Assume that f is a binary function, where possible solutions + * to the above conjecture are of the form: + * f -> (lambda (xy) t[x,y]) + * We say e.g. that the 2nd argument of f is irrelevant if we + * can determine: + * F has a solution + * if and only if + * F has a solution of the form f -> (lambda (xy) t[x] ) + * We conclude that arguments are irrelevant using the following + * techniques. + * + * + * (1) Argument invariance: + * + * Let s[z] be a term whose free variables are contained in { z }. + * If all occurrences of f-applications in F are of the form: + * f(t, s[t]) + * then: + * f = (lambda (xy) r[x,y]) + * is a solution to F only if: + * f = (lambda (xy) r[x,s[x]]) + * is as well. + * Hence the second argument of f is not relevant. + * + * + * (2) Variable irrelevance: + * + * If F is equivalent to: + * exists f. forall w z u1...un. C1 ^...^Cm, + * where for i=1...m, Ci is of the form: + * ( w1 = f(tm1[z], u1) ^ + * ... ^ + * wn = f(tmn[z], un) ) => Pm(w1...wn, z) + * then the second argument of f is irrelevant. + * We call u1...un single occurrence variables + * (in Ci). + * + * + * TODO (#1210) others, generalize (2)? + * + */ + +/** This structure stores information regarding + * an argument of a function to synthesize. + * + * It is used to store whether the argument + * position in the function to synthesize is + * relevant. + */ +class CegConjectureProcessArg +{ + public: + CegConjectureProcessArg() : d_var_single_occ(false), d_relevant(false) {} + /** template definition + * This is the term s[z] described + * under "Argument Invariance" above. + */ + Node d_template; + /** single occurrence + * Whether we are trying to show this argument + * is irrelevant by "Variable irrelevance" + * described above. + */ + bool d_var_single_occ; + /** whether this argument is relevant + * An argument is marked as relevant if: + * (A) it is explicitly marked as relevant + * due to a function application containing + * a relevant term at this argument position, or + * (B) if it is given conflicting template definitions. + */ + bool d_relevant; +}; + +/** This structure stores information regarding conjecture-specific +* analysis of a single function to synthesize within +* a conjecture to synthesize. +* +* It maintains information about each of the function to +* synthesize's arguments. +*/ +struct CegConjectureProcessFun +{ + public: + CegConjectureProcessFun() {} + ~CegConjectureProcessFun() {} + /** initialize this class for function f */ + void init(Node f); + /** process terms + * + * This is called once per conjunction in + * the synthesis conjecture. + * + * ns are the f-applications to process, + * ks are the variables we introduced to flatten them, + * nf is the flattened form of our conjecture to process, + * free_vars maps all subterms of n and nf to the set + * of variables (in set synth_fv) they contain. + * + * This updates information regarding which arguments + * of the function-to-synthesize are relevant. + */ + void processTerms( + std::vector<Node>& ns, + std::vector<Node>& ks, + Node nf, + std::unordered_set<Node, NodeHashFunction>& synth_fv, + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction>& free_vars); + /** is the i^th argument of the function-to-synthesize of this class relevant? + */ + bool isArgRelevant(unsigned i); + /** get irrelevant arguments for the function-to-synthesize of this class */ + void getIrrelevantArgs(std::unordered_set<unsigned>& args); + + private: + /** the synth fun associated with this */ + Node d_synth_fun; + /** properties of each argument */ + std::vector<CegConjectureProcessArg> d_arg_props; + /** variables for each argument type of f + * + * These are used to express templates for argument + * invariance, in the data member + * CegConjectureProcessArg::d_template. + */ + std::vector<Node> d_arg_vars; + /** map from d_arg_vars to the argument # + * they represent. + */ + std::unordered_map<Node, unsigned, NodeHashFunction> d_arg_var_num; + /** check match + * This function returns true iff we can infer: + * cn * { x -> n_arg_map[d_arg_var_num[x]] | x in d_arg_vars } = n + * In other words, cn and n are equivalent + * via the substitution mapping argument variables to terms + * specified by n_arg_map. The rewriter is used for inferring + * this equivalence. + * + * For example, if n_arg_map contains { 1 -> t, 2 -> s }, then + * checkMatch( x1+x2, t+s, n_arg_map ) returns true, + * checkMatch( x1+1, t+1, n_arg_map ) returns true, + * checkMatch( 0, 0, n_arg_map ) returns true, + * checkMatch( x1+1, s, n_arg_map ) returns false. + */ + bool checkMatch(Node cn, + Node n, + std::unordered_map<unsigned, Node>& n_arg_map); + /** infer definition + * + * In the following, we say a term is a "template + * definition" if its free variables are a subset of d_arg_vars. + * + * If this function returns a non-null node ret, then + * checkMatch( ret, n, term_to_arg_carry^-1 ) returns true. + * and ret is a template definition. + * + * The free variables for all subterms of n are stored in + * free_vars. The map term_to_arg_carry is injective. + * + * For example, if term_to_arg_carry contains { t -> 1, s -> 2 } and + * free_vars is { t -> {y}, r -> {y}, s -> {}, q -> {}, ... -> {} }, then + * inferDefinition( 0, term_to_arg_carry, free_vars ) + * returns 0 + * inferDefinition( t, term_to_arg_carry, free_vars ) + * returns x1 + * inferDefinition( t+s+q, term_to_arg_carry, free_vars ) + * returns x1+x2+q + * inferDefinition( t+r, term_to_arg_carry, free_vars ) + * returns null + * + * Notice that multiple definitions are possible, e.g. above: + * inferDefinition( s, term_to_arg_carry, free_vars ) + * may return either s or x2 + * TODO (#1210) : try multiple definitions? + */ + Node inferDefinition( + Node n, + std::unordered_map<Node, unsigned, NodeHashFunction>& term_to_arg_carry, + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction>& free_vars); + /** Assign relevant definition + * + * If def is non-null, + * this function assigns def as a template definition + * for the argument positions in args. + * This is called when there exists a term of the form + * f( t1....tn ) + * in the synthesis conjecture that we are processing, + * where t_i = def * sigma for all i \in args, + * for some substitution sigma, where def is a template + * definition. + * + * If def is null, then there exists a term of the form + * f( t1....tn ) + * where t_i = s for for all i \in args, and s is not + * a template definition. In this case, at least one + * argument in args must be marked as a relevant + * argument position. + * + * Returns a value rid such that: + * (1) rid occurs in args, + * (2) if def is null, then argument rid was marked + * relevant by this call. + */ + unsigned assignRelevantDef(Node def, std::vector<unsigned>& args); + /** returns true if n is in d_arg_vars, updates arg_index + * to its position in d_arg_vars. + */ + bool isArgVar(Node n, unsigned& arg_index); +}; + +/** Ceg Conjecture Process +* +* This class implements static techniques for preprocessing and analysis of +* sygus conjectures. +* +* It is used as a back-end to CegConjecture, which calls it using the following +* interface: +* (1) When a sygus conjecture is asserted, we call +* CegConjectureProcess::simplify( q ), +* where q is the sygus conjecture in original form. +* +* (2) After a sygus conjecture is simplified and converted to deep +* embedding form, we call CegConjectureProcess::initialize( n, candidates ). +* +* (3) During enumerative SyGuS search, calls may be made by +* the extension of the quantifier-free datatypes decision procedure for +* sygus to CegConjectureProcess::getSymmetryBreakingPredicate(...), which are +* used for pruning search space based on conjecture-specific analysis. +*/ +class CegConjectureProcess +{ + public: + CegConjectureProcess(QuantifiersEngine* qe); + ~CegConjectureProcess(); + /** simplify the synthesis conjecture q + * Returns a formula that is equivalent to q. + * This simplification pass is called before all others + * in CegConjecture::assign. + * + * This function is intended for simplifications that + * impact whether or not the synthesis conjecture is + * single-invocation. + */ + Node preSimplify(Node q); + /** simplify the synthesis conjecture q + * Returns a formula that is equivalent to q. + * This simplification pass is called after all others + * in CegConjecture::assign. + */ + Node postSimplify(Node q); + /** initialize + * + * n is the "base instantiation" of the deep-embedding version of + * the synthesis conjecture under "candidates". + * (see CegConjecture::d_base_inst) + */ + void initialize(Node n, std::vector<Node>& candidates); + /** is the i^th argument of the function-to-synthesize f relevant? */ + bool isArgRelevant(Node f, unsigned i); + /** get irrelevant arguments for function-to-synthesize f + * returns true if f is a function-to-synthesize. + */ + bool getIrrelevantArgs(Node f, std::unordered_set<unsigned>& args); + /** get symmetry breaking predicate + * + * Returns a formula that restricts the enumerative search space (for a given + * depth) for a term x of sygus type tn whose top symbol is the tindex^{th} + * constructor, where x is a subterm of enumerator e. + */ + Node getSymmetryBreakingPredicate( + Node x, Node e, TypeNode tn, unsigned tindex, unsigned depth); + /** print out debug information about this conjecture */ + void debugPrint(const char* c); + private: + /** process conjunct + * + * This sets up initial information about functions to synthesize + * where n is a conjunct of the synthesis conjecture, and synth_fv + * is the set of (inner) universal variables in the synthesis + * conjecture. + */ + void processConjunct(Node n, + Node f, + std::unordered_set<Node, NodeHashFunction>& synth_fv); + /** flatten + * + * Flattens all applications of f in term n. + * This may add new variables to synth_fv, which + * are introduced at all positions of functions + * to synthesize in a bottom-up fashion. For each + * variable k introduced for a function application + * f(t), we add ( k -> f(t) ) to defs and ( f -> k ) + * to fun_to_defs. + */ + Node flatten(Node n, + Node f, + std::unordered_set<Node, NodeHashFunction>& synth_fv, + std::unordered_map<Node, Node, NodeHashFunction>& defs); + /** get free variables + * Constructs a map of all free variables that occur in n + * from synth_fv and stores them in the map free_vars. + */ + void getFreeVariables( + Node n, + std::unordered_set<Node, NodeHashFunction>& synth_fv, + std::unordered_map<Node, + std::unordered_set<Node, NodeHashFunction>, + NodeHashFunction>& free_vars); + /** for each synth-fun, information that is specific to this conjecture */ + std::map<Node, CegConjectureProcessFun> d_sf_info; + + /** get component vector */ + void getComponentVector(Kind k, Node n, std::vector<Node>& args); +}; + +} /* namespace CVC4::theory::quantifiers */ +} /* namespace CVC4::theory */ +} /* namespace CVC4 */ + +#endif diff --git a/src/theory/quantifiers/sygus/term_database_sygus.cpp b/src/theory/quantifiers/sygus/term_database_sygus.cpp new file mode 100644 index 000000000..b12a23c83 --- /dev/null +++ b/src/theory/quantifiers/sygus/term_database_sygus.cpp @@ -0,0 +1,1487 @@ +/********************* */ +/*! \file term_database_sygus.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief Implementation of term database sygus class + **/ + +#include "theory/quantifiers/sygus/term_database_sygus.h" + +#include "options/quantifiers_options.h" +#include "theory/arith/arith_msum.h" +#include "theory/quantifiers/quantifiers_attributes.h" +#include "theory/quantifiers/term_database.h" +#include "theory/quantifiers/term_util.h" +#include "theory/quantifiers_engine.h" + +using namespace std; +using namespace CVC4::kind; +using namespace CVC4::context; +using namespace CVC4::theory::inst; + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +TermDbSygus::TermDbSygus(context::Context* c, QuantifiersEngine* qe) + : d_quantEngine(qe), + d_syexp(new SygusExplain(this)), + d_ext_rw(new ExtendedRewriter(true)) +{ + d_true = NodeManager::currentNM()->mkConst( true ); + d_false = NodeManager::currentNM()->mkConst( false ); +} + +bool TermDbSygus::reset( Theory::Effort e ) { + return true; +} + +TNode TermDbSygus::getFreeVar( TypeNode tn, int i, bool useSygusType ) { + unsigned sindex = 0; + TypeNode vtn = tn; + if( useSygusType ){ + if( tn.isDatatype() ){ + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + if( !dt.getSygusType().isNull() ){ + vtn = TypeNode::fromType( dt.getSygusType() ); + sindex = 1; + } + } + } + while( i>=(int)d_fv[sindex][tn].size() ){ + std::stringstream ss; + if( tn.isDatatype() ){ + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + ss << "fv_" << dt.getName() << "_" << i; + }else{ + ss << "fv_" << tn << "_" << i; + } + Assert( !vtn.isNull() ); + Node v = NodeManager::currentNM()->mkSkolem( ss.str(), vtn, "for sygus normal form testing" ); + d_fv_stype[v] = tn; + d_fv_num[v] = i; + d_fv[sindex][tn].push_back( v ); + } + return d_fv[sindex][tn][i]; +} + +TNode TermDbSygus::getFreeVarInc( TypeNode tn, std::map< TypeNode, int >& var_count, bool useSygusType ) { + std::map< TypeNode, int >::iterator it = var_count.find( tn ); + if( it==var_count.end() ){ + var_count[tn] = 1; + return getFreeVar( tn, 0, useSygusType ); + }else{ + int index = it->second; + var_count[tn]++; + return getFreeVar( tn, index, useSygusType ); + } +} + +bool TermDbSygus::hasFreeVar( Node n, std::map< Node, bool >& visited ){ + if( visited.find( n )==visited.end() ){ + visited[n] = true; + if( isFreeVar( n ) ){ + return true; + } + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + if( hasFreeVar( n[i], visited ) ){ + return true; + } + } + } + return false; +} + +bool TermDbSygus::hasFreeVar( Node n ) { + std::map< Node, bool > visited; + return hasFreeVar( n, visited ); +} + +TypeNode TermDbSygus::getSygusTypeForVar( Node v ) { + Assert( d_fv_stype.find( v )!=d_fv_stype.end() ); + return d_fv_stype[v]; +} + +Node TermDbSygus::mkGeneric(const Datatype& dt, + unsigned c, + std::map<TypeNode, int>& var_count, + std::map<int, Node>& pre) +{ + Assert(c < dt.getNumConstructors()); + Assert( dt.isSygus() ); + Assert( !dt[c].getSygusOp().isNull() ); + std::vector< Node > children; + Node op = Node::fromExpr( dt[c].getSygusOp() ); + if( op.getKind()!=BUILTIN ){ + children.push_back( op ); + } + Trace("sygus-db-debug") << "mkGeneric " << dt.getName() << " " << op << " " << op.getKind() << "..." << std::endl; + for (unsigned i = 0, nargs = dt[c].getNumArgs(); i < nargs; i++) + { + TypeNode tna = getArgType( dt[c], i ); + Node a; + std::map< int, Node >::iterator it = pre.find( i ); + if( it!=pre.end() ){ + a = it->second; + }else{ + a = getFreeVarInc( tna, var_count, true ); + } + Trace("sygus-db-debug") + << " child " << i << " : " << a << " : " << a.getType() << std::endl; + Assert( !a.isNull() ); + children.push_back( a ); + } + Node ret; + if( op.getKind()==BUILTIN ){ + Trace("sygus-db-debug") << "Make builtin node..." << std::endl; + ret = NodeManager::currentNM()->mkNode( op, children ); + }else{ + Kind ok = getOperatorKind( op ); + Trace("sygus-db-debug") << "Operator kind is " << ok << std::endl; + if( children.size()==1 && ok==kind::UNDEFINED_KIND ){ + ret = children[0]; + }else{ + ret = NodeManager::currentNM()->mkNode( ok, children ); + } + } + Trace("sygus-db-debug") << "...returning " << ret << std::endl; + return ret; +} + +Node TermDbSygus::mkGeneric(const Datatype& dt, int c, std::map<int, Node>& pre) +{ + std::map<TypeNode, int> var_count; + return mkGeneric(dt, c, var_count, pre); +} + +Node TermDbSygus::sygusToBuiltin( Node n, TypeNode tn ) { + Assert( n.getType()==tn ); + Assert( tn.isDatatype() ); + std::map< Node, Node >::iterator it = d_sygus_to_builtin[tn].find( n ); + if( it==d_sygus_to_builtin[tn].end() ){ + Trace("sygus-db-debug") << "SygusToBuiltin : compute for " << n << ", type = " << tn << std::endl; + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + if( n.getKind()==APPLY_CONSTRUCTOR ){ + unsigned i = Datatype::indexOf( n.getOperator().toExpr() ); + Assert( n.getNumChildren()==dt[i].getNumArgs() ); + std::map< TypeNode, int > var_count; + std::map< int, Node > pre; + for (unsigned j = 0, size = n.getNumChildren(); j < size; j++) + { + pre[j] = sygusToBuiltin( n[j], getArgType( dt[i], j ) ); + } + Node ret = mkGeneric(dt, i, var_count, pre); + Trace("sygus-db-debug") << "SygusToBuiltin : Generic is " << ret << std::endl; + d_sygus_to_builtin[tn][n] = ret; + return ret; + } + if (n.hasAttribute(SygusPrintProxyAttribute())) + { + // this variable was associated by an attribute to a builtin node + return n.getAttribute(SygusPrintProxyAttribute()); + } + Assert(isFreeVar(n)); + // map to builtin variable type + int fv_num = getVarNum(n); + Assert(!dt.getSygusType().isNull()); + TypeNode vtn = TypeNode::fromType(dt.getSygusType()); + Node ret = getFreeVar(vtn, fv_num); + return ret; + }else{ + return it->second; + } +} + +Node TermDbSygus::sygusSubstituted( TypeNode tn, Node n, std::vector< Node >& args ) { + Assert( d_var_list[tn].size()==args.size() ); + return n.substitute( d_var_list[tn].begin(), d_var_list[tn].end(), args.begin(), args.end() ); +} + +unsigned TermDbSygus::getSygusTermSize( Node n ){ + if( n.getNumChildren()==0 ){ + return 0; + }else{ + Assert(n.getKind() == APPLY_CONSTRUCTOR); + unsigned sum = 0; + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + sum += getSygusTermSize( n[i] ); + } + const Datatype& dt = Datatype::datatypeOf(n.getOperator().toExpr()); + int cindex = Datatype::indexOf(n.getOperator().toExpr()); + Assert(cindex >= 0 && cindex < (int)dt.getNumConstructors()); + unsigned weight = dt[cindex].getWeight(); + return weight + sum; + } +} + +class ReqTrie { +public: + ReqTrie() : d_req_kind( UNDEFINED_KIND ){} + std::map< unsigned, ReqTrie > d_children; + Kind d_req_kind; + TypeNode d_req_type; + Node d_req_const; + void print( const char * c, int indent = 0 ){ + if( d_req_kind!=UNDEFINED_KIND ){ + Trace(c) << d_req_kind << " "; + }else if( !d_req_type.isNull() ){ + Trace(c) << d_req_type; + }else if( !d_req_const.isNull() ){ + Trace(c) << d_req_const; + }else{ + Trace(c) << "_"; + } + Trace(c) << std::endl; + for( std::map< unsigned, ReqTrie >::iterator it = d_children.begin(); it != d_children.end(); ++it ){ + for( int i=0; i<=indent; i++ ) { Trace(c) << " "; } + Trace(c) << it->first << " : "; + it->second.print( c, indent+1 ); + } + } + bool satisfiedBy( quantifiers::TermDbSygus * tdb, TypeNode tn ){ + if( !d_req_const.isNull() ){ + if( !tdb->hasConst( tn, d_req_const ) ){ + return false; + } + } + if( !d_req_type.isNull() ){ + if( tn!=d_req_type ){ + return false; + } + } + if( d_req_kind!=UNDEFINED_KIND ){ + int c = tdb->getKindConsNum( tn, d_req_kind ); + if( c!=-1 ){ + bool ret = true; + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + for( std::map< unsigned, ReqTrie >::iterator it = d_children.begin(); it != d_children.end(); ++it ){ + if( it->first<dt[c].getNumArgs() ){ + TypeNode tnc = tdb->getArgType( dt[c], it->first ); + if( !it->second.satisfiedBy( tdb, tnc ) ){ + ret = false; + break; + } + }else{ + ret = false; + break; + } + } + if( !ret ){ + return false; + } + // TODO : commutative operators try both? + }else{ + return false; + } + } + return true; + } + bool empty() { + return d_req_kind==UNDEFINED_KIND && d_req_const.isNull() && d_req_type.isNull(); + } +}; + +//this function gets all easy redundant cases, before consulting rewriters +bool TermDbSygus::considerArgKind( TypeNode tn, TypeNode tnp, Kind k, Kind pk, int arg ) { + const Datatype& pdt = ((DatatypeType)(tnp).toType()).getDatatype(); + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Assert( hasKind( tn, k ) ); + Assert( hasKind( tnp, pk ) ); + Trace("sygus-sb-debug") << "Consider sygus arg kind " << k << ", pk = " << pk << ", arg = " << arg << "?" << std::endl; + int c = getKindConsNum( tn, k ); + int pc = getKindConsNum( tnp, pk ); + if( k==pk ){ + //check for associativity + if( quantifiers::TermUtil::isAssoc( k ) ){ + //if the operator is associative, then a repeated occurrence should only occur in the leftmost argument position + int firstArg = getFirstArgOccurrence( pdt[pc], tn ); + Assert( firstArg!=-1 ); + if( arg!=firstArg ){ + Trace("sygus-sb-simple") << " sb-simple : do not consider " << k << " at child arg " << arg << " of " << k << " since it is associative, with first arg = " << firstArg << std::endl; + return false; + }else{ + return true; + } + } + } + //describes the shape of an alternate term to construct + // we check whether this term is in the sygus grammar below + ReqTrie rt; + Assert( rt.empty() ); + + //construct rt by cases + if( pk==NOT || pk==BITVECTOR_NOT || pk==UMINUS || pk==BITVECTOR_NEG ){ + //negation normal form + if( pk==k ){ + rt.d_req_type = getArgType( dt[c], 0 ); + }else{ + Kind reqk = UNDEFINED_KIND; //required kind for all children + std::map< unsigned, Kind > reqkc; //required kind for some children + if( pk==NOT ){ + if( k==AND ) { + rt.d_req_kind = OR;reqk = NOT; + }else if( k==OR ){ + rt.d_req_kind = AND;reqk = NOT; + //AJR : eliminate this if we eliminate xor + }else if( k==EQUAL ) { + rt.d_req_kind = XOR; + }else if( k==XOR ) { + rt.d_req_kind = EQUAL; + }else if( k==ITE ){ + rt.d_req_kind = ITE;reqkc[1] = NOT;reqkc[2] = NOT; + rt.d_children[0].d_req_type = getArgType( dt[c], 0 ); + }else if( k==LEQ || k==GT ){ + // (not (~ x y)) -----> (~ (+ y 1) x) + rt.d_req_kind = k; + rt.d_children[0].d_req_kind = PLUS; + rt.d_children[0].d_children[0].d_req_type = getArgType( dt[c], 1 ); + rt.d_children[0].d_children[1].d_req_const = NodeManager::currentNM()->mkConst( Rational( 1 ) ); + rt.d_children[1].d_req_type = getArgType( dt[c], 0 ); + //TODO: other possibilities? + }else if( k==LT || k==GEQ ){ + // (not (~ x y)) -----> (~ y (+ x 1)) + rt.d_req_kind = k; + rt.d_children[0].d_req_type = getArgType( dt[c], 1 ); + rt.d_children[1].d_req_kind = PLUS; + rt.d_children[1].d_children[0].d_req_type = getArgType( dt[c], 0 ); + rt.d_children[1].d_children[1].d_req_const = NodeManager::currentNM()->mkConst( Rational( 1 ) ); + } + }else if( pk==BITVECTOR_NOT ){ + if( k==BITVECTOR_AND ) { + rt.d_req_kind = BITVECTOR_OR;reqk = BITVECTOR_NOT; + }else if( k==BITVECTOR_OR ){ + rt.d_req_kind = BITVECTOR_AND;reqk = BITVECTOR_NOT; + }else if( k==BITVECTOR_XNOR ) { + rt.d_req_kind = BITVECTOR_XOR; + }else if( k==BITVECTOR_XOR ) { + rt.d_req_kind = BITVECTOR_XNOR; + } + }else if( pk==UMINUS ){ + if( k==PLUS ){ + rt.d_req_kind = PLUS;reqk = UMINUS; + } + }else if( pk==BITVECTOR_NEG ){ + if( k==PLUS ){ + rt.d_req_kind = PLUS;reqk = BITVECTOR_NEG; + } + } + if( !rt.empty() && ( reqk!=UNDEFINED_KIND || !reqkc.empty() ) ){ + int pcr = getKindConsNum( tnp, rt.d_req_kind ); + if( pcr!=-1 ){ + Assert( pcr<(int)pdt.getNumConstructors() ); + //must have same number of arguments + if( pdt[pcr].getNumArgs()==dt[c].getNumArgs() ){ + for( unsigned i=0; i<pdt[pcr].getNumArgs(); i++ ){ + Kind rk = reqk; + if( reqk==UNDEFINED_KIND ){ + std::map< unsigned, Kind >::iterator itr = reqkc.find( i ); + if( itr!=reqkc.end() ){ + rk = itr->second; + } + } + if( rk!=UNDEFINED_KIND ){ + rt.d_children[i].d_req_kind = rk; + rt.d_children[i].d_children[0].d_req_type = getArgType( dt[c], i ); + } + } + } + } + } + } + }else if( k==MINUS || k==BITVECTOR_SUB ){ + if( pk==EQUAL || + pk==MINUS || pk==BITVECTOR_SUB || + pk==LEQ || pk==LT || pk==GEQ || pk==GT ){ + int oarg = arg==0 ? 1 : 0; + // (~ x (- y z)) ----> (~ (+ x z) y) + // (~ (- y z) x) ----> (~ y (+ x z)) + rt.d_req_kind = pk; + rt.d_children[arg].d_req_type = getArgType( dt[c], 0 ); + rt.d_children[oarg].d_req_kind = k==MINUS ? PLUS : BITVECTOR_PLUS; + rt.d_children[oarg].d_children[0].d_req_type = getArgType( pdt[pc], oarg ); + rt.d_children[oarg].d_children[1].d_req_type = getArgType( dt[c], 1 ); + }else if( pk==PLUS || pk==BITVECTOR_PLUS ){ + // (+ x (- y z)) -----> (- (+ x y) z) + // (+ (- y z) x) -----> (- (+ x y) z) + rt.d_req_kind = pk==PLUS ? MINUS : BITVECTOR_SUB; + int oarg = arg==0 ? 1 : 0; + rt.d_children[0].d_req_kind = pk; + rt.d_children[0].d_children[0].d_req_type = getArgType( pdt[pc], oarg ); + rt.d_children[0].d_children[1].d_req_type = getArgType( dt[c], 0 ); + rt.d_children[1].d_req_type = getArgType( dt[c], 1 ); + // TODO : this is subsumbed by solving for MINUS + } + }else if( k==ITE ){ + if( pk!=ITE ){ + // (o X (ite y z w) X') -----> (ite y (o X z X') (o X w X')) + rt.d_req_kind = ITE; + rt.d_children[0].d_req_type = getArgType( dt[c], 0 ); + unsigned n_args = pdt[pc].getNumArgs(); + for( unsigned r=1; r<=2; r++ ){ + rt.d_children[r].d_req_kind = pk; + for( unsigned q=0; q<n_args; q++ ){ + if( (int)q==arg ){ + rt.d_children[r].d_children[q].d_req_type = getArgType( dt[c], r ); + }else{ + rt.d_children[r].d_children[q].d_req_type = getArgType( pdt[pc], q ); + } + } + } + //TODO: this increases term size but is probably a good idea + } + }else if( k==NOT ){ + if( pk==ITE ){ + // (ite (not y) z w) -----> (ite y w z) + rt.d_req_kind = ITE; + rt.d_children[0].d_req_type = getArgType( dt[c], 0 ); + rt.d_children[1].d_req_type = getArgType( pdt[pc], 2 ); + rt.d_children[2].d_req_type = getArgType( pdt[pc], 1 ); + } + } + Trace("sygus-sb-debug") << "Consider sygus arg kind " << k << ", pk = " << pk << ", arg = " << arg << "?" << std::endl; + if( !rt.empty() ){ + rt.print("sygus-sb-debug"); + //check if it meets the requirements + if( rt.satisfiedBy( this, tnp ) ){ + Trace("sygus-sb-debug") << "...success!" << std::endl; + Trace("sygus-sb-simple") << " sb-simple : do not consider " << k << " as arg " << arg << " of " << pk << std::endl; + //do not need to consider the kind in the search since there are ways to construct equivalent terms + return false; + }else{ + Trace("sygus-sb-debug") << "...failed." << std::endl; + } + Trace("sygus-sb-debug") << std::endl; + } + //must consider this kind in the search + return true; +} + +bool TermDbSygus::considerConst( TypeNode tn, TypeNode tnp, Node c, Kind pk, int arg ) { + const Datatype& pdt = ((DatatypeType)(tnp).toType()).getDatatype(); + // child grammar-independent + if( !considerConst( pdt, tnp, c, pk, arg ) ){ + return false; + } + // TODO : this can probably be made child grammar independent + int pc = getKindConsNum( tnp, pk ); + if( pdt[pc].getNumArgs()==2 ){ + Kind ok; + int offset; + if (d_quantEngine->getTermUtil()->hasOffsetArg(pk, arg, offset, ok)) + { + Trace("sygus-sb-simple-debug") << pk << " has offset arg " << ok << " " << offset << std::endl; + int ok_arg = getKindConsNum( tnp, ok ); + if( ok_arg!=-1 ){ + Trace("sygus-sb-simple-debug") << "...at argument " << ok_arg << std::endl; + //other operator be the same type + if( isTypeMatch( pdt[ok_arg], pdt[arg] ) ){ + int status; + Node co = d_quantEngine->getTermUtil()->getTypeValueOffset( + c.getType(), c, offset, status); + Trace("sygus-sb-simple-debug") << c << " with offset " << offset << " is " << co << ", status=" << status << std::endl; + if( status==0 && !co.isNull() ){ + if( hasConst( tn, co ) ){ + Trace("sygus-sb-simple") << " sb-simple : by offset reasoning, do not consider const " << c; + Trace("sygus-sb-simple") << " as arg " << arg << " of " << pk << " since we can use " << co << " under " << ok << " " << std::endl; + return false; + } + } + } + } + } + } + return true; +} + +bool TermDbSygus::considerConst( const Datatype& pdt, TypeNode tnp, Node c, Kind pk, int arg ) { + Assert( hasKind( tnp, pk ) ); + int pc = getKindConsNum( tnp, pk ); + bool ret = true; + Trace("sygus-sb-debug") << "Consider sygus const " << c << ", parent = " << pk << ", arg = " << arg << "?" << std::endl; + if (d_quantEngine->getTermUtil()->isIdempotentArg(c, pk, arg)) + { + if( pdt[pc].getNumArgs()==2 ){ + int oarg = arg==0 ? 1 : 0; + TypeNode otn = TypeNode::fromType( ((SelectorType)pdt[pc][oarg].getType()).getRangeType() ); + if( otn==tnp ){ + Trace("sygus-sb-simple") << " sb-simple : " << c << " is idempotent arg " << arg << " of " << pk << "..." << std::endl; + ret = false; + } + } + }else{ + Node sc = d_quantEngine->getTermUtil()->isSingularArg(c, pk, arg); + if( !sc.isNull() ){ + if( hasConst( tnp, sc ) ){ + Trace("sygus-sb-simple") << " sb-simple : " << c << " is singular arg " << arg << " of " << pk << ", evaluating to " << sc << "..." << std::endl; + ret = false; + } + } + } + if( ret ){ + ReqTrie rt; + Assert( rt.empty() ); + Node max_c = d_quantEngine->getTermUtil()->getTypeMaxValue(c.getType()); + Node zero_c = d_quantEngine->getTermUtil()->getTypeValue(c.getType(), 0); + Node one_c = d_quantEngine->getTermUtil()->getTypeValue(c.getType(), 1); + if( pk==XOR || pk==BITVECTOR_XOR ){ + if( c==max_c ){ + rt.d_req_kind = pk==XOR ? NOT : BITVECTOR_NOT; + } + }else if( pk==ITE ){ + if( arg==0 ){ + if( c==max_c ){ + rt.d_children[2].d_req_type = tnp; + }else if( c==zero_c ){ + rt.d_children[1].d_req_type = tnp; + } + } + }else if( pk==STRING_SUBSTR ){ + if( c==one_c ){ + rt.d_req_kind = STRING_CHARAT; + rt.d_children[0].d_req_type = getArgType( pdt[pc], 0 ); + rt.d_children[1].d_req_type = getArgType( pdt[pc], 1 ); + } + } + if( !rt.empty() ){ + //check if satisfied + if( rt.satisfiedBy( this, tnp ) ){ + Trace("sygus-sb-simple") << " sb-simple : do not consider const " << c << " as arg " << arg << " of " << pk; + Trace("sygus-sb-simple") << " in " << ((DatatypeType)tnp.toType()).getDatatype().getName() << std::endl; + //do not need to consider the constant in the search since there are ways to construct equivalent terms + ret = false; + } + } + } + // TODO : cache? + return ret; +} + +int TermDbSygus::solveForArgument( TypeNode tn, unsigned cindex, unsigned arg ) { + // FIXME + return -1; // TODO : if using, modify considerArgKind above + Assert( isRegistered( tn ) ); + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Assert( cindex<dt.getNumConstructors() ); + Assert( arg<dt[cindex].getNumArgs() ); + Kind nk = getConsNumKind( tn, cindex ); + TypeNode tnc = getArgType( dt[cindex], arg ); + const Datatype& cdt = ((DatatypeType)(tnc).toType()).getDatatype(); + + ReqTrie rt; + Assert( rt.empty() ); + int solve_ret = -1; + if( nk==MINUS || nk==BITVECTOR_SUB ){ + if( dt[cindex].getNumArgs()==2 && arg==0 ){ + TypeNode tnco = getArgType( dt[cindex], 1 ); + Node builtin = d_quantEngine->getTermUtil()->getTypeValue( + sygusToBuiltinType(tnc), 0); + solve_ret = getConstConsNum( tn, builtin ); + if( solve_ret!=-1 ){ + // t - s -----> ( 0 - s ) + t + rt.d_req_kind = nk == MINUS ? PLUS : BITVECTOR_PLUS; + rt.d_children[0].d_req_type = tn; // avoid? + rt.d_children[0].d_req_kind = nk; + rt.d_children[0].d_children[0].d_req_const = builtin; + rt.d_children[0].d_children[0].d_req_type = tnco; + rt.d_children[1].d_req_type = tnc; + // TODO : this can be made more general for multiple type grammars to remove MINUS entirely + } + } + } + + if( !rt.empty() ){ + Assert( solve_ret>=0 ); + Assert( solve_ret<=(int)cdt.getNumConstructors() ); + //check if satisfied + if( rt.satisfiedBy( this, tn ) ){ + Trace("sygus-sb-simple") << " sb-simple : ONLY consider " << cdt[solve_ret].getSygusOp() << " as arg " << arg << " of " << nk; + Trace("sygus-sb-simple") << " in " << ((DatatypeType)tn.toType()).getDatatype().getName() << std::endl; + return solve_ret; + } + } + + return -1; +} + +void TermDbSygus::registerSygusType( TypeNode tn ) { + std::map< TypeNode, TypeNode >::iterator itr = d_register.find( tn ); + if( itr==d_register.end() ){ + d_register[tn] = TypeNode::null(); + if( tn.isDatatype() ){ + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Trace("sygus-db") << "Register type " << dt.getName() << "..." << std::endl; + TypeNode btn = TypeNode::fromType( dt.getSygusType() ); + d_register[tn] = btn; + if( !d_register[tn].isNull() ){ + // get the sygus variable list + Node var_list = Node::fromExpr( dt.getSygusVarList() ); + if( !var_list.isNull() ){ + for( unsigned j=0; j<var_list.getNumChildren(); j++ ){ + Node sv = var_list[j]; + SygusVarNumAttribute svna; + sv.setAttribute( svna, j ); + d_var_list[tn].push_back( sv ); + } + }else{ + // no arguments to synthesis functions + } + //iterate over constructors + for( unsigned i=0; i<dt.getNumConstructors(); i++ ){ + Expr sop = dt[i].getSygusOp(); + Assert( !sop.isNull() ); + Node n = Node::fromExpr( sop ); + Trace("sygus-db") << " Operator #" << i << " : " << sop; + if( sop.getKind() == kind::BUILTIN ){ + Kind sk = NodeManager::operatorToKind( n ); + Trace("sygus-db") << ", kind = " << sk; + d_kinds[tn][sk] = i; + d_arg_kind[tn][i] = sk; + }else if( sop.isConst() ){ + Trace("sygus-db") << ", constant"; + d_consts[tn][n] = i; + d_arg_const[tn][i] = n; + } + d_ops[tn][n] = i; + d_arg_ops[tn][i] = n; + Trace("sygus-db") << std::endl; + } + //register connected types + for( unsigned i=0; i<dt.getNumConstructors(); i++ ){ + for( unsigned j=0; j<dt[i].getNumArgs(); j++ ){ + registerSygusType( getArgType( dt[i], j ) ); + } + } + } + } + } +} + +void TermDbSygus::registerEnumerator(Node e, + Node f, + CegConjecture* conj, + bool mkActiveGuard) +{ + Assert(d_enum_to_conjecture.find(e) == d_enum_to_conjecture.end()); + Trace("sygus-db") << "Register measured term : " << e << std::endl; + d_enum_to_conjecture[e] = conj; + d_enum_to_synth_fun[e] = f; + if( mkActiveGuard ){ + // make the guard + Node eg = Rewriter::rewrite( NodeManager::currentNM()->mkSkolem( "eG", NodeManager::currentNM()->booleanType() ) ); + eg = d_quantEngine->getValuation().ensureLiteral( eg ); + AlwaysAssert( !eg.isNull() ); + d_quantEngine->getOutputChannel().requirePhase( eg, true ); + //add immediate lemma + Node lem = NodeManager::currentNM()->mkNode( OR, eg, eg.negate() ); + Trace("cegqi-lemma") << "Cegqi::Lemma : enumerator : " << lem << std::endl; + d_quantEngine->getOutputChannel().lemma( lem ); + d_enum_to_active_guard[e] = eg; + } +} + +bool TermDbSygus::isEnumerator(Node e) const +{ + return d_enum_to_conjecture.find(e) != d_enum_to_conjecture.end(); +} + +CegConjecture* TermDbSygus::getConjectureForEnumerator(Node e) +{ + std::map<Node, CegConjecture*>::iterator itm = d_enum_to_conjecture.find(e); + if (itm != d_enum_to_conjecture.end()) { + return itm->second; + }else{ + return NULL; + } +} + +Node TermDbSygus::getSynthFunForEnumerator(Node e) +{ + std::map<Node, Node>::iterator itsf = d_enum_to_synth_fun.find(e); + if (itsf != d_enum_to_synth_fun.end()) + { + return itsf->second; + } + else + { + return Node::null(); + } +} + +Node TermDbSygus::getActiveGuardForEnumerator(Node e) +{ + std::map<Node, Node>::iterator itag = d_enum_to_active_guard.find(e); + if (itag != d_enum_to_active_guard.end()) { + return itag->second; + }else{ + return Node::null(); + } +} + +void TermDbSygus::getEnumerators(std::vector<Node>& mts) +{ + for (std::map<Node, CegConjecture*>::iterator itm = + d_enum_to_conjecture.begin(); + itm != d_enum_to_conjecture.end(); ++itm) { + mts.push_back( itm->first ); + } +} + +bool TermDbSygus::isRegistered( TypeNode tn ) { + return d_register.find( tn )!=d_register.end(); +} + +TypeNode TermDbSygus::sygusToBuiltinType( TypeNode tn ) { + Assert( isRegistered( tn ) ); + return d_register[tn]; +} + +void TermDbSygus::computeMinTypeDepthInternal( TypeNode root_tn, TypeNode tn, unsigned type_depth ) { + std::map< TypeNode, unsigned >::iterator it = d_min_type_depth[root_tn].find( tn ); + if( it==d_min_type_depth[root_tn].end() || type_depth<it->second ){ + d_min_type_depth[root_tn][tn] = type_depth; + Assert( tn.isDatatype() ); + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + //compute for connected types + for( unsigned i=0; i<dt.getNumConstructors(); i++ ){ + for( unsigned j=0; j<dt[i].getNumArgs(); j++ ){ + computeMinTypeDepthInternal( root_tn, getArgType( dt[i], j ), type_depth+1 ); + } + } + } +} + +unsigned TermDbSygus::getMinTypeDepth( TypeNode root_tn, TypeNode tn ){ + std::map< TypeNode, unsigned >::iterator it = d_min_type_depth[root_tn].find( tn ); + if( it==d_min_type_depth[root_tn].end() ){ + computeMinTypeDepthInternal( root_tn, root_tn, 0 ); + Assert( d_min_type_depth[root_tn].find( tn )!=d_min_type_depth[root_tn].end() ); + return d_min_type_depth[root_tn][tn]; + }else{ + return it->second; + } +} + +unsigned TermDbSygus::getMinTermSize( TypeNode tn ) { + Assert( isRegistered( tn ) ); + std::map< TypeNode, unsigned >::iterator it = d_min_term_size.find( tn ); + if( it==d_min_term_size.end() ){ + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + for( unsigned i=0; i<dt.getNumConstructors(); i++ ){ + if (dt[i].getNumArgs() == 0) + { + d_min_term_size[tn] = 0; + return 0; + } + } + // TODO : improve + d_min_term_size[tn] = 1; + return 1; + }else{ + return it->second; + } +} + +unsigned TermDbSygus::getMinConsTermSize( TypeNode tn, unsigned cindex ) { + Assert( isRegistered( tn ) ); + std::map< unsigned, unsigned >::iterator it = d_min_cons_term_size[tn].find( cindex ); + if( it==d_min_cons_term_size[tn].end() ){ + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Assert( cindex<dt.getNumConstructors() ); + unsigned ret = 0; + if( dt[cindex].getNumArgs()>0 ){ + ret = 1; + for( unsigned i=0; i<dt[cindex].getNumArgs(); i++ ){ + ret += getMinTermSize( getArgType( dt[cindex], i ) ); + } + } + d_min_cons_term_size[tn][cindex] = ret; + return ret; + }else{ + return it->second; + } +} + +unsigned TermDbSygus::getSelectorWeight(TypeNode tn, Node sel) +{ + std::map<TypeNode, std::map<Node, unsigned> >::iterator itsw = + d_sel_weight.find(tn); + if (itsw == d_sel_weight.end()) + { + d_sel_weight[tn].clear(); + itsw = d_sel_weight.find(tn); + Type t = tn.toType(); + const Datatype& dt = static_cast<DatatypeType>(t).getDatatype(); + Trace("sygus-db") << "Compute selector weights for " << dt.getName() + << std::endl; + for (unsigned i = 0, size = dt.getNumConstructors(); i < size; i++) + { + unsigned cw = dt[i].getWeight(); + for (unsigned j = 0, size2 = dt[i].getNumArgs(); j < size2; j++) + { + Node csel = Node::fromExpr(dt[i].getSelectorInternal(t, j)); + std::map<Node, unsigned>::iterator its = itsw->second.find(csel); + if (its == itsw->second.end() || cw < its->second) + { + d_sel_weight[tn][csel] = cw; + Trace("sygus-db") << " w(" << csel << ") <= " << cw << std::endl; + } + } + } + } + Assert(itsw->second.find(sel) != itsw->second.end()); + return itsw->second[sel]; +} + +int TermDbSygus::getKindConsNum( TypeNode tn, Kind k ) { + Assert( isRegistered( tn ) ); + std::map< TypeNode, std::map< Kind, int > >::iterator itt = d_kinds.find( tn ); + if( itt!=d_kinds.end() ){ + std::map< Kind, int >::iterator it = itt->second.find( k ); + if( it!=itt->second.end() ){ + return it->second; + } + } + return -1; +} + +int TermDbSygus::getConstConsNum( TypeNode tn, Node n ){ + Assert( isRegistered( tn ) ); + std::map< TypeNode, std::map< Node, int > >::iterator itt = d_consts.find( tn ); + if( itt!=d_consts.end() ){ + std::map< Node, int >::iterator it = itt->second.find( n ); + if( it!=itt->second.end() ){ + return it->second; + } + } + return -1; +} + +int TermDbSygus::getOpConsNum( TypeNode tn, Node n ) { + std::map< Node, int >::iterator it = d_ops[tn].find( n ); + if( it!=d_ops[tn].end() ){ + return it->second; + }else{ + return -1; + } +} + +bool TermDbSygus::hasKind( TypeNode tn, Kind k ) { + return getKindConsNum( tn, k )!=-1; +} +bool TermDbSygus::hasConst( TypeNode tn, Node n ) { + return getConstConsNum( tn, n )!=-1; +} +bool TermDbSygus::hasOp( TypeNode tn, Node n ) { + return getOpConsNum( tn, n )!=-1; +} + +Node TermDbSygus::getConsNumOp( TypeNode tn, int i ) { + Assert( isRegistered( tn ) ); + std::map< TypeNode, std::map< int, Node > >::iterator itt = d_arg_ops.find( tn ); + if( itt!=d_arg_ops.end() ){ + std::map< int, Node >::iterator itn = itt->second.find( i ); + if( itn!=itt->second.end() ){ + return itn->second; + } + } + return Node::null(); +} + +Node TermDbSygus::getConsNumConst( TypeNode tn, int i ) { + Assert( isRegistered( tn ) ); + std::map< TypeNode, std::map< int, Node > >::iterator itt = d_arg_const.find( tn ); + if( itt!=d_arg_const.end() ){ + std::map< int, Node >::iterator itn = itt->second.find( i ); + if( itn!=itt->second.end() ){ + return itn->second; + } + } + return Node::null(); +} + +Kind TermDbSygus::getConsNumKind( TypeNode tn, int i ) { + Assert( isRegistered( tn ) ); + std::map< TypeNode, std::map< int, Kind > >::iterator itt = d_arg_kind.find( tn ); + if( itt!=d_arg_kind.end() ){ + std::map< int, Kind >::iterator itk = itt->second.find( i ); + if( itk!=itt->second.end() ){ + return itk->second; + } + } + return UNDEFINED_KIND; +} + +bool TermDbSygus::isKindArg( TypeNode tn, int i ) { + return getConsNumKind( tn, i )!=UNDEFINED_KIND; +} + +bool TermDbSygus::isConstArg( TypeNode tn, int i ) { + Assert( isRegistered( tn ) ); + std::map< TypeNode, std::map< int, Node > >::iterator itt = d_arg_const.find( tn ); + if( itt!=d_arg_const.end() ){ + return itt->second.find( i )!=itt->second.end(); + }else{ + return false; + } +} + +TypeNode TermDbSygus::getArgType(const DatatypeConstructor& c, unsigned i) +{ + Assert(i < c.getNumArgs()); + return TypeNode::fromType( ((SelectorType)c[i].getType()).getRangeType() ); +} + +/** get first occurrence */ +int TermDbSygus::getFirstArgOccurrence( const DatatypeConstructor& c, TypeNode tn ) { + for( unsigned i=0; i<c.getNumArgs(); i++ ){ + TypeNode tni = getArgType( c, i ); + if( tni==tn ){ + return i; + } + } + return -1; +} + +bool TermDbSygus::isTypeMatch( const DatatypeConstructor& c1, const DatatypeConstructor& c2 ) { + if( c1.getNumArgs()!=c2.getNumArgs() ){ + return false; + }else{ + for( unsigned i=0; i<c1.getNumArgs(); i++ ){ + if( getArgType( c1, i )!=getArgType( c2, i ) ){ + return false; + } + } + return true; + } +} + +Node TermDbSygus::minimizeBuiltinTerm( Node n ) { + if( ( n.getKind()==EQUAL || n.getKind()==LEQ || n.getKind()==LT || n.getKind()==GEQ || n.getKind()==GT ) && + ( n[0].getType().isInteger() || n[0].getType().isReal() ) ){ + bool changed = false; + std::vector< Node > mon[2]; + for( unsigned r=0; r<2; r++ ){ + unsigned ro = r==0 ? 1 : 0; + Node c; + Node nc; + if( n[r].getKind()==PLUS ){ + for( unsigned i=0; i<n[r].getNumChildren(); i++ ){ + if (ArithMSum::getMonomial(n[r][i], c, nc) + && c.getConst<Rational>().isNegativeOne()) + { + mon[ro].push_back( nc ); + changed = true; + }else{ + if( !n[r][i].isConst() || !n[r][i].getConst<Rational>().isZero() ){ + mon[r].push_back( n[r][i] ); + } + } + } + }else{ + if (ArithMSum::getMonomial(n[r], c, nc) + && c.getConst<Rational>().isNegativeOne()) + { + mon[ro].push_back( nc ); + changed = true; + }else{ + if( !n[r].isConst() || !n[r].getConst<Rational>().isZero() ){ + mon[r].push_back( n[r] ); + } + } + } + } + if( changed ){ + Node nn[2]; + for( unsigned r=0; r<2; r++ ){ + nn[r] = mon[r].size()==0 ? NodeManager::currentNM()->mkConst( Rational(0) ) : ( mon[r].size()==1 ? mon[r][0] : NodeManager::currentNM()->mkNode( PLUS, mon[r] ) ); + } + return NodeManager::currentNM()->mkNode( n.getKind(), nn[0], nn[1] ); + } + } + return n; +} + +Node TermDbSygus::expandBuiltinTerm( Node t ){ + if( t.getKind()==EQUAL ){ + if( t[0].getType().isReal() ){ + return NodeManager::currentNM()->mkNode( AND, NodeManager::currentNM()->mkNode( LEQ, t[0], t[1] ), + NodeManager::currentNM()->mkNode( LEQ, t[1], t[0] ) ); + }else if( t[0].getType().isBoolean() ){ + return NodeManager::currentNM()->mkNode( OR, NodeManager::currentNM()->mkNode( AND, t[0], t[1] ), + NodeManager::currentNM()->mkNode( AND, t[0].negate(), t[1].negate() ) ); + } + }else if( t.getKind()==ITE && t.getType().isBoolean() ){ + return NodeManager::currentNM()->mkNode( OR, NodeManager::currentNM()->mkNode( AND, t[0], t[1] ), + NodeManager::currentNM()->mkNode( AND, t[0].negate(), t[2] ) ); + } + return Node::null(); +} + + +Kind TermDbSygus::getComparisonKind( TypeNode tn ) { + if( tn.isInteger() || tn.isReal() ){ + return LT; + }else if( tn.isBitVector() ){ + return BITVECTOR_ULT; + }else{ + return UNDEFINED_KIND; + } +} + +Kind TermDbSygus::getPlusKind( TypeNode tn, bool is_neg ) { + if( tn.isInteger() || tn.isReal() ){ + return is_neg ? MINUS : PLUS; + }else if( tn.isBitVector() ){ + return is_neg ? BITVECTOR_SUB : BITVECTOR_PLUS; + }else{ + return UNDEFINED_KIND; + } +} + +Node TermDbSygus::getSemanticSkolem( TypeNode tn, Node n, bool doMk ){ + std::map< Node, Node >::iterator its = d_semantic_skolem[tn].find( n ); + if( its!=d_semantic_skolem[tn].end() ){ + return its->second; + }else if( doMk ){ + Node ss = NodeManager::currentNM()->mkSkolem( "sem", tn, "semantic skolem for sygus" ); + d_semantic_skolem[tn][n] = ss; + return ss; + }else{ + return Node::null(); + } +} + +bool TermDbSygus::involvesDivByZero( Node n, std::map< Node, bool >& visited ){ + if( visited.find( n )==visited.end() ){ + visited[n] = true; + Kind k = n.getKind(); + if( k==DIVISION || k==DIVISION_TOTAL || k==INTS_DIVISION || k==INTS_DIVISION_TOTAL || + k==INTS_MODULUS || k==INTS_MODULUS_TOTAL ){ + if( n[1].isConst() ){ + if (n[1] + == d_quantEngine->getTermUtil()->getTypeValue(n[1].getType(), 0)) + { + return true; + } + }else{ + // if it has free variables it might be a non-zero constant + if( !hasFreeVar( n[1] ) ){ + return true; + } + } + } + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + if( involvesDivByZero( n[i], visited ) ){ + return true; + } + } + } + return false; +} + +bool TermDbSygus::involvesDivByZero( Node n ) { + std::map< Node, bool > visited; + return involvesDivByZero( n, visited ); +} + +void doStrReplace(std::string& str, const std::string& oldStr, const std::string& newStr){ + size_t pos = 0; + while((pos = str.find(oldStr, pos)) != std::string::npos){ + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } +} + +Kind TermDbSygus::getOperatorKind( Node op ) { + Assert( op.getKind()!=BUILTIN ); + if (op.getKind() == LAMBDA) + { + // we use APPLY_UF instead of APPLY, since the rewriter for APPLY_UF + // does beta-reduction but does not for APPLY + return APPLY_UF; + }else{ + TypeNode tn = op.getType(); + if( tn.isConstructor() ){ + return APPLY_CONSTRUCTOR; + } + else if (tn.isSelector()) + { + return APPLY_SELECTOR; + } + else if (tn.isTester()) + { + return APPLY_TESTER; + } + else if (tn.isFunction()) + { + return APPLY_UF; + } + return NodeManager::operatorToKind(op); + } +} + +Node TermDbSygus::getAnchor( Node n ) { + if( n.getKind()==APPLY_SELECTOR_TOTAL ){ + return getAnchor( n[0] ); + }else{ + return n; + } +} + +unsigned TermDbSygus::getAnchorDepth( Node n ) { + if( n.getKind()==APPLY_SELECTOR_TOTAL ){ + return 1+getAnchorDepth( n[0] ); + }else{ + return 0; + } +} + + +void TermDbSygus::registerEvalTerm( Node n ) { + if( options::sygusDirectEval() ){ + if( n.getKind()==APPLY_UF && !n.getType().isBoolean() ){ + TypeNode tn = n[0].getType(); + if( tn.isDatatype() ){ + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + if( dt.isSygus() ){ + Node f = n.getOperator(); + if( n[0].getKind()!=APPLY_CONSTRUCTOR ){ + if (d_eval_processed.find(n) == d_eval_processed.end()) + { + Trace("sygus-eager") + << "TermDbSygus::eager: Register eval term : " << n + << std::endl; + d_eval_processed.insert(n); + d_evals[n[0]].push_back(n); + TypeNode tn = n[0].getType(); + Assert(tn.isDatatype()); + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Node var_list = Node::fromExpr(dt.getSygusVarList()); + Assert(dt.isSygus()); + d_eval_args[n[0]].push_back(std::vector<Node>()); + bool isConst = true; + for (unsigned j = 1; j < n.getNumChildren(); j++) + { + d_eval_args[n[0]].back().push_back(n[j]); + if (!n[j].isConst()) + { + isConst = false; + } + } + d_eval_args_const[n[0]].push_back(isConst); + Node a = getAnchor(n[0]); + d_subterms[a][n[0]] = true; + } + } + } + } + } + } +} + +void TermDbSygus::registerModelValue( Node a, Node v, std::vector< Node >& terms, std::vector< Node >& vals, std::vector< Node >& exps ) { + std::map< Node, std::map< Node, bool > >::iterator its = d_subterms.find( a ); + if( its!=d_subterms.end() ){ + Trace("sygus-eager") << "registerModelValue : " << a << ", has " << its->second.size() << " registered subterms." << std::endl; + for( std::map< Node, bool >::iterator itss = its->second.begin(); itss != its->second.end(); ++itss ){ + Node n = itss->first; + Trace("sygus-eager-debug") << "...process : " << n << std::endl; + std::map< Node, std::vector< std::vector< Node > > >::iterator it = d_eval_args.find( n ); + if( it!=d_eval_args.end() && !it->second.empty() ){ + TNode at = a; + TNode vt = v; + Node vn = n.substitute( at, vt ); + vn = Rewriter::rewrite( vn ); + unsigned start = d_node_mv_args_proc[n][vn]; + // get explanation in terms of testers + std::vector< Node > antec_exp; + d_syexp->getExplanationForConstantEquality(n, vn, antec_exp); + Node antec = antec_exp.size()==1 ? antec_exp[0] : NodeManager::currentNM()->mkNode( kind::AND, antec_exp ); + //Node antec = n.eqNode( vn ); + TypeNode tn = n.getType(); + Assert( tn.isDatatype() ); + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + Assert( dt.isSygus() ); + Trace("sygus-eager") << "TermDbSygus::eager: Register model value : " << vn << " for " << n << std::endl; + Trace("sygus-eager") << "...it has " << it->second.size() << " evaluations, already processed " << start << "." << std::endl; + Node bTerm = sygusToBuiltin( vn, tn ); + Trace("sygus-eager") << "Built-in term : " << bTerm << std::endl; + std::vector< Node > vars; + Node var_list = Node::fromExpr( dt.getSygusVarList() ); + for( unsigned j=0; j<var_list.getNumChildren(); j++ ){ + vars.push_back( var_list[j] ); + } + //evaluation children + std::vector< Node > eval_children; + eval_children.push_back( Node::fromExpr( dt.getSygusEvaluationFunc() ) ); + eval_children.push_back( n ); + //for each evaluation + for( unsigned i=start; i<it->second.size(); i++ ){ + Node res; + Node expn; + // unfold? + bool do_unfold = false; + if( options::sygusUnfoldBool() ){ + if( bTerm.getKind()==ITE || bTerm.getType().isBoolean() ){ + do_unfold = true; + } + } + if( do_unfold ){ + // TODO : this is replicated for different values, possibly do better caching + std::map< Node, Node > vtm; + std::vector< Node > exp; + vtm[n] = vn; + eval_children.insert( eval_children.end(), it->second[i].begin(), it->second[i].end() ); + Node eval_fun = NodeManager::currentNM()->mkNode( kind::APPLY_UF, eval_children ); + eval_children.resize( 2 ); + res = unfold( eval_fun, vtm, exp ); + expn = exp.size()==1 ? exp[0] : NodeManager::currentNM()->mkNode( kind::AND, exp ); + }else{ + + EvalSygusInvarianceTest esit; + eval_children.insert( eval_children.end(), it->second[i].begin(), it->second[i].end() ); + Node conj = + NodeManager::currentNM()->mkNode(kind::APPLY_UF, eval_children); + eval_children[1] = vn; + Node eval_fun = NodeManager::currentNM()->mkNode( kind::APPLY_UF, eval_children ); + res = evaluateWithUnfolding(eval_fun); + esit.init(conj, n, res); + eval_children.resize( 2 ); + eval_children[1] = n; + + //evaluate with minimal explanation + std::vector< Node > mexp; + d_syexp->getExplanationFor(n, vn, mexp, esit); + Assert( !mexp.empty() ); + expn = mexp.size()==1 ? mexp[0] : NodeManager::currentNM()->mkNode( kind::AND, mexp ); + + //if all constant, we can use evaluation to minimize the explanation + //Assert( i<d_eval_args_const[n].size() ); + //if( d_eval_args_const[n][i] ){ + /* + std::map< Node, Node > vtm; + std::map< Node, Node > visited; + std::map< Node, std::vector< Node > > exp; + vtm[n] = vn; + res = crefEvaluate( eval_fun, vtm, visited, exp ); + Assert( !exp[eval_fun].empty() ); + expn = exp[eval_fun].size()==1 ? exp[eval_fun][0] : NodeManager::currentNM()->mkNode( kind::AND, exp[eval_fun] ); + */ + /* + //otherwise, just do a substitution + }else{ + Assert( vars.size()==it->second[i].size() ); + res = bTerm.substitute( vars.begin(), vars.end(), it->second[i].begin(), it->second[i].end() ); + res = Rewriter::rewrite( res ); + expn = antec; + } + */ + } + Assert( !res.isNull() ); + terms.push_back( d_evals[n][i] ); + vals.push_back( res ); + exps.push_back( expn ); + Trace("sygus-eager") << "Conclude : " << d_evals[n][i] << " == " << res << ", cref eval = " << d_eval_args_const[n][i] << std::endl; + Trace("sygus-eager") << " from " << expn << std::endl; + } + d_node_mv_args_proc[n][vn] = it->second.size(); + } + } + } +} + +Node TermDbSygus::unfold( Node en, std::map< Node, Node >& vtm, std::vector< Node >& exp, bool track_exp ) { + if( en.getKind()==kind::APPLY_UF ){ + Trace("sygus-db-debug") << "Unfold : " << en << std::endl; + Node ev = en[0]; + if( track_exp ){ + std::map< Node, Node >::iterator itv = vtm.find( en[0] ); + if( itv!=vtm.end() ){ + ev = itv->second; + }else{ + Assert( false ); + } + Assert( en[0].getType()==ev.getType() ); + Assert( ev.isConst() ); + } + Assert( ev.getKind()==kind::APPLY_CONSTRUCTOR ); + std::vector< Node > args; + for( unsigned i=1; i<en.getNumChildren(); i++ ){ + args.push_back( en[i] ); + } + const Datatype& dt = ((DatatypeType)(ev.getType()).toType()).getDatatype(); + unsigned i = Datatype::indexOf( ev.getOperator().toExpr() ); + if( track_exp ){ + //explanation + Node ee = NodeManager::currentNM()->mkNode( kind::APPLY_TESTER, Node::fromExpr( dt[i].getTester() ), en[0] ); + if( std::find( exp.begin(), exp.end(), ee )==exp.end() ){ + exp.push_back( ee ); + } + } + Assert( !dt.isParametric() ); + std::map< int, Node > pre; + for( unsigned j=0; j<dt[i].getNumArgs(); j++ ){ + std::vector< Node > cc; + //get the evaluation argument for the selector + Type rt = dt[i][j].getRangeType(); + const Datatype & ad = ((DatatypeType)dt[i][j].getRangeType()).getDatatype(); + cc.push_back( Node::fromExpr( ad.getSygusEvaluationFunc() ) ); + Node s; + if( en[0].getKind()==kind::APPLY_CONSTRUCTOR ){ + s = en[0][j]; + }else{ + s = NodeManager::currentNM()->mkNode( kind::APPLY_SELECTOR_TOTAL, dt[i].getSelectorInternal( en[0].getType().toType(), j ), en[0] ); + } + cc.push_back( s ); + if( track_exp ){ + //update vtm map + vtm[s] = ev[j]; + } + cc.insert( cc.end(), args.begin(), args.end() ); + pre[j] = NodeManager::currentNM()->mkNode( kind::APPLY_UF, cc ); + } + std::map< TypeNode, int > var_count; + Node ret = mkGeneric( dt, i, var_count, pre ); + // if it is a variable, apply the substitution + if( ret.getKind()==kind::BOUND_VARIABLE ){ + Assert( ret.hasAttribute(SygusVarNumAttribute()) ); + int i = ret.getAttribute(SygusVarNumAttribute()); + Assert( Node::fromExpr( dt.getSygusVarList() )[i]==ret ); + ret = args[i]; + } + else + { + ret = Rewriter::rewrite(ret); + } + return ret; + }else{ + Assert( en.isConst() ); + } + return en; +} + + +Node TermDbSygus::getEagerUnfold( Node n, std::map< Node, Node >& visited ) { + std::map< Node, Node >::iterator itv = visited.find( n ); + if( itv==visited.end() ){ + Trace("cegqi-eager-debug") << "getEagerUnfold " << n << std::endl; + Node ret; + if( n.getKind()==APPLY_UF ){ + TypeNode tn = n[0].getType(); + Trace("cegqi-eager-debug") << "check " << n[0].getType() << std::endl; + if( tn.isDatatype() ){ + const Datatype& dt = ((DatatypeType)(tn).toType()).getDatatype(); + if( dt.isSygus() ){ + Trace("cegqi-eager") << "Unfold eager : " << n << std::endl; + Node bTerm = sygusToBuiltin( n[0], tn ); + Trace("cegqi-eager") << "Built-in term : " << bTerm << std::endl; + std::vector< Node > vars; + std::vector< Node > subs; + Node var_list = Node::fromExpr( dt.getSygusVarList() ); + Assert( var_list.getNumChildren()+1==n.getNumChildren() ); + for( unsigned j=0; j<var_list.getNumChildren(); j++ ){ + vars.push_back( var_list[j] ); + } + for( unsigned j=1; j<n.getNumChildren(); j++ ){ + Node nc = getEagerUnfold( n[j], visited ); + subs.push_back( nc ); + Assert(subs[j - 1].getType().isComparableTo( + var_list[j - 1].getType())); + } + Assert( vars.size()==subs.size() ); + bTerm = bTerm.substitute( vars.begin(), vars.end(), subs.begin(), subs.end() ); + Trace("cegqi-eager") << "Built-in term after subs : " << bTerm << std::endl; + Trace("cegqi-eager-debug") << "Types : " << bTerm.getType() << " " << n.getType() << std::endl; + Assert(n.getType().isComparableTo(bTerm.getType())); + ret = bTerm; + } + } + } + if( ret.isNull() ){ + if( n.getKind()!=FORALL ){ + bool childChanged = false; + std::vector< Node > children; + for( unsigned i=0; i<n.getNumChildren(); i++ ){ + Node nc = getEagerUnfold( n[i], visited ); + childChanged = childChanged || n[i]!=nc; + children.push_back( nc ); + } + if( childChanged ){ + if( n.getMetaKind() == kind::metakind::PARAMETERIZED ){ + children.insert( children.begin(), n.getOperator() ); + } + ret = NodeManager::currentNM()->mkNode( n.getKind(), children ); + } + } + if( ret.isNull() ){ + ret = n; + } + } + visited[n] = ret; + return ret; + }else{ + return itv->second; + } +} + + +Node TermDbSygus::evaluateBuiltin( TypeNode tn, Node bn, std::vector< Node >& args ) { + if( !args.empty() ){ + std::map< TypeNode, std::vector< Node > >::iterator it = d_var_list.find( tn ); + Assert( it!=d_var_list.end() ); + Assert( it->second.size()==args.size() ); + return Rewriter::rewrite( bn.substitute( it->second.begin(), it->second.end(), args.begin(), args.end() ) ); + }else{ + return Rewriter::rewrite( bn ); + } +} + +Node TermDbSygus::evaluateWithUnfolding( + Node n, std::unordered_map<Node, Node, NodeHashFunction>& visited) +{ + std::unordered_map<Node, Node, NodeHashFunction>::iterator it = + visited.find(n); + if( it==visited.end() ){ + Node ret = n; + while( ret.getKind()==APPLY_UF && ret[0].getKind()==APPLY_CONSTRUCTOR ){ + ret = unfold( ret ); + } + if( ret.getNumChildren()>0 ){ + std::vector< Node > children; + if( ret.getMetaKind() == kind::metakind::PARAMETERIZED ){ + children.push_back( ret.getOperator() ); + } + bool childChanged = false; + for( unsigned i=0; i<ret.getNumChildren(); i++ ){ + Node nc = evaluateWithUnfolding( ret[i], visited ); + childChanged = childChanged || nc!=ret[i]; + children.push_back( nc ); + } + if( childChanged ){ + ret = NodeManager::currentNM()->mkNode( ret.getKind(), children ); + } + ret = getExtRewriter()->extendedRewrite(ret); + } + visited[n] = ret; + return ret; + }else{ + return it->second; + } +} + +Node TermDbSygus::evaluateWithUnfolding( Node n ) { + std::unordered_map<Node, Node, NodeHashFunction> visited; + return evaluateWithUnfolding( n, visited ); +} + +}/* CVC4::theory::quantifiers namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + diff --git a/src/theory/quantifiers/sygus/term_database_sygus.h b/src/theory/quantifiers/sygus/term_database_sygus.h new file mode 100644 index 000000000..e796a3adc --- /dev/null +++ b/src/theory/quantifiers/sygus/term_database_sygus.h @@ -0,0 +1,286 @@ +/********************* */ +/*! \file term_database_sygus.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** \brief term database sygus class + **/ + +#include "cvc4_private.h" + +#ifndef __CVC4__THEORY__QUANTIFIERS__TERM_DATABASE_SYGUS_H +#define __CVC4__THEORY__QUANTIFIERS__TERM_DATABASE_SYGUS_H + +#include <unordered_set> + +#include "theory/quantifiers/extended_rewrite.h" +#include "theory/quantifiers/sygus/sygus_explain.h" +#include "theory/quantifiers/term_database.h" + +namespace CVC4 { +namespace theory { +namespace quantifiers { + +class CegConjecture; + +// TODO :issue #1235 split and document this class +class TermDbSygus { + public: + TermDbSygus(context::Context* c, QuantifiersEngine* qe); + ~TermDbSygus() {} + /** Reset this utility */ + bool reset(Theory::Effort e); + /** Identify this utility */ + std::string identify() const { return "TermDbSygus"; } + /** register the sygus type */ + void registerSygusType(TypeNode tn); + /** register a variable e that we will do enumerative search on + * conj is the conjecture that the enumeration of e is for. + * f is the synth-fun that the enumeration of e is for. + * mkActiveGuard is whether we want to make an active guard for e + * (see d_enum_to_active_guard). + * + * Notice that enumerator e may not be equivalent + * to f in synthesis-through-unification approaches + * (e.g. decision tree construction for PBE synthesis). + */ + void registerEnumerator(Node e, + Node f, + CegConjecture* conj, + bool mkActiveGuard = false); + /** is e an enumerator? */ + bool isEnumerator(Node e) const; + /** return the conjecture e is associated with */ + CegConjecture* getConjectureForEnumerator(Node e); + /** return the function-to-synthesize e is associated with */ + Node getSynthFunForEnumerator(Node e); + /** get active guard for e */ + Node getActiveGuardForEnumerator(Node e); + /** get all registered enumerators */ + void getEnumerators(std::vector<Node>& mts); + /** get the explanation utility */ + SygusExplain* getExplain() { return d_syexp.get(); } + /** get the extended rewrite utility */ + ExtendedRewriter* getExtRewriter() { return d_ext_rw.get(); } + //-----------------------------conversion from sygus to builtin + /** get free variable + * + * This class caches a list of free variables for each type, which are + * used, for instance, for constructing canonical forms of terms with free + * variables. This function returns the i^th free variable for type tn. + * If useSygusType is true, then this function returns a variable of the + * analog type for sygus type tn (see d_fv for details). + */ + TNode getFreeVar(TypeNode tn, int i, bool useSygusType = false); + /** get free variable and increment + * + * This function returns the next free variable for type tn, and increments + * the counter in var_count for that type. + */ + TNode getFreeVarInc(TypeNode tn, + std::map<TypeNode, int>& var_count, + bool useSygusType = false); + /** returns true if n is a cached free variable (in d_fv). */ + bool isFreeVar(Node n) { return d_fv_stype.find(n) != d_fv_stype.end(); } + /** returns the index of n in the free variable cache (d_fv). */ + int getVarNum(Node n) { return d_fv_num[n]; } + /** returns true if n has a cached free variable (in d_fv). */ + bool hasFreeVar(Node n); + /** make generic + * + * This function returns a builtin term f( t1, ..., tn ) where f is the + * builtin op of the sygus datatype constructor specified by arguments + * dt and c. The mapping pre maps child indices to the term for that index + * in the term we are constructing. That is, for each i = 1,...,n: + * If i is in the domain of pre, then ti = pre[i]. + * If i is not in the domain of pre, then ti = d_fv[1][ var_count[Ti ] ], + * and var_count[Ti] is incremented. + */ + Node mkGeneric(const Datatype& dt, + unsigned c, + std::map<TypeNode, int>& var_count, + std::map<int, Node>& pre); + /** same as above, but with empty var_count */ + Node mkGeneric(const Datatype& dt, int c, std::map<int, Node>& pre); + /** sygus to builtin + * + * Given a sygus datatype term n of type tn, this function returns its analog, + * that is, the term that n encodes. + */ + Node sygusToBuiltin(Node n, TypeNode tn); + /** same as above, but without tn */ + Node sygusToBuiltin(Node n) { return sygusToBuiltin(n, n.getType()); } + //-----------------------------end conversion from sygus to builtin + + private: + /** reference to the quantifiers engine */ + QuantifiersEngine* d_quantEngine; + /** sygus explanation */ + std::unique_ptr<SygusExplain> d_syexp; + /** sygus explanation */ + std::unique_ptr<ExtendedRewriter> d_ext_rw; + /** mapping from enumerator terms to the conjecture they are associated with + */ + std::map<Node, CegConjecture*> d_enum_to_conjecture; + /** mapping from enumerator terms to the function-to-synthesize they are + * associated with + */ + std::map<Node, Node> d_enum_to_synth_fun; + /** mapping from enumerator terms to the guard they are associated with + * The guard G for an enumerator e has the semantics + * if G is true, then there are more values of e to enumerate". + */ + std::map<Node, Node> d_enum_to_active_guard; + + //-----------------------------conversion from sygus to builtin + /** cache for sygusToBuiltin */ + std::map<TypeNode, std::map<Node, Node> > d_sygus_to_builtin; + /** a cache of fresh variables for each type + * + * We store two versions of this list: + * index 0: mapping from builtin types to fresh variables of that type, + * index 1: mapping from sygus types to fresh varaibles of the type they + * encode. + */ + std::map<TypeNode, std::vector<Node> > d_fv[2]; + /** Maps free variables to the domain type they are associated with in d_fv */ + std::map<Node, TypeNode> d_fv_stype; + /** Maps free variables to their index in d_fv. */ + std::map<Node, int> d_fv_num; + /** recursive helper for hasFreeVar, visited stores nodes we have visited. */ + bool hasFreeVar(Node n, std::map<Node, bool>& visited); + //-----------------------------end conversion from sygus to builtin + + // TODO :issue #1235 : below here needs refactor + + public: + Node d_true; + Node d_false; + +private: + void computeMinTypeDepthInternal( TypeNode root_tn, TypeNode tn, unsigned type_depth ); + bool involvesDivByZero( Node n, std::map< Node, bool >& visited ); + + private: + // information for sygus types + std::map<TypeNode, TypeNode> d_register; // stores sygus -> builtin type + std::map<TypeNode, std::vector<Node> > d_var_list; + std::map<TypeNode, std::map<int, Kind> > d_arg_kind; + std::map<TypeNode, std::map<Kind, int> > d_kinds; + std::map<TypeNode, std::map<int, Node> > d_arg_const; + std::map<TypeNode, std::map<Node, int> > d_consts; + std::map<TypeNode, std::map<Node, int> > d_ops; + std::map<TypeNode, std::map<int, Node> > d_arg_ops; + std::map<TypeNode, std::map<Node, Node> > d_semantic_skolem; + // grammar information + // root -> type -> _ + std::map<TypeNode, std::map<TypeNode, unsigned> > d_min_type_depth; + // std::map< TypeNode, std::map< Node, std::map< std::map< int, bool > > > + // d_consider_const; + // type -> cons -> _ + std::map<TypeNode, unsigned> d_min_term_size; + std::map<TypeNode, std::map<unsigned, unsigned> > d_min_cons_term_size; + /** a cache for getSelectorWeight */ + std::map<TypeNode, std::map<Node, unsigned> > d_sel_weight; + + public: // general sygus utilities + bool isRegistered( TypeNode tn ); + // get the minimum depth of type in its parent grammar + unsigned getMinTypeDepth( TypeNode root_tn, TypeNode tn ); + // get the minimum size for a constructor term + unsigned getMinTermSize( TypeNode tn ); + unsigned getMinConsTermSize( TypeNode tn, unsigned cindex ); + /** get the weight of the selector, where tn is the domain of sel */ + unsigned getSelectorWeight(TypeNode tn, Node sel); + + public: + TypeNode sygusToBuiltinType( TypeNode tn ); + int getKindConsNum( TypeNode tn, Kind k ); + int getConstConsNum( TypeNode tn, Node n ); + int getOpConsNum( TypeNode tn, Node n ); + bool hasKind( TypeNode tn, Kind k ); + bool hasConst( TypeNode tn, Node n ); + bool hasOp( TypeNode tn, Node n ); + Node getConsNumConst( TypeNode tn, int i ); + Node getConsNumOp( TypeNode tn, int i ); + Kind getConsNumKind( TypeNode tn, int i ); + bool isKindArg( TypeNode tn, int i ); + bool isConstArg( TypeNode tn, int i ); + /** get arg type */ + TypeNode getArgType(const DatatypeConstructor& c, unsigned i); + /** get first occurrence */ + int getFirstArgOccurrence( const DatatypeConstructor& c, TypeNode tn ); + /** is type match */ + bool isTypeMatch( const DatatypeConstructor& c1, const DatatypeConstructor& c2 ); + + TypeNode getSygusTypeForVar( Node v ); + Node sygusSubstituted( TypeNode tn, Node n, std::vector< Node >& args ); + Node getSygusNormalized( Node n, std::map< TypeNode, int >& var_count, std::map< Node, Node >& subs ); + Node getNormalized(TypeNode t, Node prog); + unsigned getSygusTermSize( Node n ); + /** given a term, construct an equivalent smaller one that respects syntax */ + Node minimizeBuiltinTerm( Node n ); + /** given a term, expand it into more basic components */ + Node expandBuiltinTerm( Node n ); + /** get comparison kind */ + Kind getComparisonKind( TypeNode tn ); + Kind getPlusKind( TypeNode tn, bool is_neg = false ); + // get semantic skolem for n (a sygus term whose builtin version is n) + Node getSemanticSkolem( TypeNode tn, Node n, bool doMk = true ); + /** involves div-by-zero */ + bool involvesDivByZero( Node n ); + + /** get operator kind */ + static Kind getOperatorKind( Node op ); + + /** get anchor */ + static Node getAnchor( Node n ); + static unsigned getAnchorDepth( Node n ); + +public: // for symmetry breaking + bool considerArgKind( TypeNode tn, TypeNode tnp, Kind k, Kind pk, int arg ); + bool considerConst( TypeNode tn, TypeNode tnp, Node c, Kind pk, int arg ); + bool considerConst( const Datatype& pdt, TypeNode tnp, Node c, Kind pk, int arg ); + int solveForArgument( TypeNode tnp, unsigned cindex, unsigned arg ); + +//for eager instantiation + // TODO (as part of #1235) move some of these functions to sygus_explain.h + private: + /** the set of evaluation terms we have already processed */ + std::unordered_set<Node, NodeHashFunction> d_eval_processed; + std::map< Node, std::map< Node, bool > > d_subterms; + std::map< Node, std::vector< Node > > d_evals; + std::map< Node, std::vector< std::vector< Node > > > d_eval_args; + std::map< Node, std::vector< bool > > d_eval_args_const; + std::map< Node, std::map< Node, unsigned > > d_node_mv_args_proc; + +public: + void registerEvalTerm( Node n ); + void registerModelValue( Node n, Node v, std::vector< Node >& exps, std::vector< Node >& terms, std::vector< Node >& vals ); + Node unfold( Node en, std::map< Node, Node >& vtm, std::vector< Node >& exp, bool track_exp = true ); + Node unfold( Node en ){ + std::map< Node, Node > vtm; + std::vector< Node > exp; + return unfold( en, vtm, exp, false ); + } + Node getEagerUnfold( Node n, std::map< Node, Node >& visited ); + + // builtin evaluation, returns rewrite( bn [ args / vars(tn) ] ) + Node evaluateBuiltin( TypeNode tn, Node bn, std::vector< Node >& args ); + // evaluate with unfolding + Node evaluateWithUnfolding( + Node n, std::unordered_map<Node, Node, NodeHashFunction>& visited); + Node evaluateWithUnfolding( Node n ); +}; + +}/* CVC4::theory::quantifiers namespace */ +}/* CVC4::theory namespace */ +}/* CVC4 namespace */ + +#endif /* __CVC4__THEORY__QUANTIFIERS__TERM_DATABASE_H */ |