GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_unif_io.h Lines: 13 13 100.0 %
Date: 2021-09-29 Branches: 6 10 60.0 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Mathias Preiner, Haniel Barbosa
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_io
14
 */
15
16
#include "cvc5_private.h"
17
18
#ifndef CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_IO_H
19
#define CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_IO_H
20
21
#include <map>
22
#include "theory/quantifiers/sygus/sygus_unif.h"
23
24
namespace cvc5 {
25
namespace theory {
26
namespace quantifiers {
27
28
class SygusUnifIo;
29
30
/** Unification context
31
  *
32
  * This class maintains state information during calls to
33
  * SygusUnifIo::constructSolution, which implements unification-based
34
  * approaches for constructing solutions to synthesis conjectures.
35
  */
36
47
class UnifContextIo : public UnifContext
37
{
38
 public:
39
  UnifContextIo();
40
  /** get current role */
41
  NodeRole getCurrentRole() override;
42
43
  /**
44
   * This intiializes this context based on information in sui regarding the
45
   * kinds of examples it contains.
46
   */
47
  void initialize(SygusUnifIo* sui);
48
49
  //----------for ITE strategy
50
  /** the value of the context conditional
51
  *
52
  * This stores a list of Boolean constants that is the same length of the
53
  * number of input/output example pairs we are considering. For each i,
54
  * if d_vals[i] = true, i/o pair #i is active according to this context
55
  * if d_vals[i] = false, i/o pair #i is inactive according to this context
56
  */
57
  std::vector<Node> d_vals;
58
  /** update the examples
59
  *
60
  * if pol=true, this method updates d_vals to d_vals & vals
61
  * if pol=false, this method updates d_vals to d_vals & ( ~vals )
62
  */
63
  bool updateContext(SygusUnifIo* sui, std::vector<Node>& vals, bool pol);
64
  //----------end for ITE strategy
65
66
  //----------for CONCAT strategies
67
  /** the position in the strings
68
  *
69
  * For each i/o example pair, this stores the length of the current solution
70
  * for the input of the pair, where the solution for that input is a prefix
71
  * or
72
  * suffix of the output of the pair. For example, if our i/o pairs are:
73
  *   f( "abcd" ) = "abcdcd"
74
  *   f( "aa" ) = "aacd"
75
  * If the solution we have currently constructed is str.++( x1, "c", ... ),
76
  * then d_str_pos = ( 5, 3 ), where notice that
77
  *   str.++( "abc", "c" ) is a prefix of "abcdcd" and
78
  *   str.++( "aa", "c" ) is a prefix of "aacd".
79
  */
80
  std::vector<unsigned> d_str_pos;
81
  /** update the string examples
82
  *
83
  * This method updates d_str_pos to d_str_pos + pos, and updates the current
84
  * role to nrole.
85
  */
86
  bool updateStringPosition(SygusUnifIo* sui,
87
                            std::vector<size_t>& pos,
88
                            NodeRole nrole);
89
  /** get current strings
90
  *
91
  * This returns the prefix/suffix of the string constants stored in vals
92
  * of size d_str_pos, and stores the result in ex_vals. For example, if vals
93
  * is (abcdcd", "aacde") and d_str_pos = ( 5, 3 ), then we add
94
  * "d" and "de" to ex_vals.
95
  */
96
  void getCurrentStrings(SygusUnifIo* sui,
97
                         const std::vector<Node>& vals,
98
                         std::vector<Node>& ex_vals);
99
  /** get string increment
100
  *
101
  * If this method returns true, then inc and tot are updated such that
102
  *   for all active indices i,
103
  *      vals[i] is a prefix (or suffix if isPrefix=false) of ex_vals[i], and
104
  *      inc[i] = str.len(vals[i])
105
  *   for all inactive indices i, inc[i] = 0
106
  * We set tot to the sum of inc[i] for i=1,...,n. This indicates the total
107
  * number of characters incremented across all examples.
108
  */
109
  bool getStringIncrement(SygusUnifIo* sui,
110
                          bool isPrefix,
111
                          const std::vector<Node>& ex_vals,
112
                          const std::vector<Node>& vals,
113
                          std::vector<size_t>& inc,
114
                          size_t& tot);
115
  /** returns true if ex_vals[i] = vals[i] for all active indices i. */
116
  bool isStringSolved(SygusUnifIo* sui,
117
                      const std::vector<Node>& ex_vals,
118
                      const std::vector<Node>& vals);
119
  //----------end for CONCAT strategies
120
121
  /** visited role
122
  *
123
  * This is the current set of enumerator/node role pairs we are currently
124
  * visiting. This set is cleared when the context is updated.
125
  */
126
  std::map<Node, std::map<NodeRole, bool>> d_visit_role;
127
128
 private:
129
  /** true and false nodes */
130
  Node d_true;
131
  Node d_false;
132
  /** current role (see getCurrentRole). */
133
  NodeRole d_curr_role;
134
};
135
136
/** Subsumption trie
137
*
138
* This class manages a set of terms for a PBE sygus enumerator.
139
*
140
* In PBE sygus, we are interested in, for each term t, the set of I/O examples
141
* that it satisfies, which can be represented by a vector of Booleans.
142
* For example, given conjecture:
143
*   f( 1 ) = 2 ^ f( 3 ) = 4 ^ f( -1 ) = 1 ^ f( 5 ) = 5
144
* If solutions for f are of the form (lambda x. [term]), then:
145
*   Term x satisfies 0001,
146
*   Term x+1 satisfies 1100,
147
*   Term 2 satisfies 0100.
148
* Above, term 2 is subsumed by term x+1, since the set of I/O examples that
149
* x+1 satisfies are a superset of those satisfied by 2.
150
*/
151
6848
class SubsumeTrie
152
{
153
 public:
154
6848
  SubsumeTrie() {}
155
  /**
156
  * Adds term t to the trie, removes all terms that are subsumed by t from the
157
  * trie and adds them to subsumed. The set of I/O examples that t satisfies
158
  * is given by (pol ? vals : !vals).
159
  */
160
  Node addTerm(Node t,
161
               const std::vector<Node>& vals,
162
               bool pol,
163
               std::vector<Node>& subsumed);
164
  /**
165
  * Adds term c to the trie, without calculating/updating based on
166
  * subsumption. This is useful for using this class to store conditionals
167
  * in ITE strategies, where any conditional whose set of vals is unique
168
  * (as opposed to not subsumed) is useful.
169
  */
170
  Node addCond(Node c, const std::vector<Node>& vals, bool pol);
171
  /**
172
    * Returns the set of terms that are subsumed by (pol ? vals : !vals).
173
    */
174
  void getSubsumed(const std::vector<Node>& vals,
175
                   bool pol,
176
                   std::vector<Node>& subsumed);
177
  /**
178
    * Returns the set of terms that subsume (pol ? vals : !vals). This
179
    * is for instance useful when determining whether there exists a term
180
    * that satisfies all active examples in the decision tree learning
181
    * algorithm.
182
    */
183
  void getSubsumedBy(const std::vector<Node>& vals,
184
                     bool pol,
185
                     std::vector<Node>& subsumed_by);
186
  /**
187
   * Get the leaves of the trie, which we store in the map v. We consider their
188
   * evaluation on points such that (pol ? vals : !vals) is true.
189
   *
190
   * v[-1] stores the children that always evaluate to !pol,
191
   * v[1] stores the children that always evaluate to pol,
192
   * v[0] stores the children that both evaluate to true and false for at least
193
   * one example.
194
   */
195
  void getLeaves(const std::vector<Node>& vals,
196
                 bool pol,
197
                 std::map<int, std::vector<Node>>& v);
198
  /** is this trie empty? */
199
3748
  bool isEmpty() { return d_term.isNull() && d_children.empty(); }
200
  /** clear this trie */
201
17
  void clear()
202
  {
203
17
    d_term = Node::null();
204
17
    d_children.clear();
205
17
  }
206
207
 private:
208
  /** the term at this node */
209
  Node d_term;
210
  /** the children nodes of this trie */
211
  std::map<Node, SubsumeTrie> d_children;
212
  /** helper function for above functions */
213
  Node addTermInternal(Node t,
214
                       const std::vector<Node>& vals,
215
                       bool pol,
216
                       std::vector<Node>& subsumed,
217
                       bool spol,
218
                       unsigned index,
219
                       int status,
220
                       bool checkExistsOnly,
221
                       bool checkSubsume);
222
  /** helper function for above functions
223
   *
224
   * This adds to v[-1], v[0], v[1] the children of the trie that occur
225
   * along paths that contain only false (v[-1]), a mix of true/false (v[0]),
226
   * and only true (v[1]) values for respectively for relevant points.
227
   *
228
   * vals/pol is used to determine the relevant points, which impacts which
229
   * paths of the trie to traverse on this call.
230
   * In particular, all points such that (pol ? vals[index] : !vals[index])
231
   * are relevant.
232
   *
233
   * Paths that contain an unknown value for any relevant point are not
234
   * traversed. In the larger picture, this ensures that terms are not used in a
235
   * way such that their unknown value is relevant to the overall behavior of
236
   * a synthesis solution.
237
   *
238
   * status holds the current value of v (0,1,-1) that we will be adding to.
239
   */
240
  void getLeavesInternal(const std::vector<Node>& vals,
241
                         bool pol,
242
                         std::map<int, std::vector<Node>>& v,
243
                         unsigned index,
244
                         int status);
245
};
246
247
class SynthConjecture;
248
249
/** Sygus unification I/O utility
250
 *
251
 * This class implement synthesis-by-unification, where the specification is
252
 * I/O examples.
253
 *
254
 * Since I/O specifications for multiple functions can be fully separated, we
255
 * assume that this class is used only for a single function to synthesize.
256
 *
257
 * In addition to the base class which maintains a strategy tree, this class
258
 * maintains:
259
 * (1) A set of input/output examples that are the specification for f.
260
 * (2) A set of terms that have been enumerated for enumerators (d_ecache). This
261
 * can be updated via calls to notifyEnumeration.
262
 */
263
class SygusUnifIo : public SygusUnif
264
{
265
  friend class UnifContextIo;
266
267
 public:
268
  SygusUnifIo(Env& env, SynthConjecture* p);
269
  ~SygusUnifIo();
270
271
  /** initialize
272
   *
273
   * This initializes this class for solving PBE conjectures for
274
   * function-to-synthesize f.
275
   *
276
   * We only initialize for one function f, since I/O specifications across
277
   * multiple functions can be separated.
278
   */
279
  void initializeCandidate(
280
      TermDbSygus* tds,
281
      Node f,
282
      std::vector<Node>& enums,
283
      std::map<Node, std::vector<Node>>& strategy_lemmas) override;
284
  /** Notify enumeration */
285
  void notifyEnumeration(Node e, Node v, std::vector<Node>& lemmas) override;
286
287
  /** Construct solution */
288
  bool constructSolution(std::vector<Node>& sols,
289
                         std::vector<Node>& lemmas) override;
290
 protected:
291
  /** The synthesis conjecture */
292
  SynthConjecture* d_parent;
293
  /** the function-to-synthesize */
294
  Node d_candidate;
295
  /**
296
   * Whether we will try to construct solution on the next call to
297
   * constructSolution. This flag is set to true when we successfully
298
   * register a new value for an enumerator.
299
   */
300
  bool d_check_sol;
301
  /** The number of values we have enumerated for all enumerators. */
302
  unsigned d_cond_count;
303
  /** The solution for the function of this class, if one has been found */
304
  Node d_solution;
305
  /** the term size of the above solution */
306
  unsigned d_sol_term_size;
307
  /** partial solutions
308
   *
309
   * Maps indices for I/O points to a list of solutions for that point, for each
310
   * type. We may have more than one type for solutions, e.g. for grammar:
311
   *   A -> ite( A, B, C ) | ...
312
   * where terms of type B and C can both act as solutions.
313
   */
314
  std::map<size_t, std::map<TypeNode, std::unordered_set<Node>>> d_psolutions;
315
  /**
316
   * This flag is set to true if the solution construction was
317
   * non-deterministic with respect to failure/success.
318
   *
319
   * The solution construction for the string concatenation strategy is
320
   * non-deterministic with respect to success/failure. That is, choosing
321
   * a particular string may lead to being unsolvable in the recursive calls,
322
   * whereas others may not. For example, if our pool of enumerated strings is:
323
   *   { "A", x, "B" }
324
   * and our I/O example is:
325
   *   f( "AC" ) = "ACB"
326
   * then choosing to consider a solution of the form ( "A" ++ _ ) leads
327
   * to a recursive call where we are solving for f' in:
328
   *   "A" ++ f'("AC") = "ACB"
329
   * which is unsolvable since we cannot generate a term starting with "C"
330
   * from the pool above. Whereas if we would have chosen ( x ++ _ ), this
331
   * leads to a recursive call where we are solving for f' in:
332
   *   "AC" ++ f'("AC") = "ACB"
333
   * which can be closed with "B", giving us (x ++ "B") as a solution.
334
   */
335
  bool d_sol_cons_nondet;
336
  /**
337
   * Whether we are using information gain heuristic during solution
338
   * construction.
339
   */
340
  bool d_solConsUsingInfoGain;
341
  /** true and false nodes */
342
  Node d_true;
343
  Node d_false;
344
  /** input of I/O examples */
345
  std::vector<std::vector<Node>> d_examples;
346
  /** output of I/O examples */
347
  std::vector<Node> d_examples_out;
348
349
  /**
350
  * This class stores information regarding an enumerator, including:
351
  * a database of values that have been enumerated for this enumerator.
352
  */
353
122
  class EnumCache
354
  {
355
   public:
356
122
    EnumCache() {}
357
    /**
358
    * Notify this class that the term v has been enumerated for this enumerator.
359
    * Its evaluation under the set of examples in sui are stored in results.
360
    */
361
    void addEnumValue(Node v, std::vector<Node>& results);
362
    /**
363
    * Notify this class that slv is the complete solution to the synthesis
364
    * conjecture. This occurs rarely, for instance, when during an ITE strategy
365
    * we find that a single enumerated term covers all examples.
366
    */
367
17
    void setSolved(Node slv) { d_enum_solved = slv; }
368
    /** Have we been notified that a complete solution exists? */
369
1093
    bool isSolved() { return !d_enum_solved.isNull(); }
370
    /** Get the complete solution to the synthesis conjecture. */
371
36
    Node getSolved() { return d_enum_solved; }
372
    /** Values that have been enumerated for this enumerator */
373
    std::vector<Node> d_enum_vals;
374
    /**
375
      * This either stores the values of f( I ) for inputs
376
      * or the value of f( I ) = O if d_role==enum_io
377
      */
378
    std::vector<std::vector<Node>> d_enum_vals_res;
379
    /**
380
    * The set of values in d_enum_vals that have been "subsumed" by others
381
    * (see SubsumeTrie for explanation of subsumed).
382
    */
383
    std::vector<Node> d_enum_subsume;
384
    /** Map from values to their index in d_enum_vals. */
385
    std::map<Node, unsigned> d_enum_val_to_index;
386
    /**
387
    * A subsumption trie containing the values in d_enum_vals. Depending on the
388
    * role of this enumerator, values may either be added to d_term_trie with
389
    * subsumption (if role=enum_io), or without (if role=enum_ite_condition or
390
    * enum_concat_term).
391
    */
392
    SubsumeTrie d_term_trie;
393
394
   private:
395
    /**
396
      * Whether an enumerated value for this conjecture has solved the entire
397
      * conjecture.
398
      */
399
    Node d_enum_solved;
400
  };
401
  /** maps enumerators to the information above */
402
  std::map<Node, EnumCache> d_ecache;
403
  /** Construct solution node
404
   *
405
   * This is called for the (single) function-to-synthesize during a call to
406
   * constructSolution. If this returns a non-null node, then that term is a
407
   * solution for the function-to-synthesize in the overall conjecture.
408
   */
409
  Node constructSolutionNode(std::vector<Node>& lemmas);
410
  /** domain-specific enumerator exclusion techniques
411
   *
412
   * Returns true if the value v for e can be excluded based on a
413
   * domain-specific exclusion technique like the ones below.
414
   *
415
   * results : the values of v under the input examples,
416
   * exp : if this function returns true, then exp contains a (possibly
417
   * generalize) explanation for why v can be excluded.
418
   */
419
  bool getExplanationForEnumeratorExclude(
420
      Node e,
421
      Node v,
422
      std::vector<Node>& results,
423
      std::vector<Node>& exp);
424
  /** returns true if we can exlude values of e based on negative str.contains
425
   *
426
   * Values v for e may be excluded if we realize that the value of v under the
427
   * substitution for some input example will never be contained in some output
428
   * example. For details on this technique, see NegContainsSygusInvarianceTest
429
   * in sygus_invariance.h.
430
   *
431
   * This function depends on whether e is being used to enumerate values
432
   * for any node that is conditional in the strategy graph. For example,
433
   * nodes that are children of ITE strategy nodes are conditional. If any node
434
   * is conditional, then this function returns false.
435
   */
436
  bool useStrContainsEnumeratorExclude(Node e);
437
  /** cache for the above function */
438
  std::map<Node, bool> d_use_str_contains_eexc;
439
  /**
440
   * cache for the above function, stores whether enumerators e are in
441
   * a conditional context, e.g. used for enumerating the return values for
442
   * leaves of ITE trees.
443
   */
444
  std::map<Node, bool> d_use_str_contains_eexc_conditional;
445
446
  /** the unification context used within constructSolution */
447
  UnifContextIo d_context;
448
  /** initialize construct solution */
449
  void initializeConstructSol() override;
450
  /** initialize construct solution for */
451
  void initializeConstructSolFor(Node f) override;
452
  /** construct solution */
453
  Node constructSol(Node f,
454
                    Node e,
455
                    NodeRole nrole,
456
                    int ind,
457
                    std::vector<Node>& lemmas) override;
458
  /** construct best conditional
459
   *
460
   * This returns the condition in conds that maximizes information gain with
461
   * respect to the current active points in d_context. For example, see
462
   * Alur et al. TACAS 2017 for an example of information gain.
463
   */
464
  Node constructBestConditional(Node ce,
465
                                const std::vector<Node>& conds) override;
466
};
467
468
}  // namespace quantifiers
469
}  // namespace theory
470
}  // namespace cvc5
471
472
#endif /* CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_IO_H */