From: Andrew Reynolds Date: Mon, 23 Oct 2017 19:43:20 +0000 (-0500) Subject: Document sygus programming-by-examples utility (#1260) X-Git-Tag: cvc5-1.0.0~5543 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=2f11cfd563ef96402042e9a3b0086712de660ae6;p=cvc5.git Document sygus programming-by-examples utility (#1260) * Initial documentation and clean up of SyGuS PBE class, fix issue with concat strategy. * More documentation, cleanup. * Do not use concat strategy for 3+ arguments to concat, add regression. * Minor --- diff --git a/src/theory/quantifiers/ce_guided_pbe.cpp b/src/theory/quantifiers/ce_guided_pbe.cpp index ac09a31d0..e4bf43963 100644 --- a/src/theory/quantifiers/ce_guided_pbe.cpp +++ b/src/theory/quantifiers/ce_guided_pbe.cpp @@ -35,6 +35,7 @@ void indent( const char * c, int ind ) { } } } + void print_val( const char * c, std::vector< Node >& vals, bool pol = true ){ if( Trace.isOn(c) ){ for( unsigned i=0; i& vals, bool pol = true ){ } } } -void print_strat( const char * c, unsigned s ){ - switch(s){ - case CegConjecturePbe::strat_ITE:Trace(c) << "ITE";break; - case CegConjecturePbe::strat_CONCAT:Trace(c) << "CONCAT";break; - case CegConjecturePbe::strat_ID:Trace(c) << "ID";break; - default:Trace(c) << "strat_" << s;break; - } -} -void print_role( const char * c, unsigned r ){ + +void CegConjecturePbe::print_role( const char * c, unsigned r ){ switch(r){ case CegConjecturePbe::enum_io:Trace(c) << "IO";break; case CegConjecturePbe::enum_ite_condition:Trace(c) << "CONDITION";break; @@ -61,6 +55,15 @@ void print_role( const char * c, unsigned r ){ } } +void CegConjecturePbe::print_strat( const char * c, unsigned s ){ + switch(s){ + case CegConjecturePbe::strat_ITE:Trace(c) << "ITE";break; + case CegConjecturePbe::strat_CONCAT:Trace(c) << "CONCAT";break; + case CegConjecturePbe::strat_ID:Trace(c) << "ID";break; + default:Trace(c) << "strat_" << s;break; + } +} + CegConjecturePbe::CegConjecturePbe(QuantifiersEngine* qe, CegConjecture* p) : d_qe(qe), d_parent(p){ @@ -250,7 +253,7 @@ Node CegConjecturePbe::PbeTrie::addPbeExampleEval(TypeNode etn, Node e, Node b, bool CegConjecturePbe::hasExamples(Node e) { if (isPbe()) { - e = getCandidateForEnumerator(e); + e = getSynthFunctionForEnumerator(e); std::map::iterator itx = d_examples_invalid.find(e); if (itx == d_examples_invalid.end()) { return d_examples.find(e) != d_examples.end(); @@ -260,7 +263,7 @@ bool CegConjecturePbe::hasExamples(Node e) { } unsigned CegConjecturePbe::getNumExamples(Node e) { - e = getCandidateForEnumerator(e); + e = getSynthFunctionForEnumerator(e); std::map > >::iterator it = d_examples.find(e); if (it != d_examples.end()) { @@ -271,7 +274,7 @@ unsigned CegConjecturePbe::getNumExamples(Node e) { } void CegConjecturePbe::getExample(Node e, unsigned i, std::vector& ex) { - e = getCandidateForEnumerator(e); + e = getSynthFunctionForEnumerator(e); std::map > >::iterator it = d_examples.find(e); if (it != d_examples.end()) { @@ -283,7 +286,7 @@ void CegConjecturePbe::getExample(Node e, unsigned i, std::vector& ex) { } Node CegConjecturePbe::getExampleOut(Node e, unsigned i) { - e = getCandidateForEnumerator(e); + e = getSynthFunctionForEnumerator(e); std::map >::iterator it = d_examples_out.find(e); if (it != d_examples_out.end()) { Assert(i < it->second.size()); @@ -297,7 +300,7 @@ Node CegConjecturePbe::getExampleOut(Node e, unsigned i) { Node CegConjecturePbe::addSearchVal(TypeNode tn, Node e, Node bvr) { Assert(isPbe()); Assert(!e.isNull()); - e = getCandidateForEnumerator(e); + e = getSynthFunctionForEnumerator(e); std::map::iterator itx = d_examples_invalid.find(e); if (itx == d_examples_invalid.end()) { unsigned nex = d_examples[e].size(); @@ -310,7 +313,7 @@ Node CegConjecturePbe::addSearchVal(TypeNode tn, Node e, Node bvr) { Node CegConjecturePbe::evaluateBuiltin(TypeNode tn, Node bn, Node e, unsigned i) { - e = getCandidateForEnumerator(e); + e = getSynthFunctionForEnumerator(e); std::map::iterator itx = d_examples_invalid.find(e); if (itx == d_examples_invalid.end()) { std::map > >::iterator it = @@ -323,7 +326,7 @@ Node CegConjecturePbe::evaluateBuiltin(TypeNode tn, Node bn, Node e, return Rewriter::rewrite(bn); } -Node CegConjecturePbe::getCandidateForEnumerator(Node e) { +Node CegConjecturePbe::getSynthFunctionForEnumerator(Node e) { std::map::const_iterator it = d_enum_to_candidate.find(e); if (it != d_enum_to_candidate.end()) { return it->second; @@ -340,8 +343,7 @@ void CegConjecturePbe::registerEnumerator( Node et, Node c, TypeNode tn, unsigne Trace("sygus-unif-debug") << ", role = "; print_role( "sygus-unif-debug", enum_role ); Trace("sygus-unif-debug") << ", in search = " << inSearch << std::endl; - d_einfo[et].d_parent_candidate = c; - d_einfo[et].d_role = enum_role; + 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 ); @@ -387,9 +389,9 @@ void CegConjecturePbe::collectEnumeratorTypes( Node e, TypeNode tn, unsigned enu std::map< Node, unsigned > cop_to_strat; std::map< Node, unsigned > cop_to_cindex; + // look at builtin operartors first (when r=0), then defined operators (when r=1) for( unsigned r=0; r<2; r++ ){ for( unsigned j=0; j utchildren; utchildren.push_back( cop ); std::vector< Node > sks; @@ -449,7 +452,7 @@ void CegConjecturePbe::collectEnumeratorTypes( Node e, TypeNode tn, unsigned enu cop_to_strat[cop] = strat_ITE; } }else if( eut.getKind()==kind::STRING_CONCAT ){ - if( dt[j].getNumArgs()>=eut.getNumChildren() ){ + if( dt[j].getNumArgs()>=eut.getNumChildren() && eut.getNumChildren()==2 ){ cop_to_strat[cop] = strat_CONCAT; } }else if( eut.getKind()==kind::APPLY_UF ){ @@ -505,7 +508,8 @@ void CegConjecturePbe::collectEnumeratorTypes( Node e, TypeNode tn, unsigned enu } if( cop_to_strat.find( cop )!=cop_to_strat.end() ){ Trace("sygus-unif") << "...type " << dt.getName() << " has defined constructor matching strategy "; - Trace("sygus-unif") << cop_to_strat[cop] << ", enumerate child types..." << std::endl; + print_strat("sygus-unif",cop_to_strat[cop]); + Trace("sygus-unif") << "..." << std::endl; for( unsigned k=0; k& lem std::map< Node, EnumInfo >::iterator itns = d_einfo.find( es ); Assert( itns!=d_einfo.end() ); Trace("sygus-unif") << " " << es << ", role = "; - print_role( "sygus-unif", itns->second.d_role ); + print_role( "sygus-unif", itns->second.getRole() ); Trace("sygus-unif") << std::endl; } } @@ -679,7 +683,7 @@ void CegConjecturePbe::staticLearnRedundantOps( Node c, Node e, std::map< Node, indent("sygus-unif", ind); Trace("sygus-unif") << e << " : role : "; - print_role("sygus-unif", itn->second.d_role); + print_role("sygus-unif", itn->second.getRole()); Trace("sygus-unif") << " : "; if( itn->second.isTemplated() ){ @@ -847,7 +851,7 @@ void CegConjecturePbe::addEnumeratedValue( Node x, Node v, std::vector< Node >& Assert( itv!=d_einfo.end() ); Trace("sygus-pbe-enum") << "Process " << xs << " from " << s << std::endl; //bool prevIsCover = false; - if( itv->second.d_role==enum_io ){ + if( itv->second.getRole()==enum_io ){ Trace("sygus-pbe-enum") << " IO-Eval of "; //prevIsCover = itv->second.isFeasible(); }else{ @@ -870,7 +874,7 @@ void CegConjecturePbe::addEnumeratedValue( Node x, Node v, std::vector< Node >& Assert( res.isConst() ); } Node resb; - if( itv->second.d_role==enum_io ){ + if( itv->second.getRole()==enum_io ){ Node out = itxo->second[j]; Assert( out.isConst() ); resb = res==out ? d_true : d_false; @@ -888,7 +892,7 @@ void CegConjecturePbe::addEnumeratedValue( Node x, Node v, std::vector< Node >& } } bool keep = false; - if( itv->second.d_role==enum_io ){ + if( itv->second.getRole()==enum_io ){ if( cond_vals.find( d_true )!=cond_vals.end() || cond_vals.empty() ){ // latter is the degenerate case of no examples //check subsumbed/subsuming std::vector< Node > subsume; @@ -950,7 +954,7 @@ void CegConjecturePbe::addEnumeratedValue( Node x, Node v, std::vector< Node >& itv->second.addEnumValue( this, v, results ); /* if( Trace.isOn("sygus-pbe-enum") ){ - if( itv->second.d_role==enum_io ){ + if( itv->second.getRole()==enum_io ){ if( !prevIsCover && itv->second.isFeasible() ){ Trace("sygus-pbe-enum") << "...PBE : success : Evaluation of " << xs << " now covers all examples." << std::endl; } @@ -1038,7 +1042,7 @@ bool CegConjecturePbe::getExplanationForEnumeratorExclude( Node c, Node x, Node if( ei.d_enum_slave.size()==1 ){ // this check whether the example evaluates to something that is larger than the output // if so, then this term is never useful when using a concatenation strategy - if( ei.d_role==enum_concat_term ){ + if( ei.getRole()==enum_concat_term ){ if( Trace.isOn("sygus-pbe-cterm-debug") ){ Trace("sygus-pbe-enum") << std::endl; } @@ -1091,7 +1095,7 @@ void CegConjecturePbe::EnumInfo::addEnumValue( CegConjecturePbe * pbe, Node v, s d_enum_vals.push_back( v ); d_enum_vals_res.push_back( results ); /* - if( d_role==enum_io ){ + if( getRole()==enum_io ){ // compute if( d_enum_total.empty() ){ d_enum_total = results; @@ -1460,12 +1464,12 @@ Node CegConjecturePbe::constructSolution( Node c, Node e, UnifContext& x, int in std::map< Node, EnumInfo >::iterator itn = d_einfo.find( e ); Assert( itn!=d_einfo.end() ); Node ret_dt; - if( itn->second.d_role==enum_any ){ + if( itn->second.getRole()==enum_any ){ indent("sygus-pbe-dt", ind); ret_dt = constructBestSolvedTerm( itn->second.d_enum_vals, x ); Trace("sygus-pbe-dt") << "return PBE: success : use any " << d_tds->sygusToBuiltin( ret_dt ) << std::endl; Assert( !ret_dt.isNull() ); - }else if( itn->second.d_role==enum_io && !x.isReturnValueModified() && itn->second.isSolved() ){ + }else if( itn->second.getRole()==enum_io && !x.isReturnValueModified() && itn->second.isSolved() ){ // this type has a complete solution ret_dt = itn->second.getSolved(); indent("sygus-pbe-dt", ind); @@ -1481,7 +1485,7 @@ Node CegConjecturePbe::constructSolution( Node c, Node e, UnifContext& x, int in // get the term enumerator for this type bool success = true; std::map< Node, EnumInfo >::iterator itet; - if( itn->second.d_role==enum_concat_term ){ + if( itn->second.getRole()==enum_concat_term ){ itet = itn; }else{ std::map< unsigned, Node >::iterator itnt = itt->second.d_enum.find( enum_concat_term ); @@ -1518,7 +1522,7 @@ Node CegConjecturePbe::constructSolution( Node c, Node e, UnifContext& x, int in Trace("sygus-pbe-dt-debug") << " ...not currently string solved." << std::endl; } } - }else if( itn->second.d_role==enum_io && !x.isReturnValueModified() ){ + }else if( itn->second.getRole()==enum_io && !x.isReturnValueModified() ){ // it has an enumerated value that is conditionally correct under the current assumptions std::vector< Node > subsumed_by; itn->second.d_term_trie.getSubsumedBy( this, x.d_vals, true, subsumed_by ); @@ -1552,31 +1556,33 @@ Node CegConjecturePbe::constructSolution( Node c, Node e, UnifContext& x, int in int incr_type = 0; std::map< Node, std::vector< unsigned > > incr; - // construct the child order + // construct the child order based on heuristics std::vector< unsigned > corder; + std::unordered_set< unsigned > cused; if( strat==strat_CONCAT ){ for( unsigned r=0; r<2; r++ ){ + // Concatenate strategy can only be applied from the endpoints. + // This chooses the appropriate endpoint for which we stay within the same SyGuS type. + // In other words, if we are synthesizing based on a production rule ( T -> str.++( T1, ..., Tn ) ), then we + // prefer recursing on the 1st argument of the concat if T1 = T, and the last argument if Tn = T. unsigned sc = r==0 ? 0 : itts->second.d_cenum.size()-1; Node ce = itts->second.d_cenum[sc]; if( ce.getType()==etn ){ // prefer simple recursion (self type) Assert( d_einfo.find( ce )!=d_einfo.end() ); - Assert( d_einfo[ce].d_role==enum_concat_term ); + Assert( d_einfo[ce].getRole()==enum_concat_term ); corder.push_back( sc ); - unsigned inc = r==0 ? 1 : -1; - unsigned scc = sc + inc; - while( scc>=0 && sccsecond.d_cenum.size() ){ - corder.push_back( scc ); - scc = scc + inc; - } + cused.insert( sc ); break; } } - }else{ - for( unsigned sc=0; scsecond.d_cenum.size(); sc++ ){ + } + // fill remaining children for which there is no preference + for( unsigned sc=0; scsecond.d_cenum.size(); sc++ ){ + if( cused.find( sc )==cused.end() ){ corder.push_back( sc ); } - } + } Assert( corder.size()==itts->second.d_cenum.size() ); @@ -1703,7 +1709,7 @@ Node CegConjecturePbe::constructSolution( Node c, Node e, UnifContext& x, int in std::map< Node, EnumInfo >::iterator itsc = d_einfo.find( ce ); Assert( itsc!=d_einfo.end() ); // ensured by the child order we set above - Assert( itsc->second.d_role==enum_concat_term ); + Assert( itsc->second.getRole()==enum_concat_term ); // check if each return value is a prefix/suffix of all open examples incr_type = sc==0 ? -1 : 1; if( x.d_has_string_pos==0 || x.d_has_string_pos==incr_type ){ diff --git a/src/theory/quantifiers/ce_guided_pbe.h b/src/theory/quantifiers/ce_guided_pbe.h index 2f6f591ba..e4c252194 100644 --- a/src/theory/quantifiers/ce_guided_pbe.h +++ b/src/theory/quantifiers/ce_guided_pbe.h @@ -31,21 +31,142 @@ class CegEntailmentInfer; /** CegConjecturePbe * -* This class implements optimizations that target Programming-By-Examples (PBE) -* synthesis conjectures. -* [EX#1] An example of a PBE synthesis conjecture is: +* This class implements optimizations that target synthesis conjectures +* that are in Programming-By-Examples (PBE) form. * -* exists f. forall x. ( x = 0 => f( x ) = 2 ) ^ ( x = 5 => f( x ) = 7 ) ^ ( x = -* 6 => f( x ) = 8 ) +* [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 getSynthFunctionForEnumerator. +* +* 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 */ + 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; } + /** get candidate for enumerator */ + Node getSynthFunctionForEnumerator(Node e); + /** 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& 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; + /** parent conjecture + * This is the data structure that contains global information about the conjecture. + */ 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 @@ -56,7 +177,12 @@ private: * 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 ) + * 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 @@ -65,8 +191,9 @@ private: /** 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 ) */ + /** 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; /** map from enumerators to candidate varaibles (function-to-synthesize). An * enumerator may not be equivalent @@ -74,58 +201,16 @@ private: * approaches (e.g. decision tree construction). */ std::map d_enum_to_candidate; - + /** 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 ); - bool d_is_pbe; -public: - Node d_true; - Node d_false; - enum { - enum_io, - enum_ite_condition, - enum_concat_term, - enum_any, - }; - enum { - strat_ITE, - strat_CONCAT, - strat_ID, - }; -public: - CegConjecturePbe( QuantifiersEngine * qe, CegConjecture * p ); - ~CegConjecturePbe(); - - void initialize( Node n, std::vector< Node >& candidates, std::vector< Node >& lemmas ); - bool getPbeExamples( Node v, std::vector< std::vector< Node > >& exs, - std::vector< Node >& exos, std::vector< Node >& exts); - /** is PBE enabled for any enumerator? */ - bool isPbe() { return d_is_pbe; } - /** get candidate for enumerator */ - Node getCandidateForEnumerator(Node e); - /** 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& ex); - /** get the output value of the i^th I/O example for enumerator e */ - Node getExampleOut(Node e, unsigned i); - int getExampleId(Node n); - /** add the search val, returns an equivalent value (possibly the same) */ - Node addSearchVal(TypeNode tn, Node e, Node bvr); - /** evaluate builtin */ - Node evaluateBuiltin(TypeNode tn, Node bn, Node e, unsigned i); - - private: + + //--------------------------------- PBE search values /** this class is an index of candidate solutions for PBE synthesis */ class PbeTrie { - private: - Node addPbeExampleEval(TypeNode etn, Node e, Node b, std::vector& ex, - CegConjecturePbe* cpbe, unsigned index, - unsigned ntotal); - - public: + public: PbeTrie() {} ~PbeTrie() {} Node d_lazy_child; @@ -133,25 +218,22 @@ public: 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& ex, + CegConjecturePbe* cpbe, unsigned index, + unsigned ntotal); }; - /** trie of candidate solutions tried, for each (enumerator, type), - * where type is a type in the grammar of the space of solutions for a subterm - * of e - */ + /** 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 > d_pbe_trie; - - private: // for registration - void collectEnumeratorTypes( Node c, TypeNode tn, unsigned enum_role ); - void registerEnumerator( Node et, Node c, TypeNode tn, unsigned enum_role, bool inSearch ); - void staticLearnRedundantOps( Node c, std::vector< Node >& lemmas ); - void staticLearnRedundantOps( Node c, Node e, std::map< Node, bool >& visited, std::vector< Node >& redundant, - std::vector< Node >& lemmas, int ind ); - - /** register candidate conditional */ - bool inferTemplate( unsigned k, Node n, std::map< Node, unsigned >& templ_var_index, std::map< unsigned, unsigned >& templ_injection ); - /** get guard status */ - int getGuardStatus( Node g ); -public: + //--------------------------------- end PBE search values + + // -------------------------------- decision tree learning + //index filter class IndexFilter { public: IndexFilter(){} @@ -161,17 +243,11 @@ public: unsigned next( unsigned i ); void clear() { d_next.clear(); } bool isEq( std::vector< Node >& vs, Node v ); - }; -private: + }; // subsumption trie class SubsumeTrie { - private: - 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 ); public: SubsumeTrie(){} - Node d_term; - std::map< Node, SubsumeTrie > d_children; // 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) @@ -180,37 +256,74 @@ private: 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 ); - private: - void getLeavesInternal( CegConjecturePbe * pbe, std::vector< Node >& vals, bool pol, std::map< int, std::vector< Node > >& v, IndexFilter * f, - unsigned index, int status ); - public: // 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 ); - public: + /** 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 + + /** roles for enumerators */ + enum { + enum_io, + enum_ite_condition, + enum_concat_term, + enum_any, + }; + /** print the role with Trace c. */ + static void print_role( const char * c, unsigned r ); + /** strategies for SyGuS datatype types */ + enum { + strat_ITE, + strat_CONCAT, + strat_ID, + }; + /** print the strategy with Trace c. */ + static void print_strat( const char * c, unsigned s ); + + /** information about an enumerator */ class EnumInfo { - private: - /** an OR of all in d_enum_res */ - //std::vector< Node > d_enum_total; - //bool d_enum_total_true; - Node d_enum_solved; public: EnumInfo() : d_role( enum_io ){} - Node d_parent_candidate; + /** 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, unsigned role ){ + d_parent_candidate = c; + d_role = role; + } + bool isTemplated() { return !d_template.isNull(); } + 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; } + unsigned getRole() { return d_role; } + Node d_parent_candidate; // for template Node d_template; Node d_template_arg; - // TODO : make private - unsigned d_role; - Node d_active_guard; std::vector< Node > d_enum_slave; /** values we have enumerated */ @@ -222,16 +335,18 @@ private: std::vector< Node > d_enum_subsume; std::map< Node, unsigned > d_enum_val_to_index; SubsumeTrie d_term_trie; - public: - bool isTemplated() { return !d_template.isNull(); } - 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; } + 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). */ + unsigned d_role; }; + /** maps enumerators to the information above */ std::map< Node, EnumInfo > d_einfo; -private: + + class CandidateInfo; + /** represents a strategy for a SyGuS datatype type */ class EnumTypeInfoStrat { public: unsigned d_this; @@ -239,6 +354,9 @@ private: std::vector< TypeNode > d_csol_cts; std::vector< Node > d_cenum; }; + + + /** stores enumerators and strategies for a SyGuS datatype type */ class EnumTypeInfo { public: EnumTypeInfo() : d_parent( NULL ){} @@ -250,14 +368,23 @@ private: std::map< Node, EnumTypeInfoStrat > d_strat; bool isSolved( CegConjecturePbe * pbe ); }; + + + /** 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; + /** root SyGuS datatype for the function-to-synthesize, + * which encodes the overall syntactic restrictions on the space + * of solutions. + */ TypeNode d_root; + /** Information for each 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; - // role -> sygus type -> enumerator + /** maps sygus datatypes to their enumerator */ std::map< TypeNode, Node > d_search_enum; bool d_check_sol; unsigned d_cond_count; @@ -267,25 +394,27 @@ private: Node getRootEnumerator(); bool isNonTrivial(); }; - // candidate -> sygus type -> info + /** maps a function-to-synthesize to the above information */ std::map< Node, CandidateInfo > d_cinfo; - + + //------------------------------ representation of an enumeration strategy /** add enumerated value */ void addEnumeratedValue( Node x, Node v, std::vector< Node >& lems ); bool getExplanationForEnumeratorExclude( Node c, Node x, Node v, std::vector< Node >& results, EnumInfo& ei, std::vector< Node >& exp ); -private: - // filtering verion - /* - class FilterSubsumeTrie { - public: - SubsumeTrie d_trie; - IndexFilter d_filter; - Node addTerm( Node t, std::vector< bool >& vals, std::vector< Node >& subsumed, bool checkExistsOnly = false ){ - return d_trie.addTerm( t, vals, subsumed, &d_filter, d_filter.start(), checkExistsOnly ); - } - }; - */ + //------------------------------ strategy registration + void collectEnumeratorTypes( Node c, TypeNode tn, unsigned enum_role ); + void registerEnumerator( Node et, Node c, TypeNode tn, unsigned enum_role, bool inSearch ); + void staticLearnRedundantOps( Node c, std::vector< Node >& lemmas ); + void staticLearnRedundantOps( Node c, Node e, std::map< Node, bool >& visited, std::vector< Node >& redundant, + std::vector< Node >& lemmas, int ind ); + + /** register candidate conditional */ + bool inferTemplate( unsigned k, Node n, std::map< Node, unsigned >& templ_var_index, std::map< unsigned, unsigned >& templ_injection ); + //------------------------------ end strategy registration + + + //------------------------------ constructing solutions class UnifContext { public: UnifContext() : d_has_string_pos(0) {} @@ -319,25 +448,35 @@ private: }; /** construct solution */ Node constructSolution( Node c ); + /** helper function for construct solution. + * Construct a solution based on enumerator e for function-to-synthesize c, + * in context x, where ind is the term depth of the context. + */ Node constructSolution( Node c, Node e, 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 ); - void getStringIncrement( bool isPrefix, Node c, Node v, - std::map< Node, unsigned > total_inc, - std::map< Node, std::vector< unsigned > > incr ); -public: - void registerCandidates( std::vector< Node >& candidates ); - void getCandidateList( std::vector< Node >& candidates, std::vector< Node >& clist ); - // lems and candidate values are outputs - bool constructCandidates( std::vector< Node >& enums, std::vector< Node >& enum_values, - std::vector< Node >& candidates, std::vector< Node >& candidate_values, - std::vector< Node >& lems ); + //------------------------------ end constructing solutions + + /** get guard status + * Returns 1 if g is asserted true in the SAT solver. + * Returns -1 if g is asserted false in the SAT solver. + * Returns 0 otherwise. + */ + int getGuardStatus( Node g ); }; diff --git a/test/regress/regress0/sygus/Makefile.am b/test/regress/regress0/sygus/Makefile.am index a297cee81..6dfcb922a 100644 --- a/test/regress/regress0/sygus/Makefile.am +++ b/test/regress/regress0/sygus/Makefile.am @@ -65,7 +65,8 @@ TESTS = commutative.sy \ parse-bv-let.sy \ cegar1.sy \ triv-type-mismatch-si.sy \ - nia-max-square-ns.sy + nia-max-square-ns.sy \ + strings-concat-3-args.sy # sygus tests currently taking too long for make regress diff --git a/test/regress/regress0/sygus/strings-concat-3-args.sy b/test/regress/regress0/sygus/strings-concat-3-args.sy new file mode 100644 index 000000000..3c93c51d3 --- /dev/null +++ b/test/regress/regress0/sygus/strings-concat-3-args.sy @@ -0,0 +1,18 @@ +; EXPECT: unsat +; COMMAND-LINE: --no-dump-synth +(set-logic SLIA) +(synth-fun f ((x String)) String +((Start String (ntString)) + +(ntString String (x "" (str.++ ntStringConst ntString ntString))) + +(ntStringConst String ("a" "b" " ")) + +)) + +; can be solved with concat PBE strategy, although we currently are not (issue #1259) +; regardless, this is small enough to solve quickly +(constraint (= (f "def") "ab def")) + +(check-synth) +