GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_process_conj.h Lines: 4 4 100.0 %
Date: 2021-05-24 Branches: 0 0 0.0 %

Line Exec Source
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