GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_unif_rl.h Lines: 7 7 100.0 %
Date: 2021-05-22 Branches: 14 46 30.4 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Haniel Barbosa, 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
 * sygus_unif_rl
14
 */
15
16
#include "cvc5_private.h"
17
18
#ifndef CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_RL_H
19
#define CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_RL_H
20
21
#include <map>
22
#include "options/main_options.h"
23
#include "theory/quantifiers/sygus/sygus_unif.h"
24
25
#include "theory/quantifiers/lazy_trie.h"
26
27
namespace cvc5 {
28
namespace theory {
29
namespace quantifiers {
30
31
using BoolNodePair = std::pair<bool, Node>;
32
using BoolNodePairHashFunction =
33
    PairHashFunction<bool, Node, BoolHashFunction, std::hash<Node>>;
34
using BoolNodePairMap =
35
    std::unordered_map<BoolNodePair, Node, BoolNodePairHashFunction>;
36
using NodePairMap = std::unordered_map<Node, Node>;
37
using NodePair = std::pair<Node, Node>;
38
39
class SynthConjecture;
40
41
/** Sygus unification Refinement Lemmas utility
42
 *
43
 * This class implement synthesis-by-unification, where the specification is a
44
 * set of refinement lemmas. With respect to SygusUnif, it's main interface
45
 * function is addExample, which adds a refinement lemma to the specification.
46
 */
47
class SygusUnifRl : public SygusUnif
48
{
49
 public:
50
  SygusUnifRl(SynthConjecture* p);
51
  ~SygusUnifRl();
52
53
  /** initialize */
54
  void initializeCandidate(
55
      TermDbSygus* tds,
56
      Node f,
57
      std::vector<Node>& enums,
58
      std::map<Node, std::vector<Node>>& strategy_lemmas) override;
59
60
  /** Notify enumeration (unused) */
61
  void notifyEnumeration(Node e, Node v, std::vector<Node>& lemmas) override;
62
  /** Construct solution */
63
  bool constructSolution(std::vector<Node>& sols,
64
                         std::vector<Node>& lemmas) override;
65
  /** add refinement lemma
66
   *
67
   * This adds a lemma to the specification. It returns the purified form
68
   * of the lemma based on the method purifyLemma below. The method adds the
69
   * head of "evaluation points" to the map eval_hds, where an evaluation point
70
   * is a term of the form:
71
   *   ev( e1, c1...cn )
72
   * where ev is an evaluation function for the sygus deep embedding, e1 is of
73
   * sygus datatype type (the "head" of the evaluation point), and c1...cn are
74
   * constants. If ev( e1, c1...cn ) is the purified form of ev( e, t1...tn ),
75
   * then e1 is added to eval_hds[e]. We add evaluation points to eval_hds only
76
   * for those terms that are newly generated by this call (and have not been
77
   * returned by a previous call to this method).
78
   */
79
  Node addRefLemma(Node lemma, std::map<Node, std::vector<Node>>& eval_hds);
80
  /**
81
   * whether f is being synthesized with unification strategies. This can be
82
   * checked through wehether f has conditional or point enumerators (we use the
83
   * former)
84
   */
85
  bool usingUnif(Node f) const;
86
  /** get condition for evaluation point
87
   *
88
   * Returns the strategy point corresponding to the condition of the strategy
89
   * point e.
90
   */
91
  Node getConditionForEvaluationPoint(Node e) const;
92
  /** set conditional enumerators
93
   *
94
   * This informs this class that the current set of conditions for evaluation
95
   * point e are enumerated by "enums" and have values "conds"; "guard" is
96
   * Boolean variable whose semantics correspond to "there is a solution using
97
   * at most enums.size() conditions."
98
   */
99
  void setConditions(Node e,
100
                     Node guard,
101
                     const std::vector<Node>& enums,
102
                     const std::vector<Node>& conds);
103
104
  /** retrieve the head of evaluation points for candidate c, if any */
105
  std::vector<Node> getEvalPointHeads(Node c);
106
107
  /**
108
   * Whether we are using condition pool enumeration (Section 4 of Barbosa et al
109
   * FMCAD 2019). This is determined by option::sygusUnifPi().
110
   */
111
  bool usingConditionPool() const;
112
  /** Whether we are additionally using information gain.  */
113
  bool usingConditionPoolInfoGain() const;
114
115
 protected:
116
  /** reference to the parent conjecture */
117
  SynthConjecture* d_parent;
118
  /** Whether we are using condition pool enumeration */
119
  bool d_useCondPool;
120
  /** Whether we are additionally using information gain heuristics */
121
  bool d_useCondPoolIGain;
122
  /* Functions-to-synthesize (a.k.a. candidates) with unification strategies */
123
  std::unordered_set<Node> d_unif_candidates;
124
  /** construct sol */
125
  Node constructSol(Node f,
126
                    Node e,
127
                    NodeRole nrole,
128
                    int ind,
129
                    std::vector<Node>& lemmas) override;
130
  /** collects data from refinement lemmas to drive solution construction
131
   *
132
   * In particular it rebuilds d_app_to_pt whenever d_prev_rlemmas is different
133
   * from d_rlemmas, in which case we may have added or removed data points
134
   */
135
  void initializeConstructSol() override;
136
  /** initialize construction solution for function-to-synthesize f */
137
  void initializeConstructSolFor(Node f) override;
138
  /** maps unif functions-to~synhesize to their last built solutions */
139
  std::map<Node, Node> d_cand_to_sol;
140
  /*
141
    --------------------------------------------------------------
142
        Purification
143
    --------------------------------------------------------------
144
  */
145
  /**
146
   * maps heads of applications of a unif function-to-synthesize to their tuple
147
   * of arguments (which constitute a "data point" aka an "evaluation point")
148
   */
149
  std::map<Node, std::vector<Node>> d_hd_to_pt;
150
  /** maps unif candidates to heads of their evaluation points */
151
  std::map<Node, std::vector<Node>> d_cand_to_eval_hds;
152
  /**
153
   * maps applications of unif functions-to-synthesize to the result of their
154
   * purification */
155
  std::map<Node, Node> d_app_to_purified;
156
  /** maps unif functions-to-synthesize to counters of heads of evaluation
157
   * points */
158
  std::map<Node, unsigned> d_cand_to_hd_count;
159
  /**
160
   * This is called on the refinement lemma and will rewrite applications of
161
   * functions-to-synthesize to their respective purified form, i.e. such that
162
   * all unification functions are applied over concrete values. Moreover
163
   * unification functions are also rewritten such that every different tuple of
164
   * arguments has a fresh function symbol applied to it.
165
   *
166
   * Non-unification functions are also equated to their model values when they
167
   * occur as arguments of unification functions.
168
   *
169
   * A vector of guards with the (negated) equalities between the original
170
   * arguments and their model values is populated accordingly.
171
   *
172
   * When the traversal encounters an application of a unification
173
   * function-to-synthesize it will proceed to ensure that the arguments of that
174
   * function application are constants (ensureConst becomes "true"). If an
175
   * applicatin of a non-unif function-to-synthesize is reached, the requirement
176
   * is lifted (ensureConst becomes "false"). This avoides introducing spurious
177
   * equalities in model_guards.
178
   *
179
   * For example if "f" is being synthesized with a unification strategy and "g"
180
   * is not then the application
181
   *   f(g(f(g(0))))=1
182
   * would be purified into
183
   *   g(0) = c1 ^ g(f1(c1)) = c3 => f2(c3)
184
   *
185
   * Similarly
186
   *   f(+(0,f(g(0))))
187
   * would be purified into
188
   *   g(0) = c1 ^ f1(c1) = c2 => f2(+(0,c2))
189
   *
190
   * This function also populates the maps between candidates, heads and
191
   * evaluation points
192
   */
193
  Node purifyLemma(Node n,
194
                   bool ensureConst,
195
                   std::vector<Node>& model_guards,
196
                   BoolNodePairMap& cache);
197
  /*
198
    --------------------------------------------------------------
199
        Strategy information
200
    --------------------------------------------------------------
201
  */
202
  /**
203
   * This class stores the necessary information for building a decision tree
204
   * for a particular node in the strategy tree of a candidate variable f.
205
   */
206
  class DecisionTreeInfo
207
  {
208
   public:
209
9
    DecisionTreeInfo()
210
9
        : d_unif(nullptr), d_strategy(nullptr), d_strategy_index(0)
211
    {
212
9
    }
213
9
    ~DecisionTreeInfo() {}
214
    /** initializes this class */
215
    void initialize(Node cond_enum,
216
                    SygusUnifRl* unif,
217
                    SygusUnifStrategy* strategy,
218
                    unsigned strategy_index);
219
    /** returns index of strategy information of strategy node for this DT */
220
    unsigned getStrategyIndex() const;
221
    /** builds solution, if possible, using the given constructor
222
     *
223
     * A solution is possible when all different valued heads can be separated,
224
     * i.e. the current set of conditions separates them in a decision tree
225
     */
226
    Node buildSol(Node cons, std::vector<Node>& lemmas);
227
    /** bulids a solution by considering all condition values ever enumerated */
228
    Node buildSolAllCond(Node cons, std::vector<Node>& lemmas);
229
    /** builds a solution by incrementally adding points and conditions to DT
230
     *
231
     * Differently from the above method, here a condition is only added to the
232
     * DT when it's necessary for resolving a separation conflict (i.e. heads
233
     * with different values in the same leaf of the DT). Only one value per
234
     * condition enumerated is considered.
235
     *
236
     * If a solution cannot be built, then there are more conflicts to be
237
     * resolved than condition enumerators. A conflict lemma is added to lemmas
238
     * that forces a new assigment in which the conflict is removed (heads are
239
     * made equal) or a new condition is enumerated to try to separate them.
240
     */
241
    Node buildSolMinCond(Node cons, std::vector<Node>& lemmas);
242
    /** reference to parent unif util */
243
    SygusUnifRl* d_unif;
244
    /** enumerator template (if no templates, nodes in pair are Node::null()) */
245
    NodePair d_template;
246
    /** enumerated condition values, this is set by setConditions(...). */
247
    std::vector<Node> d_conds;
248
    /** gathered evaluation point heads */
249
    std::vector<Node> d_hds;
250
    /** all enumerated model values for conditions */
251
    std::unordered_set<Node> d_cond_mvs;
252
    /** get condition enumerator */
253
9
    Node getConditionEnumerator() const { return d_cond_enum; }
254
    /** set conditions */
255
    void setConditions(Node guard,
256
                       const std::vector<Node>& enums,
257
                       const std::vector<Node>& conds);
258
259
   private:
260
    /** true and false nodes */
261
    Node d_true;
262
    Node d_false;
263
    /** Accumulates solutions built when considering all enumerated condition
264
     * values (which may generate repeated solutions) */
265
    std::unordered_set<Node> d_sols;
266
    /**
267
     * Conditional enumerator variables corresponding to the condition values in
268
     * d_conds. These are used for generating separation lemmas during
269
     * buildSol. This is set by setConditions(...).
270
     */
271
    std::vector<Node> d_enums;
272
    /**
273
     * The guard literal whose semantics is "we need at most d_enums.size()
274
     * conditions in our solution. This is set by setConditions(...).
275
     */
276
    Node d_guard;
277
    /**
278
     * reference to inferred strategy for the function-to-synthesize this DT is
279
     * associated with
280
     */
281
    SygusUnifStrategy* d_strategy;
282
    /** index of strategy information of strategy node this DT is based on
283
     *
284
     * this is the index of the strategy (d_strats[index]) in the strategy node
285
     * to which this decision tree corresponds, which can be accessed through
286
     * the above strategy reference
287
     */
288
    unsigned d_strategy_index;
289
    /**
290
     * The enumerator in the strategy tree that stores conditions of the
291
     * decision tree.
292
     */
293
    Node d_cond_enum;
294
    /** extracts solution from decision tree built
295
     *
296
     * Depending on the active options, the decision tree might be rebuilt
297
     * before a solution is extracted, for example to optimize size (smaller
298
     * DTs) or chance of having a general solution (information gain heuristics)
299
     */
300
    Node extractSol(Node cons, std::map<Node, Node>& hd_mv);
301
302
    /** rebuild decision tree using information gain heuristic
303
     *
304
     * In a scenario in which the decision tree potentially contains more
305
     * conditions than necessary, it is beneficial to rebuild it in a way that
306
     * "better" conditions occurr closer to the top.
307
     *
308
     * The information gain heuristic selects conditions that lead to a
309
     * greater reduction of the Shannon entropy in the set of points
310
     */
311
    void recomputeSolHeuristically(std::map<Node, Node>& hd_mv);
312
    /** recursively select (best) conditions to split heads
313
     *
314
     * At each call picks the best condition based on the information gain
315
     * heuristic and splits the set of heads accordingly, then recurses on
316
     * them.
317
     *
318
     * The base case is a set being fully classified (i.e. all heads have the
319
     * same value)
320
     *
321
     * hds is the set of evaluation point heads we must classify with the
322
     * values in conds. The classification is guided by how a condition value
323
     * splits the heads through its evaluation on the points associated with
324
     * the heads. The metric is based on the model values of the heads (hd_mv)
325
     * in the resulting splits.
326
     *
327
     * ind is the current level of indentation (for debugging)
328
     */
329
    void buildDtInfoGain(std::vector<Node>& hds,
330
                         std::vector<Node> conds,
331
                         std::map<Node, Node>& hd_mv,
332
                         int ind);
333
    /** computes the Shannon entropy of a set of heads
334
     *
335
     * The entropy depends on how many positive and negative heads are in the
336
     * set and in their distribution. The polarity of the evaluation heads is
337
     * queried from their model values in hd_mv.
338
     *
339
     * ind is the current level of indentation (for debugging)
340
     */
341
    double getEntropy(const std::vector<Node>& hds,
342
                      std::map<Node, Node>& hd_mv,
343
                      int ind);
344
    /** evaluates a condition on a set of points
345
     *
346
     * The result is two sets of points: those on which the condition holds
347
     * and those on which it does not
348
     */
349
    std::pair<std::vector<Node>, std::vector<Node>> evaluateCond(
350
        std::vector<Node>& pts, Node cond);
351
    /** Classifies evaluation points according to enumerated condition values
352
     *
353
     * Maintains the invariant that points evaluated in the same way in the
354
     * current condition values are kept in the same "separation class."
355
     */
356
875377
    class PointSeparator : public LazyTrieEvaluator
357
    {
358
     public:
359
9
      PointSeparator() : d_dt(nullptr) {}
360
      /** initializes this class */
361
      void initialize(DecisionTreeInfo* dt);
362
      /**
363
       * evaluates the respective evaluation point of the head n on the index-th
364
       * condition
365
       */
366
      Node evaluate(Node n, unsigned index) override;
367
368
      /** the lazy trie for building the separation classes */
369
      LazyTrieMulti d_trie;
370
      /** extracts solution from decision tree built */
371
      Node extractSol(Node cons, std::map<Node, Node>& hd_mv);
372
      /** computes the result of applying cond on the respective point of hd
373
       *
374
       * If for example cond is (\lambda xy. x < y) and hd is an evaluation head
375
       * in point (hd 0 1) this function will result in true, since
376
       *   (\lambda xy. x < y) 0 1 evaluates to true
377
       */
378
      Node computeCond(Node cond, Node hd);
379
380
     private:
381
      /** reference to parent unif util */
382
      DecisionTreeInfo* d_dt;
383
      /** cache of conditions evaluations on heads
384
       *
385
       * If for example cond is (\lambda xy. x < y) and hd is an evaluation head
386
       * in point (hd 0 1), then after invoking computeCond(cond, hd) this map
387
       * will contain d_eval_cond_hd[<cond, hd>] = true, since
388
       *
389
       *   (\lambda xy. x < y) 0 1 evaluates to true
390
       */
391
      std::map<std::pair<Node, Node>, Node> d_eval_cond_hd;
392
    };
393
    /**
394
     * Utility for determining how evaluation points are separated by currently
395
     * enumerated condiotion values
396
     */
397
    PointSeparator d_pt_sep;
398
  };
399
  /** maps strategy points in the strategy tree to the above data */
400
  std::map<Node, DecisionTreeInfo> d_stratpt_to_dt;
401
  /** maps conditional enumerators to strategy points in which they occur */
402
  std::map<Node, std::vector<Node>> d_cenum_to_stratpt;
403
  /** maps unif candidates to their conditional enumerators */
404
  std::map<Node, std::vector<Node>> d_cand_cenums;
405
  /** all conditional enumerators */
406
  std::vector<Node> d_cond_enums;
407
  /** register strategy
408
   *
409
   * Initialize the above data for the relevant enumerators in the strategy tree
410
   * of candidate variable f. For each strategy point e which there is a
411
   * decision tree strategy, we add e to enums. For each strategy with index
412
   * i in an strategy point e, if we are not using the strategy, we add i to
413
   * unused_strats[e]. This map is later passed to
414
   * SygusUnifStrategy::staticLearnRedundantOps.
415
   */
416
  void registerStrategy(
417
      Node f,
418
      std::vector<Node>& enums,
419
      std::map<Node, std::unordered_set<unsigned>>& unused_strats);
420
  /** register strategy node
421
   *
422
   * Called while traversing the strategy tree of f. The arguments e and nrole
423
   * indicate the current node in the tree we are traversing, and visited
424
   * indicates the nodes we have already visited. The arguments enums and
425
   * unused_strats are modified as described above.
426
   */
427
  void registerStrategyNode(
428
      Node f,
429
      Node e,
430
      NodeRole nrole,
431
      std::map<Node, std::map<NodeRole, bool>>& visited,
432
      std::vector<Node>& enums,
433
      std::map<Node, std::unordered_set<unsigned>>& unused_strats);
434
  /** register conditional enumerator
435
   *
436
   * Registers that cond is a conditional enumerator for building a (recursive)
437
   * decision tree at strategy node e within the strategy tree of f.
438
   */
439
  void registerConditionalEnumerator(Node f,
440
                                     Node e,
441
                                     Node cond,
442
                                     unsigned strategy_index);
443
};
444
445
}  // namespace quantifiers
446
}  // namespace theory
447
}  // namespace cvc5
448
449
#endif /* CVC5__THEORY__QUANTIFIERS__SYGUS_UNIF_RL_H */