summaryrefslogtreecommitdiff
path: root/src/preprocessing/passes/symmetry_detect.h
blob: 1bc354419df412fc1fb17ea84cf59ac88248c3c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*********************                                                        */
/*! \file symmetry_detect.h
 ** \verbatim
 ** Top contributors (to current version):
 **   Andrew Reynolds, Paul Meng
 ** This file is part of the CVC4 project.
 ** Copyright (c) 2009-2019 by the authors listed in the file AUTHORS
 ** in the top-level source directory) and their institutional affiliations.
 ** All rights reserved.  See the file COPYING in the top-level source
 ** directory for licensing information.\endverbatim
 **
 ** \brief Symmetry detection for theories
 **/

#include "cvc4_private.h"

#ifndef __CVC4__PREPROCESSING__PASSES__SYMMETRY_DETECT_H
#define __CVC4__PREPROCESSING__PASSES__SYMMETRY_DETECT_H

#include <map>
#include <string>
#include <vector>
#include "expr/node.h"
#include "theory/quantifiers/term_canonize.h"

namespace CVC4 {
namespace preprocessing {
namespace passes {
namespace symbreak {

/**
 * This class stores a "partition", which is a way of representing a
 * class of symmetries.
 *
 * For example, when finding symmetries for a term like x+y = 0, we
 * construct a partition { w -> { x, y } } that indicates that automorphisms
 * over { x, y } do not affect the satisfiability of this term. In this
 * example, we have the following assignments to the members of this class:
 *   d_term : x+y=0
 *   d_sterm : w+w=0
 *   d_var_to_subvar : { x -> w, y -> w }
 *   d_subvar_to_vars : { w -> { x, y } }
 * We often identify a partition with its d_subvar_to_vars field.
 *
 * We call w a "symmetry breaking variable".
 */
class Partition
{
 public:
  /** The term for which the partition was computed for. */
  Node d_term;
  /** Substituted term corresponding to the partition
   *
   * This is equal to d_term * d_var_to_subvar, where * is application of
   * substitution.
   */
  Node d_sterm;
  /**
   * Mapping between the variable and the symmetry breaking variable e.g.
   * { x -> w, y -> w }.
   */
  std::map<Node, Node> d_var_to_subvar;

  /**
   * Mapping between the symmetry breaking variables and variables, e.g.
   * { w-> { x, y } }
   */
  std::map<Node, std::vector<Node> > d_subvar_to_vars;
  /** Add variable v to d_subvar_to_vars[sv]. */
  void addVariable(Node sv, Node v);
  /** Remove variable sv from the domain of d_subvar_to_vars. */
  void removeVariable(Node sv);
  /** Sorts the ranges of d_subvar_to_vars. */
  void normalize();
  /** Print a partition */
  static void printPartition(const char* c, Partition p);
  /** get variables */
  void getVariables(std::vector<Node>& vars);
  /** get substitution */
  void getSubstitution(std::vector<Node>& vars, std::vector<Node>& subs);
};

/** partition merger
 *
 * This class is used to identify sets of children of commutative operators k
 * that are identical up to a set of automorphisms.
 *
 * This class is used when we have detected symmetries for the children
 * of a term t of the form <k>( t_1, ..., t_n ), where k is a commutative
 * operator. For each i=1,...n, partitions[i] represents symmetries (in the
 * form of a partition) computed for the child t_i.
 *
 * The vector d_indices of this class stores a list ( i_1...i_m ) such that
 * ( t_i_j * partition[i_j].d_var_to_subvar ) is syntactically equivalent
 * for each j=1...m, where * is application of substitution.
 *
 * In detail, we say that
 *   partition[j1] = { w -> X_1 },
 *   ...,
 *   partition[jp] = { w -> X_p }
 * are mergeable if s=|X_1|=...=|X_p|, and all subsets of
 * X* = ( union_{k=1...p} X_k ) of size s are equal to exactly one of
 * X_1 ... X_p.
 */
class PartitionMerger
{
 public:
  PartitionMerger()
      : d_kind(kind::UNDEFINED_KIND),
        d_master_base_index(0),
        d_num_new_indices_needed(0)
  {
  }
  /** initialize this class
   *
   * We will be trying to merge the given partitions that occur at the given
   * indices. k is the kind of the operator that partitions are children of.
   */
  void initialize(Kind k,
                  const std::vector<Partition>& partitions,
                  const std::vector<unsigned>& indices);
  /** merge
   *
   * This method tries to "merge" partitions occurring at the indices d_indices
   * of the vector partitions.
   *
   * Assuming the setting described above, if there exists a mergeable set of
   * partitions with indices (j_m1...j_mp), we remove {j_m1...j_mp} \ { j_m1 }
   * from active_indices, and update partition[j1] := { w -> X* }.
   *
   * The base_index is an index from d_indices that serves as a basis for
   * detecting this symmetry. In particular, we start by assuming that
   * p=1, and j_m1 is base_index. We proceed by trying to find sets of indices
   * that add exactly one variable to X* at a time. We return
   * true if p>1, that is, at least one partition was merged with the
   * base_index.
   */
  bool merge(std::vector<Partition>& partitions,
             unsigned base_index,
             std::unordered_set<unsigned>& active_indices,
             std::vector<unsigned>& merged_indices);

 private:
  /** the kind of the node we are consdiering */
  Kind d_kind;
  /** indices we are considering */
  std::vector<unsigned> d_indices;
  /** count the number of times each variable occurs */
  std::map<Node, unsigned> d_occurs_count;
  /** the number of times each variable occurs up to the given index */
  std::map<unsigned, std::map<Node, unsigned> > d_occurs_by;
  /** current master base index */
  unsigned d_master_base_index;
  /** the indices that occur in the current symmetry (the list (j1...jp)) */
  std::unordered_set<unsigned> d_base_indices;
  /** the free variables that occur in the current symmetry (the set X*)*/
  std::unordered_set<Node, NodeHashFunction> d_base_vars;
  /** the number of indices we need to add to extended X* by one variable */
  unsigned d_num_new_indices_needed;
  /** the variables we have currently tried to add to X* */
  std::unordered_set<Node, NodeHashFunction> d_merge_var_tried;
  /** merge new variable
   *
   * This is a recursive helper for the merge function. This function attempts
   * to construct a set of indices {j1...jp} of partitions and a variable
   * "merge_var" such that partitions[ji] is of the form { w -> X_ji }, where
   * merge_var in X_ji and ( X_ji \ { merge_var } ) is a subset of the base
   * variables X*. We require that p = d_num_new_indices_needed, where
   * d_num_new_indices_needed is
   *   |d_base_vars| choose (|X_ji|-1)
   * that is, n!/((n-k)!*k!) where n=|d_base_vars| and k=|X_ji|-1.
   *
   * curr_index : the index of d_indices we are currently considering whether
   * to add to new_indices,
   * new_indices : the currently considered indices {j1...jp},
   * merge_var : the variable we are currently trying to add to X*,
   * new_merge_var_max : the maximum number of times that merge_var might
   * appear in remainding indices, i.e. d_indices[curr_index]...d_indices.end(),
   * which is used as an optimization for recognizing quickly when this method
   * will fail.
   */
  bool mergeNewVar(unsigned curr_index,
                   std::vector<unsigned>& new_indices,
                   Node& merge_var,
                   unsigned num_merge_var_max,
                   std::vector<Partition>& partitions,
                   std::unordered_set<unsigned>& active_indices);
};

/**
 * This is the class to detect symmetries between variables or terms relative
 * to a set of input assertions.
 */
class SymmetryDetect
{
 public:
  /** constructor */
  SymmetryDetect();

  /**
   * Destructor
   * */
  ~SymmetryDetect() {}

  /** Get the final partition after symmetry detection.
   *
   *  If a vector in sterms contains two variables x and y,
   *  then assertions and assertions { x -> y, y -> x } are
   *  equisatisfiable.
   * */
  void compute(std::vector<std::vector<Node> >& part,
               const std::vector<Node>& assertions);

  /** Get the final partition after symmetry detection.
   *
   *  If a vector in sterms contains two terms t and s,
   *  then assertions and assertions { t -> s, s -> t } are
   *  equisatisfiable.
   * */
  void computeTerms(std::vector<std::vector<Node> >& sterms,
                    const std::vector<Node>& assertions);

 private:
  /** (canonical) symmetry breaking variables for each type */
  std::map<TypeNode, std::vector<Node> > d_sb_vars;
  /**
   * Get the index^th symmetry breaking variable for type tn in the above
   * vector. These are fresh variables of type tn which are used for
   * constructing a canonical form for terms considered by this class, and
   * are used in the domains of partitions (Partition::d_subvar_to_vars).
   * This variable is created by this method if it does not already exist.
   */
  Node getSymBreakVariable(TypeNode tn, unsigned index);
  /**
   * Get the index[tn]^th symmetry breaking variable for type tn using the
   * above function and increment index[tn].
   */
  Node getSymBreakVariableInc(TypeNode tn, std::map<TypeNode, unsigned>& index);

  /** True and false constant nodes */
  Node d_trueNode;
  Node d_falseNode;

  /** term canonizer (for quantified formulas) */
  theory::quantifiers::TermCanonize d_tcanon;

  /** Cache for partitions */
  std::map<Node, Partition> d_term_partition;

  /** detect
   *
   * Called on the input assertions, where assertions are interpreted as a
   * conjunction. This computes a partition P of the variables in assertions
   * such that for each set S in P, then all automorphisms for S applied to
   * assertions result in an equisatisfiable formula.
   */
  Partition detect(const std::vector<Node>& assertions);

  /** Find symmetries in node */
  Partition findPartitions(Node node);

  /** Collect children of a node
   *  If the kind of node is associative, we will chain its children together.
   *  We might need optimizations here, such as rewriting the input to negation
   *  normal form.
   * */
  void collectChildren(Node node, std::vector<Node>& children);

  /** Compute alpha equivalent terms
   *
   * This is used for determining pairs of terms in sterms that are
   * alpha-equivalent. In detail, this constructs sterm_to_indices such that if
   * sterm_to_indices[t] contains an index i, then there exists a k such that
   * indices[k] = i and sterms[k] is alpha-equivalent to t, and sterm_to_indices
   * contains indices[k] for each k=1,...,indicies.size()-1. For example,
   * computeAlphaEqTerms( { 0, 3, 7 }, { Q(a), forall x. P(x), forall y. P(y) }
   * may construct sterm_to_indices such that
   *   sterm_to_indices[Q(a)] -> { 0 }
   *   sterm_to_indices[forall x. P(x)] -> { 3, 7 }
   */
  void computeAlphaEqTerms(
      const std::vector<unsigned>& indices,
      const std::vector<Node>& sterms,
      std::map<Node, std::vector<unsigned> >& sterm_to_indices);
  /** process partitions
   *
   * This method is called when we have detected symmetries for the children
   * of a term t of the form <k>( t_1, ..., t_n ), where k is a commutative
   * operator.  The vector indices stores a list ( i_1...i_m ) such that
   * ( t_i_j * partition[i_j].d_var_to_subvar ) is syntactically equivalent
   * for each j=1...m, where * is application of substitution. In particular,
   * ( t_i_j * partition[i_j].d_var_to_subvar ) is equal to
   * partitions[i_j].d_sterm for each j=1...m.
   *
   * This function calls mergePartitions on subsets of these indices for which
   * it is possible to "merge" (see PartitionMerger). In particular, we consider
   * subsets of indices whose corresponding partitions are of the form
   *   { w -> { x1...xn } }
   * for each n. This means that partitions like { w -> { x1 } } and
   * { w -> { x1 x2 } } are considered separately when merging.
   */
  void processPartitions(Kind k,
                         std::vector<Partition>& partitions,
                         const std::vector<unsigned>& indices,
                         std::unordered_set<unsigned>& active_indices,
                         std::vector<Node>& fixedSVar,
                         std::vector<Node>& fixedVar);
  /** merge partitions
   *
   * This method merges groups of partitions occurring in indices using the
   * PartitionMerger class. Each merge updates one partition in partitions (the
   * master index of the merge) and removes a set of indices from active_indices
   * (the slave indices).
   */
  void mergePartitions(Kind k,
                       std::vector<Partition>& partitions,
                       const std::vector<unsigned>& indices,
                       std::unordered_set<unsigned>& active_indices);
  //-------------------for symmetry breaking terms
  /** symmetry breaking id counter */
  unsigned d_tsym_id_counter;
  /** list of term symmetries */
  std::map<unsigned, std::vector<Node> > d_tsyms;
  /** list of term symmetries */
  std::map<unsigned, std::vector<Node> > d_tsym_to_vars;
  /** variables to ids */
  std::map<Node, std::vector<unsigned> > d_var_to_tsym_ids;
  /** store term symmetry */
  void storeTermSymmetry(const std::vector<Node>& symTerms,
                         const std::vector<Node>& vars);
  //-------------------end for symmetry breaking terms
};

}  // namespace symbreak
}  // namespace passes
}  // namespace preprocessing
}  // namespace CVC4

#endif
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback