GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_unif_rl.h Lines: 7 7 100.0 %
Date: 2021-09-29 Branches: 6 12 50.0 %

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