summaryrefslogtreecommitdiff
path: root/src/preprocessing/passes/symmetry_detect.h
diff options
context:
space:
mode:
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>2018-07-23 17:39:12 -0500
committerGitHub <noreply@github.com>2018-07-23 17:39:12 -0500
commit3471e731b8538a906e37f8aeb4b0301598b34eff (patch)
treee67f70809e791c18aa9ca4800de87a7c707d062b /src/preprocessing/passes/symmetry_detect.h
parent03925b816a0f9aeb079e2c0037a426b5946e2eae (diff)
Generalize symmetry detection for 1 symmetry variable mapped to n input variables (#1888)
Diffstat (limited to 'src/preprocessing/passes/symmetry_detect.h')
-rw-r--r--src/preprocessing/passes/symmetry_detect.h297
1 files changed, 224 insertions, 73 deletions
diff --git a/src/preprocessing/passes/symmetry_detect.h b/src/preprocessing/passes/symmetry_detect.h
index 758169611..9a5f83868 100644
--- a/src/preprocessing/passes/symmetry_detect.h
+++ b/src/preprocessing/passes/symmetry_detect.h
@@ -25,6 +25,185 @@
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;
+ /** sorts the ranges of d_subvar_to_vars. */
+ void normalize();
+ /** Print a partition */
+ static void printPartition(const char* c, Partition p);
+};
+
+/** 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);
+
+ 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);
+};
+/**
+ * We build the partition trie indexed by
+ * parts[0].var_to_subvar[v]....parts[n].var_to_subvar[v]. The leaves of a
+ * partition trie is the new regions of a partition
+ */
+class PartitionTrie
+{
+ public:
+ /** Variables at the leave */
+ std::vector<Node> d_variables;
+
+ /** The mapping from a node to its children */
+ std::map<Node, PartitionTrie> d_children;
+
+ /** Add variable v to the trie, indexed by
+ * parts[0].var_to_subvar[v]....parts[n].var_to_subvar[v]. */
+ Node addNode(Node v, std::vector<Partition>& parts);
+
+ /** Get all the new regions of a partition and store in part
+ *
+ * This constructs a new partition, part, where each set in this partition
+ * corresponds to one leaf in the PartitionTrie pt.
+ * var_to_svar: map from variables to symmetry variables to use in part.
+ */
+ void getNewPartition(Partition& part,
+ PartitionTrie& pt,
+ std::map<Node, Node>& var_to_svar);
+};
/**
* This is the class to detect symmetries from input based on terms equality.
@@ -54,49 +233,21 @@ class SymmetryDetect
void getPartition(std::vector<std::vector<Node> >& parts, const std::vector<Node>& assertions);
private:
+ /** (canonical) symmetry breaking variables for each type */
+ std::map<TypeNode, std::vector<Node> > d_sb_vars;
/**
- * This is the class to store the partition,
- * where d_term store the term corresponding to the partition,
- * d_var_to_subvar is the mapping from the variable to the substitution
- * variable, and d_subvar_to_vars is the mapping from the substitution
- * variable to a list of variables forming a region of a partition.
+ * 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.
*/
- class Partition
- {
- public:
- /** Term corresponding to the partition, e.g., x + y = 0 */
- Node d_term;
- /** Mapping between the variable and the substitution variable x -> w, y -> w,
- * z -> w */
- std::map<Node, Node> d_var_to_subvar;
-
- /** Mapping between the substitution variable and variables w-> { x, y, z } */
- std::map<Node, std::vector<Node> > d_subvar_to_vars;
- };
-
+ Node getSymBreakVariable(TypeNode tn, unsigned index);
/**
- * We build the partition trie indexed by
- * parts[0].var_to_subvar[v]....parts[n].var_to_subvar[v]. The leaves of a
- * partition trie is the new regions of a partition
+ * Get the index[tn]^th symmetry breaking variable for type tn using the
+ * above function and increment index[tn].
*/
- class PartitionTrie
- {
- public:
- /** Variables at the leave */
- std::vector<Node> d_variables;
-
- /** The mapping from a node to its children */
- std::map<Node, PartitionTrie> d_children;
-
- /** Add variable v to the trie, indexed by
- * parts[0].var_to_subvar[v]....parts[n].var_to_subvar[v]. */
- void addNode(Node v, std::vector<Partition>& parts);
- void addNode(Node v, std::vector<Node>& subs);
-
- /** Get all the new regions of a partition and store in part */
- void getNewPartition(Partition& part, PartitionTrie& pt);
- };
-
+ Node getSymBreakVariableInc(TypeNode tn, std::map<TypeNode, unsigned>& index);
/** True and false constant nodes */
Node d_trueNode;
@@ -115,7 +266,7 @@ class SymmetryDetect
Partition detect(const std::vector<Node>& assertions);
/** Find symmetries in node */
- SymmetryDetect::Partition findPartitions(Node node);
+ Partition findPartitions(Node node);
/** Collect children of a node
* If the kind of node is associative, we will chain its children together.
@@ -124,40 +275,40 @@ class SymmetryDetect
* */
void collectChildren(Node node, std::vector<Node>& children);
- /** Print a partition */
- void printPartition(Partition p);
-
- /** Retrieve all variables from partitions and put in vars */
- void getVariables(std::vector<Partition>& partitions,
- std::unordered_set<Node, NodeHashFunction>& vars);
-
- /** Process singleton partitions and add all variables to vars
- * It collects all partitions with more than 1 variable and save it in
- * partitions first. And then it collects the substitution variable to
- * variable and to term mappings respectively from partitions with 1
- * variable and invokes matches function on the mappings to check
- * if any subset of the variables can be merged. If yes, they will be merged
- * and put in partitions. The remaining ones after the merge check will be
- * put in the partitions as well.
- * */
- void processSingletonPartitions(std::vector<Partition>& partitions,
- std::unordered_set<Node, NodeHashFunction>& vars);
-
- /** Do matches on singleton partitions
- * This function checks if any subset of the expressions corresponding to
- * substitution variables are equivalent under variables substitution.
- * If the expressions are equivalent, we will merge the variables corresponding
- * to the same substitution variables and put them in partitions.
- * For example, suppose we have subvar_to_var: {w1 -> u, w2 -> x, w3 -> y,
- * w4 -> z} and subvar_to_expr: {w1 -> u>2, w2 -> x>0, w3 -> y>0, w4 -> z>1}.
- * Since [x/w]>0 is equivalent [y/w]>0 but not equivalent to [z/w]>1 and [u/w]>2,
- * and [u/w]>2 is not equivalent to [z/w]>1, we would merge x and y and put
- * w5->{x, y} and also w1->{u}, w4->{z} in partitions.
- * */
- void matches(std::vector<Partition>& partitions,
- std::map<Node, Node>& subvar_to_var,
- std::map<Node, Node>& subvar_to_expr);
+ /** 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);
+ /** 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);
};
+} // namespace symbreak
} // namespace passes
} // namespace preprocessing
} // namespace CVC4
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback