1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Mathias Preiner, Haniel Barbosa |
4 |
|
* |
5 |
|
* This file is part of the cvc5 project. |
6 |
|
* |
7 |
|
* Copyright (c) 2009-2021 by the authors listed in the file AUTHORS |
8 |
|
* in the top-level source directory and their institutional affiliations. |
9 |
|
* All rights reserved. See the file COPYING in the top-level source |
10 |
|
* directory for licensing information. |
11 |
|
* **************************************************************************** |
12 |
|
* |
13 |
|
* sygus_unif_io |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "cvc5_private.h" |
17 |
|
|
18 |
|
#ifndef CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_IO_H |
19 |
|
#define CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_IO_H |
20 |
|
|
21 |
|
#include <map> |
22 |
|
#include "theory/quantifiers/sygus/sygus_unif.h" |
23 |
|
|
24 |
|
namespace cvc5 { |
25 |
|
namespace theory { |
26 |
|
namespace quantifiers { |
27 |
|
|
28 |
|
class SygusUnifIo; |
29 |
|
|
30 |
|
/** Unification context |
31 |
|
* |
32 |
|
* This class maintains state information during calls to |
33 |
|
* SygusUnifIo::constructSolution, which implements unification-based |
34 |
|
* approaches for constructing solutions to synthesis conjectures. |
35 |
|
*/ |
36 |
47 |
class UnifContextIo : public UnifContext |
37 |
|
{ |
38 |
|
public: |
39 |
|
UnifContextIo(); |
40 |
|
/** get current role */ |
41 |
|
NodeRole getCurrentRole() override; |
42 |
|
|
43 |
|
/** |
44 |
|
* This intiializes this context based on information in sui regarding the |
45 |
|
* kinds of examples it contains. |
46 |
|
*/ |
47 |
|
void initialize(SygusUnifIo* sui); |
48 |
|
|
49 |
|
//----------for ITE strategy |
50 |
|
/** the value of the context conditional |
51 |
|
* |
52 |
|
* This stores a list of Boolean constants that is the same length of the |
53 |
|
* number of input/output example pairs we are considering. For each i, |
54 |
|
* if d_vals[i] = true, i/o pair #i is active according to this context |
55 |
|
* if d_vals[i] = false, i/o pair #i is inactive according to this context |
56 |
|
*/ |
57 |
|
std::vector<Node> d_vals; |
58 |
|
/** update the examples |
59 |
|
* |
60 |
|
* if pol=true, this method updates d_vals to d_vals & vals |
61 |
|
* if pol=false, this method updates d_vals to d_vals & ( ~vals ) |
62 |
|
*/ |
63 |
|
bool updateContext(SygusUnifIo* sui, std::vector<Node>& vals, bool pol); |
64 |
|
//----------end for ITE strategy |
65 |
|
|
66 |
|
//----------for CONCAT strategies |
67 |
|
/** the position in the strings |
68 |
|
* |
69 |
|
* For each i/o example pair, this stores the length of the current solution |
70 |
|
* for the input of the pair, where the solution for that input is a prefix |
71 |
|
* or |
72 |
|
* suffix of the output of the pair. For example, if our i/o pairs are: |
73 |
|
* f( "abcd" ) = "abcdcd" |
74 |
|
* f( "aa" ) = "aacd" |
75 |
|
* If the solution we have currently constructed is str.++( x1, "c", ... ), |
76 |
|
* then d_str_pos = ( 5, 3 ), where notice that |
77 |
|
* str.++( "abc", "c" ) is a prefix of "abcdcd" and |
78 |
|
* str.++( "aa", "c" ) is a prefix of "aacd". |
79 |
|
*/ |
80 |
|
std::vector<unsigned> d_str_pos; |
81 |
|
/** update the string examples |
82 |
|
* |
83 |
|
* This method updates d_str_pos to d_str_pos + pos, and updates the current |
84 |
|
* role to nrole. |
85 |
|
*/ |
86 |
|
bool updateStringPosition(SygusUnifIo* sui, |
87 |
|
std::vector<size_t>& pos, |
88 |
|
NodeRole nrole); |
89 |
|
/** get current strings |
90 |
|
* |
91 |
|
* This returns the prefix/suffix of the string constants stored in vals |
92 |
|
* of size d_str_pos, and stores the result in ex_vals. For example, if vals |
93 |
|
* is (abcdcd", "aacde") and d_str_pos = ( 5, 3 ), then we add |
94 |
|
* "d" and "de" to ex_vals. |
95 |
|
*/ |
96 |
|
void getCurrentStrings(SygusUnifIo* sui, |
97 |
|
const std::vector<Node>& vals, |
98 |
|
std::vector<Node>& ex_vals); |
99 |
|
/** get string increment |
100 |
|
* |
101 |
|
* If this method returns true, then inc and tot are updated such that |
102 |
|
* for all active indices i, |
103 |
|
* vals[i] is a prefix (or suffix if isPrefix=false) of ex_vals[i], and |
104 |
|
* inc[i] = str.len(vals[i]) |
105 |
|
* for all inactive indices i, inc[i] = 0 |
106 |
|
* We set tot to the sum of inc[i] for i=1,...,n. This indicates the total |
107 |
|
* number of characters incremented across all examples. |
108 |
|
*/ |
109 |
|
bool getStringIncrement(SygusUnifIo* sui, |
110 |
|
bool isPrefix, |
111 |
|
const std::vector<Node>& ex_vals, |
112 |
|
const std::vector<Node>& vals, |
113 |
|
std::vector<size_t>& inc, |
114 |
|
size_t& tot); |
115 |
|
/** returns true if ex_vals[i] = vals[i] for all active indices i. */ |
116 |
|
bool isStringSolved(SygusUnifIo* sui, |
117 |
|
const std::vector<Node>& ex_vals, |
118 |
|
const std::vector<Node>& vals); |
119 |
|
//----------end for CONCAT strategies |
120 |
|
|
121 |
|
/** visited role |
122 |
|
* |
123 |
|
* This is the current set of enumerator/node role pairs we are currently |
124 |
|
* visiting. This set is cleared when the context is updated. |
125 |
|
*/ |
126 |
|
std::map<Node, std::map<NodeRole, bool>> d_visit_role; |
127 |
|
|
128 |
|
private: |
129 |
|
/** true and false nodes */ |
130 |
|
Node d_true; |
131 |
|
Node d_false; |
132 |
|
/** current role (see getCurrentRole). */ |
133 |
|
NodeRole d_curr_role; |
134 |
|
}; |
135 |
|
|
136 |
|
/** Subsumption trie |
137 |
|
* |
138 |
|
* This class manages a set of terms for a PBE sygus enumerator. |
139 |
|
* |
140 |
|
* In PBE sygus, we are interested in, for each term t, the set of I/O examples |
141 |
|
* that it satisfies, which can be represented by a vector of Booleans. |
142 |
|
* For example, given conjecture: |
143 |
|
* f( 1 ) = 2 ^ f( 3 ) = 4 ^ f( -1 ) = 1 ^ f( 5 ) = 5 |
144 |
|
* If solutions for f are of the form (lambda x. [term]), then: |
145 |
|
* Term x satisfies 0001, |
146 |
|
* Term x+1 satisfies 1100, |
147 |
|
* Term 2 satisfies 0100. |
148 |
|
* Above, term 2 is subsumed by term x+1, since the set of I/O examples that |
149 |
|
* x+1 satisfies are a superset of those satisfied by 2. |
150 |
|
*/ |
151 |
6850 |
class SubsumeTrie |
152 |
|
{ |
153 |
|
public: |
154 |
6850 |
SubsumeTrie() {} |
155 |
|
/** |
156 |
|
* Adds term t to the trie, removes all terms that are subsumed by t from the |
157 |
|
* trie and adds them to subsumed. The set of I/O examples that t satisfies |
158 |
|
* is given by (pol ? vals : !vals). |
159 |
|
*/ |
160 |
|
Node addTerm(Node t, |
161 |
|
const std::vector<Node>& vals, |
162 |
|
bool pol, |
163 |
|
std::vector<Node>& subsumed); |
164 |
|
/** |
165 |
|
* Adds term c to the trie, without calculating/updating based on |
166 |
|
* subsumption. This is useful for using this class to store conditionals |
167 |
|
* in ITE strategies, where any conditional whose set of vals is unique |
168 |
|
* (as opposed to not subsumed) is useful. |
169 |
|
*/ |
170 |
|
Node addCond(Node c, const std::vector<Node>& vals, bool pol); |
171 |
|
/** |
172 |
|
* Returns the set of terms that are subsumed by (pol ? vals : !vals). |
173 |
|
*/ |
174 |
|
void getSubsumed(const std::vector<Node>& vals, |
175 |
|
bool pol, |
176 |
|
std::vector<Node>& subsumed); |
177 |
|
/** |
178 |
|
* Returns the set of terms that subsume (pol ? vals : !vals). This |
179 |
|
* is for instance useful when determining whether there exists a term |
180 |
|
* that satisfies all active examples in the decision tree learning |
181 |
|
* algorithm. |
182 |
|
*/ |
183 |
|
void getSubsumedBy(const std::vector<Node>& vals, |
184 |
|
bool pol, |
185 |
|
std::vector<Node>& subsumed_by); |
186 |
|
/** |
187 |
|
* Get the leaves of the trie, which we store in the map v. We consider their |
188 |
|
* evaluation on points such that (pol ? vals : !vals) is true. |
189 |
|
* |
190 |
|
* v[-1] stores the children that always evaluate to !pol, |
191 |
|
* v[1] stores the children that always evaluate to pol, |
192 |
|
* v[0] stores the children that both evaluate to true and false for at least |
193 |
|
* one example. |
194 |
|
*/ |
195 |
|
void getLeaves(const std::vector<Node>& vals, |
196 |
|
bool pol, |
197 |
|
std::map<int, std::vector<Node>>& v); |
198 |
|
/** is this trie empty? */ |
199 |
3771 |
bool isEmpty() { return d_term.isNull() && d_children.empty(); } |
200 |
|
/** clear this trie */ |
201 |
17 |
void clear() |
202 |
|
{ |
203 |
17 |
d_term = Node::null(); |
204 |
17 |
d_children.clear(); |
205 |
17 |
} |
206 |
|
|
207 |
|
private: |
208 |
|
/** the term at this node */ |
209 |
|
Node d_term; |
210 |
|
/** the children nodes of this trie */ |
211 |
|
std::map<Node, SubsumeTrie> d_children; |
212 |
|
/** helper function for above functions */ |
213 |
|
Node addTermInternal(Node t, |
214 |
|
const std::vector<Node>& vals, |
215 |
|
bool pol, |
216 |
|
std::vector<Node>& subsumed, |
217 |
|
bool spol, |
218 |
|
unsigned index, |
219 |
|
int status, |
220 |
|
bool checkExistsOnly, |
221 |
|
bool checkSubsume); |
222 |
|
/** helper function for above functions |
223 |
|
* |
224 |
|
* This adds to v[-1], v[0], v[1] the children of the trie that occur |
225 |
|
* along paths that contain only false (v[-1]), a mix of true/false (v[0]), |
226 |
|
* and only true (v[1]) values for respectively for relevant points. |
227 |
|
* |
228 |
|
* vals/pol is used to determine the relevant points, which impacts which |
229 |
|
* paths of the trie to traverse on this call. |
230 |
|
* In particular, all points such that (pol ? vals[index] : !vals[index]) |
231 |
|
* are relevant. |
232 |
|
* |
233 |
|
* Paths that contain an unknown value for any relevant point are not |
234 |
|
* traversed. In the larger picture, this ensures that terms are not used in a |
235 |
|
* way such that their unknown value is relevant to the overall behavior of |
236 |
|
* a synthesis solution. |
237 |
|
* |
238 |
|
* status holds the current value of v (0,1,-1) that we will be adding to. |
239 |
|
*/ |
240 |
|
void getLeavesInternal(const std::vector<Node>& vals, |
241 |
|
bool pol, |
242 |
|
std::map<int, std::vector<Node>>& v, |
243 |
|
unsigned index, |
244 |
|
int status); |
245 |
|
}; |
246 |
|
|
247 |
|
class SynthConjecture; |
248 |
|
|
249 |
|
/** Sygus unification I/O utility |
250 |
|
* |
251 |
|
* This class implement synthesis-by-unification, where the specification is |
252 |
|
* I/O examples. |
253 |
|
* |
254 |
|
* Since I/O specifications for multiple functions can be fully separated, we |
255 |
|
* assume that this class is used only for a single function to synthesize. |
256 |
|
* |
257 |
|
* In addition to the base class which maintains a strategy tree, this class |
258 |
|
* maintains: |
259 |
|
* (1) A set of input/output examples that are the specification for f. |
260 |
|
* (2) A set of terms that have been enumerated for enumerators (d_ecache). This |
261 |
|
* can be updated via calls to notifyEnumeration. |
262 |
|
*/ |
263 |
|
class SygusUnifIo : public SygusUnif |
264 |
|
{ |
265 |
|
friend class UnifContextIo; |
266 |
|
|
267 |
|
public: |
268 |
|
SygusUnifIo(SynthConjecture* p); |
269 |
|
~SygusUnifIo(); |
270 |
|
|
271 |
|
/** initialize |
272 |
|
* |
273 |
|
* This initializes this class for solving PBE conjectures for |
274 |
|
* function-to-synthesize f. |
275 |
|
* |
276 |
|
* We only initialize for one function f, since I/O specifications across |
277 |
|
* multiple functions can be separated. |
278 |
|
*/ |
279 |
|
void initializeCandidate( |
280 |
|
TermDbSygus* tds, |
281 |
|
Node f, |
282 |
|
std::vector<Node>& enums, |
283 |
|
std::map<Node, std::vector<Node>>& strategy_lemmas) override; |
284 |
|
/** Notify enumeration */ |
285 |
|
void notifyEnumeration(Node e, Node v, std::vector<Node>& lemmas) override; |
286 |
|
|
287 |
|
/** Construct solution */ |
288 |
|
bool constructSolution(std::vector<Node>& sols, |
289 |
|
std::vector<Node>& lemmas) override; |
290 |
|
protected: |
291 |
|
/** The synthesis conjecture */ |
292 |
|
SynthConjecture* d_parent; |
293 |
|
/** the function-to-synthesize */ |
294 |
|
Node d_candidate; |
295 |
|
/** |
296 |
|
* Whether we will try to construct solution on the next call to |
297 |
|
* constructSolution. This flag is set to true when we successfully |
298 |
|
* register a new value for an enumerator. |
299 |
|
*/ |
300 |
|
bool d_check_sol; |
301 |
|
/** The number of values we have enumerated for all enumerators. */ |
302 |
|
unsigned d_cond_count; |
303 |
|
/** The solution for the function of this class, if one has been found */ |
304 |
|
Node d_solution; |
305 |
|
/** the term size of the above solution */ |
306 |
|
unsigned d_sol_term_size; |
307 |
|
/** partial solutions |
308 |
|
* |
309 |
|
* Maps indices for I/O points to a list of solutions for that point, for each |
310 |
|
* type. We may have more than one type for solutions, e.g. for grammar: |
311 |
|
* A -> ite( A, B, C ) | ... |
312 |
|
* where terms of type B and C can both act as solutions. |
313 |
|
*/ |
314 |
|
std::map<size_t, std::map<TypeNode, std::unordered_set<Node>>> d_psolutions; |
315 |
|
/** |
316 |
|
* This flag is set to true if the solution construction was |
317 |
|
* non-deterministic with respect to failure/success. |
318 |
|
* |
319 |
|
* The solution construction for the string concatenation strategy is |
320 |
|
* non-deterministic with respect to success/failure. That is, choosing |
321 |
|
* a particular string may lead to being unsolvable in the recursive calls, |
322 |
|
* whereas others may not. For example, if our pool of enumerated strings is: |
323 |
|
* { "A", x, "B" } |
324 |
|
* and our I/O example is: |
325 |
|
* f( "AC" ) = "ACB" |
326 |
|
* then choosing to consider a solution of the form ( "A" ++ _ ) leads |
327 |
|
* to a recursive call where we are solving for f' in: |
328 |
|
* "A" ++ f'("AC") = "ACB" |
329 |
|
* which is unsolvable since we cannot generate a term starting with "C" |
330 |
|
* from the pool above. Whereas if we would have chosen ( x ++ _ ), this |
331 |
|
* leads to a recursive call where we are solving for f' in: |
332 |
|
* "AC" ++ f'("AC") = "ACB" |
333 |
|
* which can be closed with "B", giving us (x ++ "B") as a solution. |
334 |
|
*/ |
335 |
|
bool d_sol_cons_nondet; |
336 |
|
/** |
337 |
|
* Whether we are using information gain heuristic during solution |
338 |
|
* construction. |
339 |
|
*/ |
340 |
|
bool d_solConsUsingInfoGain; |
341 |
|
/** true and false nodes */ |
342 |
|
Node d_true; |
343 |
|
Node d_false; |
344 |
|
/** input of I/O examples */ |
345 |
|
std::vector<std::vector<Node>> d_examples; |
346 |
|
/** output of I/O examples */ |
347 |
|
std::vector<Node> d_examples_out; |
348 |
|
|
349 |
|
/** |
350 |
|
* This class stores information regarding an enumerator, including: |
351 |
|
* a database of values that have been enumerated for this enumerator. |
352 |
|
*/ |
353 |
122 |
class EnumCache |
354 |
|
{ |
355 |
|
public: |
356 |
122 |
EnumCache() {} |
357 |
|
/** |
358 |
|
* Notify this class that the term v has been enumerated for this enumerator. |
359 |
|
* Its evaluation under the set of examples in sui are stored in results. |
360 |
|
*/ |
361 |
|
void addEnumValue(Node v, std::vector<Node>& results); |
362 |
|
/** |
363 |
|
* Notify this class that slv is the complete solution to the synthesis |
364 |
|
* conjecture. This occurs rarely, for instance, when during an ITE strategy |
365 |
|
* we find that a single enumerated term covers all examples. |
366 |
|
*/ |
367 |
17 |
void setSolved(Node slv) { d_enum_solved = slv; } |
368 |
|
/** Have we been notified that a complete solution exists? */ |
369 |
1102 |
bool isSolved() { return !d_enum_solved.isNull(); } |
370 |
|
/** Get the complete solution to the synthesis conjecture. */ |
371 |
36 |
Node getSolved() { return d_enum_solved; } |
372 |
|
/** Values that have been enumerated for this enumerator */ |
373 |
|
std::vector<Node> d_enum_vals; |
374 |
|
/** |
375 |
|
* This either stores the values of f( I ) for inputs |
376 |
|
* or the value of f( I ) = O if d_role==enum_io |
377 |
|
*/ |
378 |
|
std::vector<std::vector<Node>> d_enum_vals_res; |
379 |
|
/** |
380 |
|
* The set of values in d_enum_vals that have been "subsumed" by others |
381 |
|
* (see SubsumeTrie for explanation of subsumed). |
382 |
|
*/ |
383 |
|
std::vector<Node> d_enum_subsume; |
384 |
|
/** Map from values to their index in d_enum_vals. */ |
385 |
|
std::map<Node, unsigned> d_enum_val_to_index; |
386 |
|
/** |
387 |
|
* A subsumption trie containing the values in d_enum_vals. Depending on the |
388 |
|
* role of this enumerator, values may either be added to d_term_trie with |
389 |
|
* subsumption (if role=enum_io), or without (if role=enum_ite_condition or |
390 |
|
* enum_concat_term). |
391 |
|
*/ |
392 |
|
SubsumeTrie d_term_trie; |
393 |
|
|
394 |
|
private: |
395 |
|
/** |
396 |
|
* Whether an enumerated value for this conjecture has solved the entire |
397 |
|
* conjecture. |
398 |
|
*/ |
399 |
|
Node d_enum_solved; |
400 |
|
}; |
401 |
|
/** maps enumerators to the information above */ |
402 |
|
std::map<Node, EnumCache> d_ecache; |
403 |
|
/** Construct solution node |
404 |
|
* |
405 |
|
* This is called for the (single) function-to-synthesize during a call to |
406 |
|
* constructSolution. If this returns a non-null node, then that term is a |
407 |
|
* solution for the function-to-synthesize in the overall conjecture. |
408 |
|
*/ |
409 |
|
Node constructSolutionNode(std::vector<Node>& lemmas); |
410 |
|
/** domain-specific enumerator exclusion techniques |
411 |
|
* |
412 |
|
* Returns true if the value v for e can be excluded based on a |
413 |
|
* domain-specific exclusion technique like the ones below. |
414 |
|
* |
415 |
|
* results : the values of v under the input examples, |
416 |
|
* exp : if this function returns true, then exp contains a (possibly |
417 |
|
* generalize) explanation for why v can be excluded. |
418 |
|
*/ |
419 |
|
bool getExplanationForEnumeratorExclude( |
420 |
|
Node e, |
421 |
|
Node v, |
422 |
|
std::vector<Node>& results, |
423 |
|
std::vector<Node>& exp); |
424 |
|
/** returns true if we can exlude values of e based on negative str.contains |
425 |
|
* |
426 |
|
* Values v for e may be excluded if we realize that the value of v under the |
427 |
|
* substitution for some input example will never be contained in some output |
428 |
|
* example. For details on this technique, see NegContainsSygusInvarianceTest |
429 |
|
* in sygus_invariance.h. |
430 |
|
* |
431 |
|
* This function depends on whether e is being used to enumerate values |
432 |
|
* for any node that is conditional in the strategy graph. For example, |
433 |
|
* nodes that are children of ITE strategy nodes are conditional. If any node |
434 |
|
* is conditional, then this function returns false. |
435 |
|
*/ |
436 |
|
bool useStrContainsEnumeratorExclude(Node e); |
437 |
|
/** cache for the above function */ |
438 |
|
std::map<Node, bool> d_use_str_contains_eexc; |
439 |
|
/** |
440 |
|
* cache for the above function, stores whether enumerators e are in |
441 |
|
* a conditional context, e.g. used for enumerating the return values for |
442 |
|
* leaves of ITE trees. |
443 |
|
*/ |
444 |
|
std::map<Node, bool> d_use_str_contains_eexc_conditional; |
445 |
|
|
446 |
|
/** the unification context used within constructSolution */ |
447 |
|
UnifContextIo d_context; |
448 |
|
/** initialize construct solution */ |
449 |
|
void initializeConstructSol() override; |
450 |
|
/** initialize construct solution for */ |
451 |
|
void initializeConstructSolFor(Node f) override; |
452 |
|
/** construct solution */ |
453 |
|
Node constructSol(Node f, |
454 |
|
Node e, |
455 |
|
NodeRole nrole, |
456 |
|
int ind, |
457 |
|
std::vector<Node>& lemmas) override; |
458 |
|
/** construct best conditional |
459 |
|
* |
460 |
|
* This returns the condition in conds that maximizes information gain with |
461 |
|
* respect to the current active points in d_context. For example, see |
462 |
|
* Alur et al. TACAS 2017 for an example of information gain. |
463 |
|
*/ |
464 |
|
Node constructBestConditional(Node ce, |
465 |
|
const std::vector<Node>& conds) override; |
466 |
|
}; |
467 |
|
|
468 |
|
} // namespace quantifiers |
469 |
|
} // namespace theory |
470 |
|
} // namespace cvc5 |
471 |
|
|
472 |
|
#endif /* CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_IO_H */ |