1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Haniel Barbosa, Andrew Reynolds, Mathias Preiner |
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_rl |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "cvc5_private.h" |
17 |
|
|
18 |
|
#ifndef CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_RL_H |
19 |
|
#define CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_RL_H |
20 |
|
|
21 |
|
#include <map> |
22 |
|
|
23 |
|
#include "options/main_options.h" |
24 |
|
#include "theory/quantifiers/lazy_trie.h" |
25 |
|
#include "theory/quantifiers/sygus/sygus_unif.h" |
26 |
|
#include "util/bool.h" |
27 |
|
|
28 |
|
namespace cvc5 { |
29 |
|
namespace theory { |
30 |
|
namespace quantifiers { |
31 |
|
|
32 |
|
using BoolNodePair = std::pair<bool, Node>; |
33 |
|
using BoolNodePairHashFunction = |
34 |
|
PairHashFunction<bool, Node, BoolHashFunction, std::hash<Node>>; |
35 |
|
using BoolNodePairMap = |
36 |
|
std::unordered_map<BoolNodePair, Node, BoolNodePairHashFunction>; |
37 |
|
using NodePairMap = std::unordered_map<Node, Node>; |
38 |
|
using NodePair = std::pair<Node, Node>; |
39 |
|
|
40 |
|
class SynthConjecture; |
41 |
|
|
42 |
|
/** Sygus unification Refinement Lemmas utility |
43 |
|
* |
44 |
|
* This class implement synthesis-by-unification, where the specification is a |
45 |
|
* set of refinement lemmas. With respect to SygusUnif, it's main interface |
46 |
|
* function is addExample, which adds a refinement lemma to the specification. |
47 |
|
*/ |
48 |
|
class SygusUnifRl : public SygusUnif |
49 |
|
{ |
50 |
|
public: |
51 |
|
SygusUnifRl(SynthConjecture* p); |
52 |
|
~SygusUnifRl(); |
53 |
|
|
54 |
|
/** initialize */ |
55 |
|
void initializeCandidate( |
56 |
|
TermDbSygus* tds, |
57 |
|
Node f, |
58 |
|
std::vector<Node>& enums, |
59 |
|
std::map<Node, std::vector<Node>>& strategy_lemmas) override; |
60 |
|
|
61 |
|
/** Notify enumeration (unused) */ |
62 |
|
void notifyEnumeration(Node e, Node v, std::vector<Node>& lemmas) override; |
63 |
|
/** Construct solution */ |
64 |
|
bool constructSolution(std::vector<Node>& sols, |
65 |
|
std::vector<Node>& lemmas) override; |
66 |
|
/** add refinement lemma |
67 |
|
* |
68 |
|
* This adds a lemma to the specification. It returns the purified form |
69 |
|
* of the lemma based on the method purifyLemma below. The method adds the |
70 |
|
* head of "evaluation points" to the map eval_hds, where an evaluation point |
71 |
|
* is a term of the form: |
72 |
|
* ev( e1, c1...cn ) |
73 |
|
* where ev is an evaluation function for the sygus deep embedding, e1 is of |
74 |
|
* sygus datatype type (the "head" of the evaluation point), and c1...cn are |
75 |
|
* constants. If ev( e1, c1...cn ) is the purified form of ev( e, t1...tn ), |
76 |
|
* then e1 is added to eval_hds[e]. We add evaluation points to eval_hds only |
77 |
|
* for those terms that are newly generated by this call (and have not been |
78 |
|
* returned by a previous call to this method). |
79 |
|
*/ |
80 |
|
Node addRefLemma(Node lemma, std::map<Node, std::vector<Node>>& eval_hds); |
81 |
|
/** |
82 |
|
* whether f is being synthesized with unification strategies. This can be |
83 |
|
* checked through wehether f has conditional or point enumerators (we use the |
84 |
|
* former) |
85 |
|
*/ |
86 |
|
bool usingUnif(Node f) const; |
87 |
|
/** get condition for evaluation point |
88 |
|
* |
89 |
|
* Returns the strategy point corresponding to the condition of the strategy |
90 |
|
* point e. |
91 |
|
*/ |
92 |
|
Node getConditionForEvaluationPoint(Node e) const; |
93 |
|
/** set conditional enumerators |
94 |
|
* |
95 |
|
* This informs this class that the current set of conditions for evaluation |
96 |
|
* point e are enumerated by "enums" and have values "conds"; "guard" is |
97 |
|
* Boolean variable whose semantics correspond to "there is a solution using |
98 |
|
* at most enums.size() conditions." |
99 |
|
*/ |
100 |
|
void setConditions(Node e, |
101 |
|
Node guard, |
102 |
|
const std::vector<Node>& enums, |
103 |
|
const std::vector<Node>& conds); |
104 |
|
|
105 |
|
/** retrieve the head of evaluation points for candidate c, if any */ |
106 |
|
std::vector<Node> getEvalPointHeads(Node c); |
107 |
|
|
108 |
|
/** |
109 |
|
* Whether we are using condition pool enumeration (Section 4 of Barbosa et al |
110 |
|
* FMCAD 2019). This is determined by option::sygusUnifPi(). |
111 |
|
*/ |
112 |
|
bool usingConditionPool() const; |
113 |
|
/** Whether we are additionally using information gain. */ |
114 |
|
bool usingConditionPoolInfoGain() const; |
115 |
|
|
116 |
|
protected: |
117 |
|
/** reference to the parent conjecture */ |
118 |
|
SynthConjecture* d_parent; |
119 |
|
/** Whether we are using condition pool enumeration */ |
120 |
|
bool d_useCondPool; |
121 |
|
/** Whether we are additionally using information gain heuristics */ |
122 |
|
bool d_useCondPoolIGain; |
123 |
|
/* Functions-to-synthesize (a.k.a. candidates) with unification strategies */ |
124 |
|
std::unordered_set<Node> d_unif_candidates; |
125 |
|
/** construct sol */ |
126 |
|
Node constructSol(Node f, |
127 |
|
Node e, |
128 |
|
NodeRole nrole, |
129 |
|
int ind, |
130 |
|
std::vector<Node>& lemmas) override; |
131 |
|
/** collects data from refinement lemmas to drive solution construction |
132 |
|
* |
133 |
|
* In particular it rebuilds d_app_to_pt whenever d_prev_rlemmas is different |
134 |
|
* from d_rlemmas, in which case we may have added or removed data points |
135 |
|
*/ |
136 |
|
void initializeConstructSol() override; |
137 |
|
/** initialize construction solution for function-to-synthesize f */ |
138 |
|
void initializeConstructSolFor(Node f) override; |
139 |
|
/** maps unif functions-to~synhesize to their last built solutions */ |
140 |
|
std::map<Node, Node> d_cand_to_sol; |
141 |
|
/* |
142 |
|
-------------------------------------------------------------- |
143 |
|
Purification |
144 |
|
-------------------------------------------------------------- |
145 |
|
*/ |
146 |
|
/** |
147 |
|
* maps heads of applications of a unif function-to-synthesize to their tuple |
148 |
|
* of arguments (which constitute a "data point" aka an "evaluation point") |
149 |
|
*/ |
150 |
|
std::map<Node, std::vector<Node>> d_hd_to_pt; |
151 |
|
/** maps unif candidates to heads of their evaluation points */ |
152 |
|
std::map<Node, std::vector<Node>> d_cand_to_eval_hds; |
153 |
|
/** |
154 |
|
* maps applications of unif functions-to-synthesize to the result of their |
155 |
|
* purification */ |
156 |
|
std::map<Node, Node> d_app_to_purified; |
157 |
|
/** maps unif functions-to-synthesize to counters of heads of evaluation |
158 |
|
* points */ |
159 |
|
std::map<Node, unsigned> d_cand_to_hd_count; |
160 |
|
/** |
161 |
|
* This is called on the refinement lemma and will rewrite applications of |
162 |
|
* functions-to-synthesize to their respective purified form, i.e. such that |
163 |
|
* all unification functions are applied over concrete values. Moreover |
164 |
|
* unification functions are also rewritten such that every different tuple of |
165 |
|
* arguments has a fresh function symbol applied to it. |
166 |
|
* |
167 |
|
* Non-unification functions are also equated to their model values when they |
168 |
|
* occur as arguments of unification functions. |
169 |
|
* |
170 |
|
* A vector of guards with the (negated) equalities between the original |
171 |
|
* arguments and their model values is populated accordingly. |
172 |
|
* |
173 |
|
* When the traversal encounters an application of a unification |
174 |
|
* function-to-synthesize it will proceed to ensure that the arguments of that |
175 |
|
* function application are constants (ensureConst becomes "true"). If an |
176 |
|
* applicatin of a non-unif function-to-synthesize is reached, the requirement |
177 |
|
* is lifted (ensureConst becomes "false"). This avoides introducing spurious |
178 |
|
* equalities in model_guards. |
179 |
|
* |
180 |
|
* For example if "f" is being synthesized with a unification strategy and "g" |
181 |
|
* is not then the application |
182 |
|
* f(g(f(g(0))))=1 |
183 |
|
* would be purified into |
184 |
|
* g(0) = c1 ^ g(f1(c1)) = c3 => f2(c3) |
185 |
|
* |
186 |
|
* Similarly |
187 |
|
* f(+(0,f(g(0)))) |
188 |
|
* would be purified into |
189 |
|
* g(0) = c1 ^ f1(c1) = c2 => f2(+(0,c2)) |
190 |
|
* |
191 |
|
* This function also populates the maps between candidates, heads and |
192 |
|
* evaluation points |
193 |
|
*/ |
194 |
|
Node purifyLemma(Node n, |
195 |
|
bool ensureConst, |
196 |
|
std::vector<Node>& model_guards, |
197 |
|
BoolNodePairMap& cache); |
198 |
|
/* |
199 |
|
-------------------------------------------------------------- |
200 |
|
Strategy information |
201 |
|
-------------------------------------------------------------- |
202 |
|
*/ |
203 |
|
/** |
204 |
|
* This class stores the necessary information for building a decision tree |
205 |
|
* for a particular node in the strategy tree of a candidate variable f. |
206 |
|
*/ |
207 |
|
class DecisionTreeInfo |
208 |
|
{ |
209 |
|
public: |
210 |
11 |
DecisionTreeInfo() |
211 |
11 |
: d_unif(nullptr), d_strategy(nullptr), d_strategy_index(0) |
212 |
|
{ |
213 |
11 |
} |
214 |
11 |
~DecisionTreeInfo() {} |
215 |
|
/** initializes this class */ |
216 |
|
void initialize(Node cond_enum, |
217 |
|
SygusUnifRl* unif, |
218 |
|
SygusUnifStrategy* strategy, |
219 |
|
unsigned strategy_index); |
220 |
|
/** returns index of strategy information of strategy node for this DT */ |
221 |
|
unsigned getStrategyIndex() const; |
222 |
|
/** builds solution, if possible, using the given constructor |
223 |
|
* |
224 |
|
* A solution is possible when all different valued heads can be separated, |
225 |
|
* i.e. the current set of conditions separates them in a decision tree |
226 |
|
*/ |
227 |
|
Node buildSol(Node cons, std::vector<Node>& lemmas); |
228 |
|
/** bulids a solution by considering all condition values ever enumerated */ |
229 |
|
Node buildSolAllCond(Node cons, std::vector<Node>& lemmas); |
230 |
|
/** builds a solution by incrementally adding points and conditions to DT |
231 |
|
* |
232 |
|
* Differently from the above method, here a condition is only added to the |
233 |
|
* DT when it's necessary for resolving a separation conflict (i.e. heads |
234 |
|
* with different values in the same leaf of the DT). Only one value per |
235 |
|
* condition enumerated is considered. |
236 |
|
* |
237 |
|
* If a solution cannot be built, then there are more conflicts to be |
238 |
|
* resolved than condition enumerators. A conflict lemma is added to lemmas |
239 |
|
* that forces a new assigment in which the conflict is removed (heads are |
240 |
|
* made equal) or a new condition is enumerated to try to separate them. |
241 |
|
*/ |
242 |
|
Node buildSolMinCond(Node cons, std::vector<Node>& lemmas); |
243 |
|
/** reference to parent unif util */ |
244 |
|
SygusUnifRl* d_unif; |
245 |
|
/** enumerator template (if no templates, nodes in pair are Node::null()) */ |
246 |
|
NodePair d_template; |
247 |
|
/** enumerated condition values, this is set by setConditions(...). */ |
248 |
|
std::vector<Node> d_conds; |
249 |
|
/** gathered evaluation point heads */ |
250 |
|
std::vector<Node> d_hds; |
251 |
|
/** all enumerated model values for conditions */ |
252 |
|
std::unordered_set<Node> d_cond_mvs; |
253 |
|
/** get condition enumerator */ |
254 |
11 |
Node getConditionEnumerator() const { return d_cond_enum; } |
255 |
|
/** set conditions */ |
256 |
|
void setConditions(Node guard, |
257 |
|
const std::vector<Node>& enums, |
258 |
|
const std::vector<Node>& conds); |
259 |
|
|
260 |
|
private: |
261 |
|
/** true and false nodes */ |
262 |
|
Node d_true; |
263 |
|
Node d_false; |
264 |
|
/** Accumulates solutions built when considering all enumerated condition |
265 |
|
* values (which may generate repeated solutions) */ |
266 |
|
std::unordered_set<Node> d_sols; |
267 |
|
/** |
268 |
|
* Conditional enumerator variables corresponding to the condition values in |
269 |
|
* d_conds. These are used for generating separation lemmas during |
270 |
|
* buildSol. This is set by setConditions(...). |
271 |
|
*/ |
272 |
|
std::vector<Node> d_enums; |
273 |
|
/** |
274 |
|
* The guard literal whose semantics is "we need at most d_enums.size() |
275 |
|
* conditions in our solution. This is set by setConditions(...). |
276 |
|
*/ |
277 |
|
Node d_guard; |
278 |
|
/** |
279 |
|
* reference to inferred strategy for the function-to-synthesize this DT is |
280 |
|
* associated with |
281 |
|
*/ |
282 |
|
SygusUnifStrategy* d_strategy; |
283 |
|
/** index of strategy information of strategy node this DT is based on |
284 |
|
* |
285 |
|
* this is the index of the strategy (d_strats[index]) in the strategy node |
286 |
|
* to which this decision tree corresponds, which can be accessed through |
287 |
|
* the above strategy reference |
288 |
|
*/ |
289 |
|
unsigned d_strategy_index; |
290 |
|
/** |
291 |
|
* The enumerator in the strategy tree that stores conditions of the |
292 |
|
* decision tree. |
293 |
|
*/ |
294 |
|
Node d_cond_enum; |
295 |
|
/** extracts solution from decision tree built |
296 |
|
* |
297 |
|
* Depending on the active options, the decision tree might be rebuilt |
298 |
|
* before a solution is extracted, for example to optimize size (smaller |
299 |
|
* DTs) or chance of having a general solution (information gain heuristics) |
300 |
|
*/ |
301 |
|
Node extractSol(Node cons, std::map<Node, Node>& hd_mv); |
302 |
|
|
303 |
|
/** rebuild decision tree using information gain heuristic |
304 |
|
* |
305 |
|
* In a scenario in which the decision tree potentially contains more |
306 |
|
* conditions than necessary, it is beneficial to rebuild it in a way that |
307 |
|
* "better" conditions occurr closer to the top. |
308 |
|
* |
309 |
|
* The information gain heuristic selects conditions that lead to a |
310 |
|
* greater reduction of the Shannon entropy in the set of points |
311 |
|
*/ |
312 |
|
void recomputeSolHeuristically(std::map<Node, Node>& hd_mv); |
313 |
|
/** recursively select (best) conditions to split heads |
314 |
|
* |
315 |
|
* At each call picks the best condition based on the information gain |
316 |
|
* heuristic and splits the set of heads accordingly, then recurses on |
317 |
|
* them. |
318 |
|
* |
319 |
|
* The base case is a set being fully classified (i.e. all heads have the |
320 |
|
* same value) |
321 |
|
* |
322 |
|
* hds is the set of evaluation point heads we must classify with the |
323 |
|
* values in conds. The classification is guided by how a condition value |
324 |
|
* splits the heads through its evaluation on the points associated with |
325 |
|
* the heads. The metric is based on the model values of the heads (hd_mv) |
326 |
|
* in the resulting splits. |
327 |
|
* |
328 |
|
* ind is the current level of indentation (for debugging) |
329 |
|
*/ |
330 |
|
void buildDtInfoGain(std::vector<Node>& hds, |
331 |
|
std::vector<Node> conds, |
332 |
|
std::map<Node, Node>& hd_mv, |
333 |
|
int ind); |
334 |
|
/** computes the Shannon entropy of a set of heads |
335 |
|
* |
336 |
|
* The entropy depends on how many positive and negative heads are in the |
337 |
|
* set and in their distribution. The polarity of the evaluation heads is |
338 |
|
* queried from their model values in hd_mv. |
339 |
|
* |
340 |
|
* ind is the current level of indentation (for debugging) |
341 |
|
*/ |
342 |
|
double getEntropy(const std::vector<Node>& hds, |
343 |
|
std::map<Node, Node>& hd_mv, |
344 |
|
int ind); |
345 |
|
/** evaluates a condition on a set of points |
346 |
|
* |
347 |
|
* The result is two sets of points: those on which the condition holds |
348 |
|
* and those on which it does not |
349 |
|
*/ |
350 |
|
std::pair<std::vector<Node>, std::vector<Node>> evaluateCond( |
351 |
|
std::vector<Node>& pts, Node cond); |
352 |
|
/** Classifies evaluation points according to enumerated condition values |
353 |
|
* |
354 |
|
* Maintains the invariant that points evaluated in the same way in the |
355 |
|
* current condition values are kept in the same "separation class." |
356 |
|
*/ |
357 |
1116718 |
class PointSeparator : public LazyTrieEvaluator |
358 |
|
{ |
359 |
|
public: |
360 |
11 |
PointSeparator() : d_dt(nullptr) {} |
361 |
|
/** initializes this class */ |
362 |
|
void initialize(DecisionTreeInfo* dt); |
363 |
|
/** |
364 |
|
* evaluates the respective evaluation point of the head n on the index-th |
365 |
|
* condition |
366 |
|
*/ |
367 |
|
Node evaluate(Node n, unsigned index) override; |
368 |
|
|
369 |
|
/** the lazy trie for building the separation classes */ |
370 |
|
LazyTrieMulti d_trie; |
371 |
|
/** extracts solution from decision tree built */ |
372 |
|
Node extractSol(Node cons, std::map<Node, Node>& hd_mv); |
373 |
|
/** computes the result of applying cond on the respective point of hd |
374 |
|
* |
375 |
|
* If for example cond is (\lambda xy. x < y) and hd is an evaluation head |
376 |
|
* in point (hd 0 1) this function will result in true, since |
377 |
|
* (\lambda xy. x < y) 0 1 evaluates to true |
378 |
|
*/ |
379 |
|
Node computeCond(Node cond, Node hd); |
380 |
|
|
381 |
|
private: |
382 |
|
/** reference to parent unif util */ |
383 |
|
DecisionTreeInfo* d_dt; |
384 |
|
/** cache of conditions evaluations on heads |
385 |
|
* |
386 |
|
* If for example cond is (\lambda xy. x < y) and hd is an evaluation head |
387 |
|
* in point (hd 0 1), then after invoking computeCond(cond, hd) this map |
388 |
|
* will contain d_eval_cond_hd[<cond, hd>] = true, since |
389 |
|
* |
390 |
|
* (\lambda xy. x < y) 0 1 evaluates to true |
391 |
|
*/ |
392 |
|
std::map<std::pair<Node, Node>, Node> d_eval_cond_hd; |
393 |
|
}; |
394 |
|
/** |
395 |
|
* Utility for determining how evaluation points are separated by currently |
396 |
|
* enumerated condiotion values |
397 |
|
*/ |
398 |
|
PointSeparator d_pt_sep; |
399 |
|
}; |
400 |
|
/** maps strategy points in the strategy tree to the above data */ |
401 |
|
std::map<Node, DecisionTreeInfo> d_stratpt_to_dt; |
402 |
|
/** maps conditional enumerators to strategy points in which they occur */ |
403 |
|
std::map<Node, std::vector<Node>> d_cenum_to_stratpt; |
404 |
|
/** maps unif candidates to their conditional enumerators */ |
405 |
|
std::map<Node, std::vector<Node>> d_cand_cenums; |
406 |
|
/** all conditional enumerators */ |
407 |
|
std::vector<Node> d_cond_enums; |
408 |
|
/** register strategy |
409 |
|
* |
410 |
|
* Initialize the above data for the relevant enumerators in the strategy tree |
411 |
|
* of candidate variable f. For each strategy point e which there is a |
412 |
|
* decision tree strategy, we add e to enums. For each strategy with index |
413 |
|
* i in an strategy point e, if we are not using the strategy, we add i to |
414 |
|
* unused_strats[e]. This map is later passed to |
415 |
|
* SygusUnifStrategy::staticLearnRedundantOps. |
416 |
|
*/ |
417 |
|
void registerStrategy( |
418 |
|
Node f, |
419 |
|
std::vector<Node>& enums, |
420 |
|
std::map<Node, std::unordered_set<unsigned>>& unused_strats); |
421 |
|
/** register strategy node |
422 |
|
* |
423 |
|
* Called while traversing the strategy tree of f. The arguments e and nrole |
424 |
|
* indicate the current node in the tree we are traversing, and visited |
425 |
|
* indicates the nodes we have already visited. The arguments enums and |
426 |
|
* unused_strats are modified as described above. |
427 |
|
*/ |
428 |
|
void registerStrategyNode( |
429 |
|
Node f, |
430 |
|
Node e, |
431 |
|
NodeRole nrole, |
432 |
|
std::map<Node, std::map<NodeRole, bool>>& visited, |
433 |
|
std::vector<Node>& enums, |
434 |
|
std::map<Node, std::unordered_set<unsigned>>& unused_strats); |
435 |
|
/** register conditional enumerator |
436 |
|
* |
437 |
|
* Registers that cond is a conditional enumerator for building a (recursive) |
438 |
|
* decision tree at strategy node e within the strategy tree of f. |
439 |
|
*/ |
440 |
|
void registerConditionalEnumerator(Node f, |
441 |
|
Node e, |
442 |
|
Node cond, |
443 |
|
unsigned strategy_index); |
444 |
|
}; |
445 |
|
|
446 |
|
} // namespace quantifiers |
447 |
|
} // namespace theory |
448 |
|
} // namespace cvc5 |
449 |
|
|
450 |
|
#endif /* CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_RL_H */ |