1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds |
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 |
|
* Utility for inferring whether a synthesis conjecture encodes a |
14 |
|
* transition system. |
15 |
|
*/ |
16 |
|
|
17 |
|
#include "cvc5_private.h" |
18 |
|
|
19 |
|
#ifndef CVC5__THEORY__QUANTIFIERS__TRANSITION_INFERENCE_H |
20 |
|
#define CVC5__THEORY__QUANTIFIERS__TRANSITION_INFERENCE_H |
21 |
|
|
22 |
|
#include <map> |
23 |
|
#include <vector> |
24 |
|
|
25 |
|
#include "expr/node.h" |
26 |
|
|
27 |
|
#include "theory/quantifiers/cegqi/inst_strategy_cegqi.h" |
28 |
|
#include "theory/quantifiers/inst_match_trie.h" |
29 |
|
#include "theory/quantifiers/single_inv_partition.h" |
30 |
|
|
31 |
|
namespace cvc5 { |
32 |
|
namespace theory { |
33 |
|
namespace quantifiers { |
34 |
|
|
35 |
|
/** |
36 |
|
* Utility for storing a deterministic trace of a transition system. A trace |
37 |
|
* is stored as a collection of vectors of values that have been taken by |
38 |
|
* the variables of transition system. For example, say we have a transition |
39 |
|
* system with variables x,y,z. Say the precondition constrains these variables |
40 |
|
* to x=1,y=2,z=3, and say that the transition relation admits the single |
41 |
|
* trace: [1,2,3], [2,3,4], [3,4,5]. We store these three vectors of variables |
42 |
|
* in the trie within this class. |
43 |
|
* |
44 |
|
* This utility is used for determining whether a transition system has a |
45 |
|
* deterministic terminating trace and hence a trivial invariant. |
46 |
|
*/ |
47 |
62 |
class DetTrace |
48 |
|
{ |
49 |
|
public: |
50 |
|
/** The current value of the trace */ |
51 |
|
std::vector<Node> d_curr; |
52 |
|
/** |
53 |
|
* Increment the trace: index the current values, if successful (they are |
54 |
|
* not a duplicate of a previous value), update the current values to vals. |
55 |
|
* Returns true if the increment was successful. |
56 |
|
*/ |
57 |
|
bool increment(Node loc, std::vector<Node>& vals); |
58 |
|
/** |
59 |
|
* Construct the formula that this trace represents with respect to variables |
60 |
|
* in vars. For details, see DetTraceTrie::constructFormula below. |
61 |
|
*/ |
62 |
|
Node constructFormula(const std::vector<Node>& vars); |
63 |
|
/** Debug print this trace on trace message c */ |
64 |
|
void print(const char* c) const; |
65 |
|
|
66 |
|
private: |
67 |
|
/** |
68 |
|
* A trie of value vectors for the variables of a transition system. Nodes |
69 |
|
* are stored as data in tries with no children at the leaves of this trie. |
70 |
|
*/ |
71 |
2602 |
class DetTraceTrie |
72 |
|
{ |
73 |
|
public: |
74 |
|
/** the children of this trie */ |
75 |
|
std::map<Node, DetTraceTrie> d_children; |
76 |
|
/** Add data loc to this trie, indexed by val. */ |
77 |
|
bool add(Node loc, const std::vector<Node>& val); |
78 |
|
/** clear the trie */ |
79 |
428 |
void clear() { d_children.clear(); } |
80 |
|
/** |
81 |
|
* Construct the formula corresponding to this trie with respect to |
82 |
|
* variables in vars. For example, if we have indexed [1,2,3] and [2,3,4] |
83 |
|
* and vars is [x,y,z], then this method returns: |
84 |
|
* ( x=1 ^ y=2 ^ z=3 ) V ( x=2 ^ y=3 ^ z=4 ). |
85 |
|
*/ |
86 |
|
Node constructFormula(const std::vector<Node>& vars, unsigned index = 0); |
87 |
|
}; |
88 |
|
/** The above trie data structure for this class */ |
89 |
|
DetTraceTrie d_trie; |
90 |
|
}; |
91 |
|
|
92 |
|
/** |
93 |
|
* Trace increment status, used for incrementTrace below. |
94 |
|
*/ |
95 |
|
enum TraceIncStatus |
96 |
|
{ |
97 |
|
// the trace was successfully incremented to a new value |
98 |
|
TRACE_INC_SUCCESS, |
99 |
|
// the trace terminated |
100 |
|
TRACE_INC_TERMINATE, |
101 |
|
// the trace encountered a bad state (violating the post-condition) |
102 |
|
TRACE_INC_CEX, |
103 |
|
// the trace was invalid |
104 |
|
TRACE_INC_INVALID |
105 |
|
}; |
106 |
|
|
107 |
|
/** |
108 |
|
* This class is used for inferring that an arbitrary synthesis conjecture |
109 |
|
* corresponds to an invariant synthesis problem for some predicate (d_func). |
110 |
|
* |
111 |
|
* The invariant-to-synthesize can either be explicitly given, via a call |
112 |
|
* to initialize( f, vars ), or otherwise inferred if this method is not called. |
113 |
|
*/ |
114 |
1235 |
class TransitionInference |
115 |
|
{ |
116 |
|
public: |
117 |
1237 |
TransitionInference() : d_complete(false) {} |
118 |
|
/** Process the conjecture n |
119 |
|
* |
120 |
|
* This initializes this class with information related to viewing it as a |
121 |
|
* transition system. This means we infer a function, the state variables, |
122 |
|
* the pre/post condition and transition relation. |
123 |
|
* |
124 |
|
* The node n should be the inner body of the negated synthesis conjecture, |
125 |
|
* prior to generating the deep embedding. That is, given: |
126 |
|
* forall f. ~forall x. P( f, x ), |
127 |
|
* this method expects n to be P( f, x ), and the argument f to be the |
128 |
|
* function f above. |
129 |
|
*/ |
130 |
|
void process(Node n, Node f); |
131 |
|
/** |
132 |
|
* Same as above, without specifying f. This will try to infer a function f |
133 |
|
* based on the body of n. |
134 |
|
*/ |
135 |
|
void process(Node n); |
136 |
|
/** |
137 |
|
* Get the function that is the subject of the synthesis problem we are |
138 |
|
* analyzing. |
139 |
|
*/ |
140 |
|
Node getFunction() const; |
141 |
|
/** |
142 |
|
* Get the variables that the function is applied to. These are the free |
143 |
|
* variables of the pre/post condition, and transition relation. These are |
144 |
|
* fresh (Skolem) variables allocated by this class. |
145 |
|
*/ |
146 |
|
void getVariables(std::vector<Node>& vars) const; |
147 |
|
/** |
148 |
|
* Get the pre/post condition, or transition relation that was inferred by |
149 |
|
* this class. |
150 |
|
*/ |
151 |
|
Node getPreCondition() const; |
152 |
|
Node getPostCondition() const; |
153 |
|
Node getTransitionRelation() const; |
154 |
|
/** |
155 |
|
* Was the analysis of the conjecture complete? |
156 |
|
* |
157 |
|
* If this is false, then the system we have inferred does not take into |
158 |
|
* account all of the synthesis conjecture. This is the case when process(...) |
159 |
|
* was called on formula that does not have the shape of a transition system. |
160 |
|
*/ |
161 |
75 |
bool isComplete() const { return d_complete; } |
162 |
|
/** |
163 |
|
* Was the analysis of the conjecture trivial? This is true if the function |
164 |
|
* did not occur in the conjecture. |
165 |
|
*/ |
166 |
5 |
bool isTrivial() const { return d_trivial; } |
167 |
|
|
168 |
|
/** |
169 |
|
* The following two functions are used for computing whether this transition |
170 |
|
* relation is deterministic and terminating. |
171 |
|
* |
172 |
|
* The argument fwd is whether we are going in the forward direction of the |
173 |
|
* transition system (starting from the precondition). |
174 |
|
* |
175 |
|
* If fwd is true, the initializeTrace method returns TRACE_INC_SUCCESS if the |
176 |
|
* precondition consists of a single conjunct of the form |
177 |
|
* ( x1 = t1 ^ ... ^ xn = tn ) |
178 |
|
* where x1...xn are the state variables of the transition system. Otherwise |
179 |
|
* it returns TRACE_INC_INVALID. |
180 |
|
*/ |
181 |
|
TraceIncStatus initializeTrace(DetTrace& dt, bool fwd = true); |
182 |
|
/** |
183 |
|
* Increment the trace dt in direction fwd. |
184 |
|
* |
185 |
|
* If fwd is true, the incrementTrace method returns TRACE_INC_INVALID if the |
186 |
|
* transition relation is not of the form |
187 |
|
* ( x1' = t1[X] ^ ... ^ xn' = tn[X] ^ Q[X] ^ P(x1...xn) ) => P( x1'...xn' ) |
188 |
|
* Otherwise, it returns TRACE_INC_TERMINATE if the values of |
189 |
|
* x1' = t1[dt.d_curr] ^ ... ^ xn' = tn[dt.d_curr] have already been executed |
190 |
|
* on trace dt (the trace has looped), or if |
191 |
|
* x1' = t1[dt.d_curr] ^ ... ^ xn' = tn[dt.d_curr] ^ Q[dt.d_curr] is unsat |
192 |
|
* (the trace has terminated). It returns TRACE_INC_CEX if the postcondition |
193 |
|
* is false for the values t1[dt.d_curr] ^ ... ^ tn[dt.d_curr]. Otherwise, |
194 |
|
* it returns TRACE_INC_SUCCESS. |
195 |
|
*/ |
196 |
|
TraceIncStatus incrementTrace(DetTrace& dt, bool fwd = true); |
197 |
|
/** |
198 |
|
* Constructs the formula corresponding to trace dt with respect to the |
199 |
|
* variables of this class. |
200 |
|
*/ |
201 |
|
Node constructFormulaTrace(DetTrace& dt) const; |
202 |
|
|
203 |
|
private: |
204 |
|
/** |
205 |
|
* The function (predicate) that is the subject of the invariant synthesis |
206 |
|
* problem we are inferring. |
207 |
|
*/ |
208 |
|
Node d_func; |
209 |
|
/** The variables that the function is applied to */ |
210 |
|
std::vector<Node> d_vars; |
211 |
|
/** |
212 |
|
* The variables that the function is applied to in the next state of the |
213 |
|
* inferred transition relation. |
214 |
|
*/ |
215 |
|
std::vector<Node> d_prime_vars; |
216 |
|
/** |
217 |
|
* Was the analysis of the synthesis conjecture passed to the process method |
218 |
|
* of this class complete? |
219 |
|
*/ |
220 |
|
bool d_complete; |
221 |
|
/** Was the analyis trivial? */ |
222 |
|
bool d_trivial; |
223 |
|
|
224 |
|
/** process disjunct |
225 |
|
* |
226 |
|
* The purpose of this function is to infer pre/post/transition conditions |
227 |
|
* for a (possibly unknown) invariant-to-synthesis, given a conjunct from |
228 |
|
* an arbitrary synthesis conjecture. |
229 |
|
* |
230 |
|
* Assume our negated synthesis conjecture is of the form: |
231 |
|
* forall f. exists x. (and (or F11 ... F1{m_1}) ... (or Fn1 ... Fn{m_n})) |
232 |
|
* This method is called on each (or Fi1 ... Fi{m_i}), where topLevel is true |
233 |
|
* for each of Fi1...F1{m_i} and false otherwise. It adds each of Fi1..Fi{m_i} |
234 |
|
* to disjuncts. |
235 |
|
* |
236 |
|
* If this method returns true, then (1) all applications of free function |
237 |
|
* symbols have operator d_func. Note this function may set d_func to a |
238 |
|
* function symbol in n if d_func was null prior to this call. In other words, |
239 |
|
* this method may infer the subject of the invariant synthesis problem; |
240 |
|
* (2) all occurrences of d_func are "top-level", that is, each Fij may be |
241 |
|
* of the form (not) <d_func>( tj ), but otherwise d_func does not occur in |
242 |
|
* (or Fi1 ... Fi{m_i}); (3) there exists at most one occurrence of |
243 |
|
* <d_func>( tj ), and (not <d_func>( tk )). |
244 |
|
* |
245 |
|
* If the above conditions are met, then terms[true] is set to <d_func>( tj ) |
246 |
|
* if Fij is <d_func>( tj ) for some j, and likewise terms[false] |
247 |
|
* is set to <d_func>( tk ) if Fik is (not <d_func>( tk )) for some k. |
248 |
|
* |
249 |
|
* The argument visited caches the results of this function for (topLevel, n). |
250 |
|
*/ |
251 |
|
bool processDisjunct(Node n, |
252 |
|
std::map<bool, Node>& terms, |
253 |
|
std::vector<Node>& disjuncts, |
254 |
|
std::map<bool, std::map<Node, bool> >& visited, |
255 |
|
bool topLevel); |
256 |
|
/** |
257 |
|
* This method infers if the conjunction of disjuncts is equivalent to a |
258 |
|
* conjunction of the form |
259 |
|
* (~) const_var[1] = const_subs[1] ... (~) const_var[n] = const_subs[n] |
260 |
|
* where the above equalities are negated iff reqPol is false, and |
261 |
|
* const_var[1] ... const_var[n] |
262 |
|
* are distinct members of vars |
263 |
|
*/ |
264 |
|
void getConstantSubstitution(const std::vector<Node>& vars, |
265 |
|
const std::vector<Node>& disjuncts, |
266 |
|
std::vector<Node>& const_var, |
267 |
|
std::vector<Node>& const_subs, |
268 |
|
bool reqPol); |
269 |
|
/** get normalized substitution |
270 |
|
* |
271 |
|
* This method takes as input a node curr of the form I( t1, ..., tn ) and |
272 |
|
* a vector of variables pvars, where pvars.size()=n. For each ti that is |
273 |
|
* a variable, it adds ti to vars, and pvars[i] to subs. For each ti that is |
274 |
|
* not a variable, it adds the disequality ti != pvars[i] to disjuncts. |
275 |
|
* |
276 |
|
* This function is used for instance to normalize an arbitrary application of |
277 |
|
* I so that is over arguments pvars. For instance if curr is I(3,5,y) and |
278 |
|
* pvars = { x1,x2,x3 }, then the formula: |
279 |
|
* I(3,5,y) ^ P(y) |
280 |
|
* is equivalent to: |
281 |
|
* x1 != 3 V x2 != 5 V I(x1,x2,x3) V P( y ) { y -> x3 } |
282 |
|
* Here, we add y and x3 to vars and subs respectively, and x1!=3 and x2!=5 |
283 |
|
* to disjuncts. |
284 |
|
*/ |
285 |
|
void getNormalizedSubstitution(Node curr, |
286 |
|
const std::vector<Node>& pvars, |
287 |
|
std::vector<Node>& vars, |
288 |
|
std::vector<Node>& subs, |
289 |
|
std::vector<Node>& disjuncts); |
290 |
|
/** |
291 |
|
* Stores one of the components of the inferred form of the synthesis |
292 |
|
* conjecture (precondition, postcondition, or transition relation). |
293 |
|
*/ |
294 |
3705 |
class Component |
295 |
|
{ |
296 |
|
public: |
297 |
3711 |
Component() {} |
298 |
|
/** The formula that was inferred for this component */ |
299 |
|
Node d_this; |
300 |
|
/** The list of conjuncts of the above formula */ |
301 |
|
std::vector<Node> d_conjuncts; |
302 |
|
/** |
303 |
|
* Maps formulas to the constant equality substitution that it entails. |
304 |
|
* For example, the formula (x=4 ^ y=x+5) may map to { x -> 4, y -> 9 }. |
305 |
|
*/ |
306 |
|
std::map<Node, std::map<Node, Node> > d_const_eq; |
307 |
|
/** Does this component have conjunct c? */ |
308 |
454 |
bool has(Node c) const |
309 |
|
{ |
310 |
908 |
return std::find(d_conjuncts.begin(), d_conjuncts.end(), c) |
311 |
1362 |
!= d_conjuncts.end(); |
312 |
|
} |
313 |
|
}; |
314 |
|
/** Components for the pre/post condition and transition relation. */ |
315 |
|
Component d_pre; |
316 |
|
Component d_post; |
317 |
|
Component d_trans; |
318 |
|
/** |
319 |
|
* Initialize trace dt, loc is a node to identify the trace, fwd is whether |
320 |
|
* we are going in the forward direction of the transition system (starting |
321 |
|
* from the precondition). |
322 |
|
* |
323 |
|
* The argument loc is a conjunct of transition relation that entails that the |
324 |
|
* trace dt has executed in its last step to its current value. For example, |
325 |
|
* if the transition relation is ( x'=x+1 ^ P( x ) ) => P(x'), and our trace's |
326 |
|
* current value was last updated [x:=1] -> [x:=2] based on x'=x+1, then loc |
327 |
|
* is the node x'=x+1. |
328 |
|
*/ |
329 |
|
TraceIncStatus initializeTrace(DetTrace& dt, Node loc, bool fwd = true); |
330 |
|
/** Same as above, for incrementing the trace dt */ |
331 |
|
TraceIncStatus incrementTrace(DetTrace& dt, Node loc, bool fwd = true); |
332 |
|
}; |
333 |
|
|
334 |
|
} // namespace quantifiers |
335 |
|
} // namespace theory |
336 |
|
} // namespace cvc5 |
337 |
|
|
338 |
|
#endif |