1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Mathias Preiner, Abdalrhman Mohamed |
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 |
|
* Class that encapsulates techniques for a single (SyGuS) synthesis |
14 |
|
* conjecture. |
15 |
|
*/ |
16 |
|
|
17 |
|
#include "cvc5_private.h" |
18 |
|
|
19 |
|
#ifndef CVC5__THEORY__QUANTIFIERS__SYNTH_CONJECTURE_H |
20 |
|
#define CVC5__THEORY__QUANTIFIERS__SYNTH_CONJECTURE_H |
21 |
|
|
22 |
|
#include <memory> |
23 |
|
|
24 |
|
#include "theory/quantifiers/expr_miner_manager.h" |
25 |
|
#include "theory/quantifiers/sygus/ce_guided_single_inv.h" |
26 |
|
#include "theory/quantifiers/sygus/cegis.h" |
27 |
|
#include "theory/quantifiers/sygus/cegis_core_connective.h" |
28 |
|
#include "theory/quantifiers/sygus/cegis_unif.h" |
29 |
|
#include "theory/quantifiers/sygus/enum_val_generator.h" |
30 |
|
#include "theory/quantifiers/sygus/example_eval_cache.h" |
31 |
|
#include "theory/quantifiers/sygus/example_infer.h" |
32 |
|
#include "theory/quantifiers/sygus/sygus_process_conj.h" |
33 |
|
#include "theory/quantifiers/sygus/sygus_repair_const.h" |
34 |
|
#include "theory/quantifiers/sygus/sygus_stats.h" |
35 |
|
#include "theory/quantifiers/sygus/synth_verify.h" |
36 |
|
#include "theory/quantifiers/sygus/template_infer.h" |
37 |
|
|
38 |
|
namespace cvc5 { |
39 |
|
namespace theory { |
40 |
|
namespace quantifiers { |
41 |
|
|
42 |
|
class CegGrammarConstructor; |
43 |
|
class SygusPbe; |
44 |
|
class SygusStatistics; |
45 |
|
|
46 |
|
/** a synthesis conjecture |
47 |
|
* This class implements approaches for a synthesis conjecture, given by data |
48 |
|
* member d_quant. |
49 |
|
* This includes both approaches for synthesis in Reynolds et al CAV 2015. It |
50 |
|
* determines which approach and optimizations are applicable to the |
51 |
|
* conjecture, and has interfaces for implementing them. |
52 |
|
*/ |
53 |
|
class SynthConjecture |
54 |
|
{ |
55 |
|
public: |
56 |
|
SynthConjecture(QuantifiersState& qs, |
57 |
|
QuantifiersInferenceManager& qim, |
58 |
|
QuantifiersRegistry& qr, |
59 |
|
TermRegistry& tr, |
60 |
|
SygusStatistics& s); |
61 |
|
~SynthConjecture(); |
62 |
|
/** presolve */ |
63 |
|
void presolve(); |
64 |
|
/** get original version of conjecture */ |
65 |
4937 |
Node getConjecture() const { return d_quant; } |
66 |
|
/** get deep embedding version of conjecture */ |
67 |
|
Node getEmbeddedConjecture() const { return d_embed_quant; } |
68 |
|
//-------------------------------for counterexample-guided check/refine |
69 |
|
/** whether the conjecture is waiting for a call to doCheck below */ |
70 |
|
bool needsCheck(); |
71 |
|
/** whether the conjecture is waiting for a call to doRefine below */ |
72 |
|
bool needsRefinement() const; |
73 |
|
/** do syntax-guided enumerative check |
74 |
|
* |
75 |
|
* This is step 2(a) of Figure 3 of Reynolds et al CAV 2015. |
76 |
|
* |
77 |
|
* The method returns true if this conjecture is finished trying solutions |
78 |
|
* for the given call to SynthEngine::check. |
79 |
|
* |
80 |
|
* Notice that we make multiple calls to doCheck on one call to |
81 |
|
* SynthEngine::check. For example, if we are using an actively-generated |
82 |
|
* enumerator, one enumerated (abstract) term may correspond to multiple |
83 |
|
* concrete terms t1, ..., tn to check, where we make up to n calls to doCheck |
84 |
|
* when each of t1, ..., tn fails to satisfy the current refinement lemmas. |
85 |
|
*/ |
86 |
|
bool doCheck(); |
87 |
|
/** do refinement |
88 |
|
* |
89 |
|
* This is step 2(b) of Figure 3 of Reynolds et al CAV 2015. |
90 |
|
* |
91 |
|
* This method is run when needsRefinement() returns true, indicating that |
92 |
|
* the last call to doCheck found a counterexample to the last candidate. |
93 |
|
* |
94 |
|
* This method adds a refinement lemma on the output channel of quantifiers |
95 |
|
* engine. If the refinement lemma is a duplicate, then we manually |
96 |
|
* exclude the current candidate via excludeCurrentSolution. This should |
97 |
|
* only occur when the synthesis conjecture for the current candidate fails |
98 |
|
* to evaluate to false for a given counterexample point, but regardless its |
99 |
|
* negation is satisfiable for the current candidate and that point. This is |
100 |
|
* exclusive to theories with partial functions, e.g. (non-linear) division. |
101 |
|
* |
102 |
|
* This method returns true if a lemma was added on the output channel, and |
103 |
|
* false otherwise. |
104 |
|
*/ |
105 |
|
bool doRefine(); |
106 |
|
//-------------------------------end for counterexample-guided check/refine |
107 |
|
/** |
108 |
|
* Prints the current synthesis solution to output stream out. This is |
109 |
|
* currently used for printing solutions for sygusStream only. We do not |
110 |
|
* enclose solutions in parentheses. |
111 |
|
*/ |
112 |
|
void printSynthSolutionInternal(std::ostream& out); |
113 |
|
/** get synth solutions |
114 |
|
* |
115 |
|
* This method returns true if this class has a solution available to the |
116 |
|
* conjecture that it was assigned. |
117 |
|
* |
118 |
|
* Let q be the synthesis conjecture assigned to this class. |
119 |
|
* This method adds entries to sol_map[q] that map functions-to-synthesize to |
120 |
|
* their builtin solution, which has the same type. For example, for synthesis |
121 |
|
* conjecture exists f. forall x. f( x )>x, this function will update |
122 |
|
* sol_map[q] to contain the entry: |
123 |
|
* f -> (lambda x. x+1) |
124 |
|
*/ |
125 |
|
bool getSynthSolutions(std::map<Node, std::map<Node, Node> >& sol_map); |
126 |
|
/** |
127 |
|
* The feasible guard whose semantics are "this conjecture is feasiable". |
128 |
|
* This is "G" in Figure 3 of Reynolds et al CAV 2015. |
129 |
|
*/ |
130 |
|
Node getGuard() const; |
131 |
|
/** is ground */ |
132 |
|
bool isGround() { return d_inner_vars.empty(); } |
133 |
|
/** are we using single invocation techniques */ |
134 |
|
bool isSingleInvocation() const; |
135 |
|
/** preregister conjecture |
136 |
|
* This is used as a heuristic for solution reconstruction, so that we |
137 |
|
* remember expressions in the conjecture before preprocessing, since they |
138 |
|
* may be helpful during solution reconstruction (Figure 5 of Reynolds et al |
139 |
|
* CAV 2015) |
140 |
|
*/ |
141 |
|
void preregisterConjecture(Node q); |
142 |
|
/** assign conjecture q to this class */ |
143 |
|
void assign(Node q); |
144 |
|
/** has a conjecture been assigned to this class */ |
145 |
588 |
bool isAssigned() { return !d_embed_quant.isNull(); } |
146 |
|
/** |
147 |
|
* Get model value for term n. |
148 |
|
*/ |
149 |
|
Node getModelValue(Node n); |
150 |
|
|
151 |
|
/** get utility for static preprocessing and analysis of conjectures */ |
152 |
300 |
SynthConjectureProcess* getProcess() { return d_ceg_proc.get(); } |
153 |
|
/** get constant repair utility */ |
154 |
110 |
SygusRepairConst* getRepairConst() { return d_sygus_rconst.get(); } |
155 |
|
/** get example inference utility */ |
156 |
12842 |
ExampleInfer* getExampleInfer() { return d_exampleInfer.get(); } |
157 |
|
/** get the example evaluation cache utility for enumerator e */ |
158 |
|
ExampleEvalCache* getExampleEvalCache(Node e); |
159 |
|
/** get program by examples module */ |
160 |
|
SygusPbe* getPbe() { return d_ceg_pbe.get(); } |
161 |
|
/** get the symmetry breaking predicate for type */ |
162 |
|
Node getSymmetryBreakingPredicate( |
163 |
|
Node x, Node e, TypeNode tn, unsigned tindex, unsigned depth); |
164 |
|
/** print out debug information about this conjecture */ |
165 |
|
void debugPrint(const char* c); |
166 |
|
/** check side condition |
167 |
|
* |
168 |
|
* This returns false if the solution { d_candidates -> cvals } does not |
169 |
|
* satisfy the side condition of the conjecture maintained by this class, |
170 |
|
* if it exists, and true otherwise. |
171 |
|
*/ |
172 |
|
bool checkSideCondition(const std::vector<Node>& cvals) const; |
173 |
|
|
174 |
|
/** get a reference to the statistics of parent */ |
175 |
|
SygusStatistics& getSygusStatistics() { return d_stats; }; |
176 |
|
|
177 |
|
private: |
178 |
|
/** Reference to the quantifiers state */ |
179 |
|
QuantifiersState& d_qstate; |
180 |
|
/** Reference to the quantifiers inference manager */ |
181 |
|
QuantifiersInferenceManager& d_qim; |
182 |
|
/** The quantifiers registry */ |
183 |
|
QuantifiersRegistry& d_qreg; |
184 |
|
/** Reference to the term registry */ |
185 |
|
TermRegistry& d_treg; |
186 |
|
/** reference to the statistics of parent */ |
187 |
|
SygusStatistics& d_stats; |
188 |
|
/** term database sygus of d_qe */ |
189 |
|
TermDbSygus* d_tds; |
190 |
|
/** The synthesis verify utility */ |
191 |
|
SynthVerify d_verify; |
192 |
|
/** The feasible guard. */ |
193 |
|
Node d_feasible_guard; |
194 |
|
/** |
195 |
|
* Do we have a solution in this solve context? This flag is reset to false |
196 |
|
* on every call to presolve. |
197 |
|
*/ |
198 |
|
bool d_hasSolution; |
199 |
|
/** the decision strategy for the feasible guard */ |
200 |
|
std::unique_ptr<DecisionStrategy> d_feasible_strategy; |
201 |
|
/** single invocation utility */ |
202 |
|
std::unique_ptr<CegSingleInv> d_ceg_si; |
203 |
|
/** template inference utility */ |
204 |
|
std::unique_ptr<SygusTemplateInfer> d_templInfer; |
205 |
|
/** utility for static preprocessing and analysis of conjectures */ |
206 |
|
std::unique_ptr<SynthConjectureProcess> d_ceg_proc; |
207 |
|
/** grammar utility */ |
208 |
|
std::unique_ptr<CegGrammarConstructor> d_ceg_gc; |
209 |
|
/** repair constant utility */ |
210 |
|
std::unique_ptr<SygusRepairConst> d_sygus_rconst; |
211 |
|
/** example inference utility */ |
212 |
|
std::unique_ptr<ExampleInfer> d_exampleInfer; |
213 |
|
/** example evaluation cache utility for each enumerator */ |
214 |
|
std::map<Node, std::unique_ptr<ExampleEvalCache> > d_exampleEvalCache; |
215 |
|
|
216 |
|
//------------------------modules |
217 |
|
/** program by examples module */ |
218 |
|
std::unique_ptr<SygusPbe> d_ceg_pbe; |
219 |
|
/** CEGIS module */ |
220 |
|
std::unique_ptr<Cegis> d_ceg_cegis; |
221 |
|
/** CEGIS UNIF module */ |
222 |
|
std::unique_ptr<CegisUnif> d_ceg_cegisUnif; |
223 |
|
/** connective core utility */ |
224 |
|
std::unique_ptr<CegisCoreConnective> d_sygus_ccore; |
225 |
|
/** the set of active modules (subset of the above list) */ |
226 |
|
std::vector<SygusModule*> d_modules; |
227 |
|
/** master module |
228 |
|
* |
229 |
|
* This is the module (one of those above) that takes sole responsibility |
230 |
|
* for this conjecture, determined during assign(...). |
231 |
|
*/ |
232 |
|
SygusModule* d_master; |
233 |
|
//------------------------end modules |
234 |
|
|
235 |
|
//------------------------enumerators |
236 |
|
/** |
237 |
|
* Get model values for terms n, store in vector v. This method returns true |
238 |
|
* if and only if all values added to v are non-null. |
239 |
|
* |
240 |
|
* The argument activeIncomplete indicates whether n contains an active |
241 |
|
* enumerator that is currently not finished enumerating values, but returned |
242 |
|
* null on a call to getEnumeratedValue. This value is used for determining |
243 |
|
* whether we should call getEnumeratedValues again within a call to |
244 |
|
* SynthConjecture::check. |
245 |
|
* |
246 |
|
* It removes terms from n that correspond to "inactive" enumerators, that |
247 |
|
* is, enumerators whose values have been exhausted. |
248 |
|
*/ |
249 |
|
bool getEnumeratedValues(std::vector<Node>& n, |
250 |
|
std::vector<Node>& v, |
251 |
|
bool& activeIncomplete); |
252 |
|
/** |
253 |
|
* Get model value for term n. If n has a value that was excluded by |
254 |
|
* datatypes sygus symmetry breaking, this method returns null. It sets |
255 |
|
* activeIncomplete to true if there is an actively-generated enumerator whose |
256 |
|
* current value is null but it has not finished generating values. |
257 |
|
*/ |
258 |
|
Node getEnumeratedValue(Node n, bool& activeIncomplete); |
259 |
|
/** enumerator generators for each actively-generated enumerator */ |
260 |
|
std::map<Node, std::unique_ptr<EnumValGenerator> > d_evg; |
261 |
|
/** |
262 |
|
* Map from enumerators to whether they are currently being |
263 |
|
* "actively-generated". That is, we are in a state where we have called |
264 |
|
* d_evg[e].addValue(v) for some v, and d_evg[e].getNext() has not yet |
265 |
|
* returned null. The range of this map stores the abstract value that |
266 |
|
* we are currently generating values from. |
267 |
|
*/ |
268 |
|
std::map<Node, Node> d_ev_curr_active_gen; |
269 |
|
/** the current waiting value of each actively-generated enumerator, if any |
270 |
|
* |
271 |
|
* This caches values that are actively generated and that we have not yet |
272 |
|
* passed to a call to SygusModule::constructCandidates. An example of when |
273 |
|
* this may occur is when there are two actively-generated enumerators e1 and |
274 |
|
* e2. Say on some iteration we actively-generate v1 for e1, the value |
275 |
|
* of e2 was excluded by symmetry breaking, and say the current master sygus |
276 |
|
* module does not handle partial models. Hence, we abort the current check. |
277 |
|
* We remember that the value of e1 was v1 by storing it here, so that on |
278 |
|
* a future check when v2 has a proper value, it is returned. |
279 |
|
*/ |
280 |
|
std::map<Node, Node> d_ev_active_gen_waiting; |
281 |
|
/** the first value enumerated for each actively-generated enumerator |
282 |
|
* |
283 |
|
* This is to implement an optimization that only guards the blocking lemma |
284 |
|
* for the first value of an actively-generated enumerator. |
285 |
|
*/ |
286 |
|
std::map<Node, Node> d_ev_active_gen_first_val; |
287 |
|
//------------------------end enumerators |
288 |
|
|
289 |
|
/** list of constants for quantified formula |
290 |
|
* The outer Skolems for the negation of d_embed_quant. |
291 |
|
*/ |
292 |
|
std::vector<Node> d_candidates; |
293 |
|
/** base instantiation |
294 |
|
* If d_embed_quant is forall d. exists y. P( d, y ), then |
295 |
|
* this is the formula exists y. P( d_candidates, y ). Notice that |
296 |
|
* (exists y. F) is shorthand above for ~( forall y. ~F ). |
297 |
|
*/ |
298 |
|
Node d_base_inst; |
299 |
|
/** list of variables on inner quantification */ |
300 |
|
std::vector<Node> d_inner_vars; |
301 |
|
/** |
302 |
|
* The set of skolems for the current "verification" lemma, if one exists. |
303 |
|
* This may be added to during calls to doCheck(). The model values for these |
304 |
|
* skolems are analyzed during doRefine(). |
305 |
|
*/ |
306 |
|
std::vector<Node> d_ce_sk_vars; |
307 |
|
/** |
308 |
|
* If we have already tested the satisfiability of the current verification |
309 |
|
* lemma, this stores the model values of d_ce_sk_vars in the current |
310 |
|
* (satisfiable, failed) verification lemma. |
311 |
|
*/ |
312 |
|
std::vector<Node> d_ce_sk_var_mvs; |
313 |
|
/** |
314 |
|
* Whether the above vector has been set. We have this flag since the above |
315 |
|
* vector may be set to empty (e.g. for ground synthesis conjectures). |
316 |
|
*/ |
317 |
|
bool d_set_ce_sk_vars; |
318 |
|
|
319 |
|
/** the asserted (negated) conjecture */ |
320 |
|
Node d_quant; |
321 |
|
/** |
322 |
|
* The side condition for solving the conjecture, after conversion to deep |
323 |
|
* embedding. |
324 |
|
*/ |
325 |
|
Node d_embedSideCondition; |
326 |
|
/** (negated) conjecture after simplification */ |
327 |
|
Node d_simp_quant; |
328 |
|
/** (negated) conjecture after simplification, conversion to deep embedding */ |
329 |
|
Node d_embed_quant; |
330 |
|
/** candidate information */ |
331 |
1842 |
class CandidateInfo |
332 |
|
{ |
333 |
|
public: |
334 |
1842 |
CandidateInfo() {} |
335 |
|
/** list of terms we have instantiated candidates with */ |
336 |
|
std::vector<Node> d_inst; |
337 |
|
}; |
338 |
|
std::map<Node, CandidateInfo> d_cinfo; |
339 |
|
/** |
340 |
|
* The first index of an instantiation in CandidateInfo::d_inst that we have |
341 |
|
* not yet tried to repair. |
342 |
|
*/ |
343 |
|
unsigned d_repair_index; |
344 |
|
/** record solution (this is used to construct solutions later) */ |
345 |
|
void recordSolution(std::vector<Node>& vs); |
346 |
|
/** get synth solutions internal |
347 |
|
* |
348 |
|
* This function constructs the body of solutions for all |
349 |
|
* functions-to-synthesize in this conjecture and stores them in sols, in |
350 |
|
* order. For each solution added to sols, we add an integer indicating what |
351 |
|
* kind of solution n is, where if sols[i] = n, then |
352 |
|
* if status[i] = 0: n is the (builtin term) corresponding to the solution, |
353 |
|
* if status[i] = 1: n is the sygus representation of the solution. |
354 |
|
* We store builtin versions under some conditions (such as when the sygus |
355 |
|
* grammar is being ignored). |
356 |
|
* |
357 |
|
* This consults the single invocation module to get synthesis solutions if |
358 |
|
* isSingleInvocation() returns true. |
359 |
|
* |
360 |
|
* For example, for conjecture exists fg. forall x. f(x)>g(x), this function |
361 |
|
* may set ( sols, status ) to ( { x+1, d_x() }, { 1, 0 } ), where d_x() is |
362 |
|
* the sygus datatype constructor corresponding to variable x. |
363 |
|
*/ |
364 |
|
bool getSynthSolutionsInternal(std::vector<Node>& sols, |
365 |
|
std::vector<int8_t>& status); |
366 |
|
//-------------------------------- sygus stream |
367 |
|
/** |
368 |
|
* Prints the current synthesis solution to the output stream indicated by |
369 |
|
* the Options object, send a lemma blocking the current solution to the |
370 |
|
* output channel, which we refer to as a "stream exclusion lemma". |
371 |
|
* |
372 |
|
* The argument enums is the set of enumerators that comprise the current |
373 |
|
* solution, and values is their current values. |
374 |
|
*/ |
375 |
|
void printAndContinueStream(const std::vector<Node>& enums, |
376 |
|
const std::vector<Node>& values); |
377 |
|
/** exclude the current solution { enums -> values } */ |
378 |
|
void excludeCurrentSolution(const std::vector<Node>& enums, |
379 |
|
const std::vector<Node>& values); |
380 |
|
/** |
381 |
|
* Whether we have guarded a stream exclusion lemma when using sygusStream. |
382 |
|
* This is an optimization that allows us to guard only the first stream |
383 |
|
* exclusion lemma. |
384 |
|
*/ |
385 |
|
bool d_guarded_stream_exc; |
386 |
|
//-------------------------------- end sygus stream |
387 |
|
/** expression miner managers for each function-to-synthesize |
388 |
|
* |
389 |
|
* Notice that for each function-to-synthesize, we enumerate a stream of |
390 |
|
* candidate solutions, where each of these streams is independent. Thus, |
391 |
|
* we maintain separate expression miner managers for each of them. |
392 |
|
* |
393 |
|
* This is used for the sygusRewSynth() option to synthesize new candidate |
394 |
|
* rewrite rules. |
395 |
|
*/ |
396 |
|
std::map<Node, ExpressionMinerManager> d_exprm; |
397 |
|
}; |
398 |
|
|
399 |
|
} // namespace quantifiers |
400 |
|
} // namespace theory |
401 |
|
} // namespace cvc5 |
402 |
|
|
403 |
|
#endif |