1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* 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 |
|
* Techniqures for static preprocessing and analysis of sygus conjectures. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "cvc5_private.h" |
17 |
|
|
18 |
|
#ifndef CVC5__THEORY__QUANTIFIERS__SYGUS_PROCESS_CONJ_H |
19 |
|
#define CVC5__THEORY__QUANTIFIERS__SYGUS_PROCESS_CONJ_H |
20 |
|
|
21 |
|
#include <map> |
22 |
|
#include <unordered_map> |
23 |
|
#include <unordered_set> |
24 |
|
#include <vector> |
25 |
|
|
26 |
|
#include "expr/node.h" |
27 |
|
#include "expr/type_node.h" |
28 |
|
|
29 |
|
namespace cvc5 { |
30 |
|
namespace theory { |
31 |
|
namespace quantifiers { |
32 |
|
|
33 |
|
/** This file contains techniques that compute |
34 |
|
* argument relevancy for synthesis functions |
35 |
|
* |
36 |
|
* Let F be a synthesis conjecture of the form: |
37 |
|
* exists f. forall X. P( f, X ) |
38 |
|
* |
39 |
|
* The classes below compute whether certain arguments of |
40 |
|
* the function-to-synthesize f are irrelevant. |
41 |
|
* Assume that f is a binary function, where possible solutions |
42 |
|
* to the above conjecture are of the form: |
43 |
|
* f -> (lambda (xy) t[x,y]) |
44 |
|
* We say e.g. that the 2nd argument of f is irrelevant if we |
45 |
|
* can determine: |
46 |
|
* F has a solution |
47 |
|
* if and only if |
48 |
|
* F has a solution of the form f -> (lambda (xy) t[x] ) |
49 |
|
* We conclude that arguments are irrelevant using the following |
50 |
|
* techniques. |
51 |
|
* |
52 |
|
* |
53 |
|
* (1) Argument invariance: |
54 |
|
* |
55 |
|
* Let s[z] be a term whose free variables are contained in { z }. |
56 |
|
* If all occurrences of f-applications in F are of the form: |
57 |
|
* f(t, s[t]) |
58 |
|
* then: |
59 |
|
* f = (lambda (xy) r[x,y]) |
60 |
|
* is a solution to F only if: |
61 |
|
* f = (lambda (xy) r[x,s[x]]) |
62 |
|
* is as well. |
63 |
|
* Hence the second argument of f is not relevant. |
64 |
|
* |
65 |
|
* |
66 |
|
* (2) Variable irrelevance: |
67 |
|
* |
68 |
|
* If F is equivalent to: |
69 |
|
* exists f. forall w z u1...un. C1 ^...^Cm, |
70 |
|
* where for i=1...m, Ci is of the form: |
71 |
|
* ( w1 = f(tm1[z], u1) ^ |
72 |
|
* ... ^ |
73 |
|
* wn = f(tmn[z], un) ) => Pm(w1...wn, z) |
74 |
|
* then the second argument of f is irrelevant. |
75 |
|
* We call u1...un single occurrence variables |
76 |
|
* (in Ci). |
77 |
|
* |
78 |
|
* |
79 |
|
* TODO (#1210) others, generalize (2)? |
80 |
|
* |
81 |
|
*/ |
82 |
|
|
83 |
|
/** This structure stores information regarding |
84 |
|
* an argument of a function to synthesize. |
85 |
|
* |
86 |
|
* It is used to store whether the argument |
87 |
|
* position in the function to synthesize is |
88 |
|
* relevant. |
89 |
|
*/ |
90 |
240 |
class SynthConjectureProcessArg |
91 |
|
{ |
92 |
|
public: |
93 |
40 |
SynthConjectureProcessArg() : d_var_single_occ(false), d_relevant(false) {} |
94 |
|
/** template definition |
95 |
|
* This is the term s[z] described |
96 |
|
* under "Argument Invariance" above. |
97 |
|
*/ |
98 |
|
Node d_template; |
99 |
|
/** single occurrence |
100 |
|
* Whether we are trying to show this argument |
101 |
|
* is irrelevant by "Variable irrelevance" |
102 |
|
* described above. |
103 |
|
*/ |
104 |
|
bool d_var_single_occ; |
105 |
|
/** whether this argument is relevant |
106 |
|
* An argument is marked as relevant if: |
107 |
|
* (A) it is explicitly marked as relevant |
108 |
|
* due to a function application containing |
109 |
|
* a relevant term at this argument position, or |
110 |
|
* (B) if it is given conflicting template definitions. |
111 |
|
*/ |
112 |
|
bool d_relevant; |
113 |
|
}; |
114 |
|
|
115 |
|
/** This structure stores information regarding conjecture-specific |
116 |
|
* analysis of a single function to synthesize within |
117 |
|
* a conjecture to synthesize. |
118 |
|
* |
119 |
|
* It maintains information about each of the function to |
120 |
|
* synthesize's arguments. |
121 |
|
*/ |
122 |
|
struct SynthConjectureProcessFun |
123 |
|
{ |
124 |
|
public: |
125 |
4 |
SynthConjectureProcessFun() {} |
126 |
4 |
~SynthConjectureProcessFun() {} |
127 |
|
/** initialize this class for function f */ |
128 |
|
void init(Node f); |
129 |
|
/** process terms |
130 |
|
* |
131 |
|
* This is called once per conjunction in |
132 |
|
* the synthesis conjecture. |
133 |
|
* |
134 |
|
* ns are the f-applications to process, |
135 |
|
* ks are the variables we introduced to flatten them, |
136 |
|
* nf is the flattened form of our conjecture to process, |
137 |
|
* free_vars maps all subterms of n and nf to the set |
138 |
|
* of variables (in set synth_fv) they contain. |
139 |
|
* |
140 |
|
* This updates information regarding which arguments |
141 |
|
* of the function-to-synthesize are relevant. |
142 |
|
*/ |
143 |
|
void processTerms( |
144 |
|
std::vector<Node>& ns, |
145 |
|
std::vector<Node>& ks, |
146 |
|
Node nf, |
147 |
|
std::unordered_set<Node>& synth_fv, |
148 |
|
std::unordered_map<Node, std::unordered_set<Node>>& free_vars); |
149 |
|
/** is the i^th argument of the function-to-synthesize of this class relevant? |
150 |
|
*/ |
151 |
|
bool isArgRelevant(unsigned i); |
152 |
|
/** get irrelevant arguments for the function-to-synthesize of this class */ |
153 |
|
void getIrrelevantArgs(std::unordered_set<unsigned>& args); |
154 |
|
|
155 |
|
private: |
156 |
|
/** the synth fun associated with this */ |
157 |
|
Node d_synth_fun; |
158 |
|
/** properties of each argument */ |
159 |
|
std::vector<SynthConjectureProcessArg> d_arg_props; |
160 |
|
/** variables for each argument type of f |
161 |
|
* |
162 |
|
* These are used to express templates for argument |
163 |
|
* invariance, in the data member |
164 |
|
* SynthConjectureProcessArg::d_template. |
165 |
|
*/ |
166 |
|
std::vector<Node> d_arg_vars; |
167 |
|
/** map from d_arg_vars to the argument # |
168 |
|
* they represent. |
169 |
|
*/ |
170 |
|
std::unordered_map<Node, unsigned> d_arg_var_num; |
171 |
|
/** check match |
172 |
|
* This function returns true iff we can infer: |
173 |
|
* cn * { x -> n_arg_map[d_arg_var_num[x]] | x in d_arg_vars } = n |
174 |
|
* In other words, cn and n are equivalent |
175 |
|
* via the substitution mapping argument variables to terms |
176 |
|
* specified by n_arg_map. The rewriter is used for inferring |
177 |
|
* this equivalence. |
178 |
|
* |
179 |
|
* For example, if n_arg_map contains { 1 -> t, 2 -> s }, then |
180 |
|
* checkMatch( x1+x2, t+s, n_arg_map ) returns true, |
181 |
|
* checkMatch( x1+1, t+1, n_arg_map ) returns true, |
182 |
|
* checkMatch( 0, 0, n_arg_map ) returns true, |
183 |
|
* checkMatch( x1+1, s, n_arg_map ) returns false. |
184 |
|
*/ |
185 |
|
bool checkMatch(Node cn, |
186 |
|
Node n, |
187 |
|
std::unordered_map<unsigned, Node>& n_arg_map); |
188 |
|
/** infer definition |
189 |
|
* |
190 |
|
* In the following, we say a term is a "template |
191 |
|
* definition" if its free variables are a subset of d_arg_vars. |
192 |
|
* |
193 |
|
* If this function returns a non-null node ret, then |
194 |
|
* checkMatch( ret, n, term_to_arg_carry^-1 ) returns true. |
195 |
|
* and ret is a template definition. |
196 |
|
* |
197 |
|
* The free variables for all subterms of n are stored in |
198 |
|
* free_vars. The map term_to_arg_carry is injective. |
199 |
|
* |
200 |
|
* For example, if term_to_arg_carry contains { t -> 1, s -> 2 } and |
201 |
|
* free_vars is { t -> {y}, r -> {y}, s -> {}, q -> {}, ... -> {} }, then |
202 |
|
* inferDefinition( 0, term_to_arg_carry, free_vars ) |
203 |
|
* returns 0 |
204 |
|
* inferDefinition( t, term_to_arg_carry, free_vars ) |
205 |
|
* returns x1 |
206 |
|
* inferDefinition( t+s+q, term_to_arg_carry, free_vars ) |
207 |
|
* returns x1+x2+q |
208 |
|
* inferDefinition( t+r, term_to_arg_carry, free_vars ) |
209 |
|
* returns null |
210 |
|
* |
211 |
|
* Notice that multiple definitions are possible, e.g. above: |
212 |
|
* inferDefinition( s, term_to_arg_carry, free_vars ) |
213 |
|
* may return either s or x2 |
214 |
|
* TODO (#1210) : try multiple definitions? |
215 |
|
*/ |
216 |
|
Node inferDefinition( |
217 |
|
Node n, |
218 |
|
std::unordered_map<Node, unsigned>& term_to_arg_carry, |
219 |
|
std::unordered_map<Node, std::unordered_set<Node>>& free_vars); |
220 |
|
/** Assign relevant definition |
221 |
|
* |
222 |
|
* If def is non-null, |
223 |
|
* this function assigns def as a template definition |
224 |
|
* for the argument positions in args. |
225 |
|
* This is called when there exists a term of the form |
226 |
|
* f( t1....tn ) |
227 |
|
* in the synthesis conjecture that we are processing, |
228 |
|
* where t_i = def * sigma for all i \in args, |
229 |
|
* for some substitution sigma, where def is a template |
230 |
|
* definition. |
231 |
|
* |
232 |
|
* If def is null, then there exists a term of the form |
233 |
|
* f( t1....tn ) |
234 |
|
* where t_i = s for for all i \in args, and s is not |
235 |
|
* a template definition. In this case, at least one |
236 |
|
* argument in args must be marked as a relevant |
237 |
|
* argument position. |
238 |
|
* |
239 |
|
* Returns a value rid such that: |
240 |
|
* (1) rid occurs in args, |
241 |
|
* (2) if def is null, then argument rid was marked |
242 |
|
* relevant by this call. |
243 |
|
*/ |
244 |
|
unsigned assignRelevantDef(Node def, std::vector<unsigned>& args); |
245 |
|
/** returns true if n is in d_arg_vars, updates arg_index |
246 |
|
* to its position in d_arg_vars. |
247 |
|
*/ |
248 |
|
bool isArgVar(Node n, unsigned& arg_index); |
249 |
|
}; |
250 |
|
|
251 |
|
/** Ceg Conjecture Process |
252 |
|
* |
253 |
|
* This class implements static techniques for preprocessing and analysis of |
254 |
|
* sygus conjectures. |
255 |
|
* |
256 |
|
* It is used as a back-end to SynthConjecture, which calls it using the |
257 |
|
* following interface: (1) When a sygus conjecture is asserted, we call |
258 |
|
* SynthConjectureProcess::simplify( q ), |
259 |
|
* where q is the sygus conjecture in original form. |
260 |
|
* |
261 |
|
* (2) After a sygus conjecture is simplified and converted to deep |
262 |
|
* embedding form, we call SynthConjectureProcess::initialize( n, candidates ). |
263 |
|
* |
264 |
|
* (3) During enumerative SyGuS search, calls may be made by |
265 |
|
* the extension of the quantifier-free datatypes decision procedure for |
266 |
|
* sygus to SynthConjectureProcess::getSymmetryBreakingPredicate(...), which are |
267 |
|
* used for pruning search space based on conjecture-specific analysis. |
268 |
|
*/ |
269 |
|
class SynthConjectureProcess |
270 |
|
{ |
271 |
|
public: |
272 |
|
SynthConjectureProcess(); |
273 |
|
~SynthConjectureProcess(); |
274 |
|
/** simplify the synthesis conjecture q |
275 |
|
* Returns a formula that is equivalent to q. |
276 |
|
* This simplification pass is called before all others |
277 |
|
* in SynthConjecture::assign. |
278 |
|
* |
279 |
|
* This function is intended for simplifications that |
280 |
|
* impact whether or not the synthesis conjecture is |
281 |
|
* single-invocation. |
282 |
|
*/ |
283 |
|
Node preSimplify(Node q); |
284 |
|
/** simplify the synthesis conjecture q |
285 |
|
* Returns a formula that is equivalent to q. |
286 |
|
* This simplification pass is called after all others |
287 |
|
* in SynthConjecture::assign. |
288 |
|
*/ |
289 |
|
Node postSimplify(Node q); |
290 |
|
/** initialize |
291 |
|
* |
292 |
|
* n is the "base instantiation" of the deep-embedding version of |
293 |
|
* the synthesis conjecture under "candidates". |
294 |
|
* (see SynthConjecture::d_base_inst) |
295 |
|
*/ |
296 |
|
void initialize(Node n, std::vector<Node>& candidates); |
297 |
|
/** is the i^th argument of the function-to-synthesize f relevant? */ |
298 |
|
bool isArgRelevant(Node f, unsigned i); |
299 |
|
/** get irrelevant arguments for function-to-synthesize f |
300 |
|
* returns true if f is a function-to-synthesize. |
301 |
|
*/ |
302 |
|
bool getIrrelevantArgs(Node f, std::unordered_set<unsigned>& args); |
303 |
|
/** get symmetry breaking predicate |
304 |
|
* |
305 |
|
* Returns a formula that restricts the enumerative search space (for a given |
306 |
|
* depth) for a term x of sygus type tn whose top symbol is the tindex^{th} |
307 |
|
* constructor, where x is a subterm of enumerator e. |
308 |
|
*/ |
309 |
|
Node getSymmetryBreakingPredicate( |
310 |
|
Node x, Node e, TypeNode tn, unsigned tindex, unsigned depth); |
311 |
|
/** print out debug information about this conjecture */ |
312 |
|
void debugPrint(const char* c); |
313 |
|
private: |
314 |
|
/** process conjunct |
315 |
|
* |
316 |
|
* This sets up initial information about functions to synthesize |
317 |
|
* where n is a conjunct of the synthesis conjecture, and synth_fv |
318 |
|
* is the set of (inner) universal variables in the synthesis |
319 |
|
* conjecture. |
320 |
|
*/ |
321 |
|
void processConjunct(Node n, Node f, std::unordered_set<Node>& synth_fv); |
322 |
|
/** flatten |
323 |
|
* |
324 |
|
* Flattens all applications of f in term n. |
325 |
|
* This may add new variables to synth_fv, which |
326 |
|
* are introduced at all positions of functions |
327 |
|
* to synthesize in a bottom-up fashion. For each |
328 |
|
* variable k introduced for a function application |
329 |
|
* f(t), we add ( k -> f(t) ) to defs and ( f -> k ) |
330 |
|
* to fun_to_defs. |
331 |
|
*/ |
332 |
|
Node flatten(Node n, |
333 |
|
Node f, |
334 |
|
std::unordered_set<Node>& synth_fv, |
335 |
|
std::unordered_map<Node, Node>& defs); |
336 |
|
/** get free variables |
337 |
|
* Constructs a map of all free variables that occur in n |
338 |
|
* from synth_fv and stores them in the map free_vars. |
339 |
|
*/ |
340 |
|
void getFreeVariables( |
341 |
|
Node n, |
342 |
|
std::unordered_set<Node>& synth_fv, |
343 |
|
std::unordered_map<Node, std::unordered_set<Node>>& free_vars); |
344 |
|
/** for each synth-fun, information that is specific to this conjecture */ |
345 |
|
std::map<Node, SynthConjectureProcessFun> d_sf_info; |
346 |
|
|
347 |
|
/** get component vector */ |
348 |
|
void getComponentVector(Kind k, Node n, std::vector<Node>& args); |
349 |
|
}; |
350 |
|
|
351 |
|
} // namespace quantifiers |
352 |
|
} // namespace theory |
353 |
|
} // namespace cvc5 |
354 |
|
|
355 |
|
#endif |