OR-Tools  9.6
symmetry_util.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <cstdint>
18 #include <memory>
19 #include <vector>
20 
23 #include "ortools/base/logging.h"
24 
25 namespace operations_research {
26 namespace sat {
27 
28 std::vector<std::vector<int>> BasicOrbitopeExtraction(
29  const std::vector<std::unique_ptr<SparsePermutation>>& generators) {
30  // Count the number of permutations that are compositions of 2-cycle and
31  // regroup them according to the number of cycles.
32  std::vector<std::vector<int>> num_cycles_to_2cyclers;
33  for (int g = 0; g < generators.size(); ++g) {
34  const std::unique_ptr<SparsePermutation>& perm = generators[g];
35  bool contain_only_2cycles = true;
36  const int num_cycles = perm->NumCycles();
37  for (int i = 0; i < num_cycles; ++i) {
38  if (perm->Cycle(i).size() != 2) {
39  contain_only_2cycles = false;
40  break;
41  }
42  }
43  if (!contain_only_2cycles) continue;
44  if (num_cycles >= num_cycles_to_2cyclers.size()) {
45  num_cycles_to_2cyclers.resize(num_cycles + 1);
46  }
47  num_cycles_to_2cyclers[num_cycles].push_back(g);
48  }
49 
50  // Heuristic: we try to grow the orbitope that has the most potential for
51  // fixing variables.
52  //
53  // TODO(user): We could grow each and keep the real maximum.
54  int best = -1;
55  int best_score = 0;
56  for (int i = 0; i < num_cycles_to_2cyclers.size(); ++i) {
57  if (num_cycles_to_2cyclers[i].size() > 1) {
58  const int num_perms = num_cycles_to_2cyclers[i].size() + 1;
59  VLOG(1) << "Potential orbitope: " << i << " x " << num_perms;
60  const int64_t score = std::min(i, num_perms);
61  if (score > best_score) {
62  best = i;
63  best_score = score;
64  }
65  }
66  }
67 
68  std::vector<std::vector<int>> orbitope;
69  if (best == -1) return orbitope;
70 
71  // We will track the element already added so we never have duplicates.
72  std::vector<bool> in_matrix;
73 
74  // Greedily grow the orbitope.
75  orbitope.resize(best);
76  for (const int g : num_cycles_to_2cyclers[best]) {
77  // Start using the first permutation.
78  if (orbitope[0].empty()) {
79  const std::unique_ptr<SparsePermutation>& perm = generators[g];
80  const int num_cycles = perm->NumCycles();
81  for (int i = 0; i < num_cycles; ++i) {
82  for (const int x : perm->Cycle(i)) {
83  orbitope[i].push_back(x);
84  if (x >= in_matrix.size()) in_matrix.resize(x + 1, false);
85  in_matrix[x] = true;
86  }
87  }
88  continue;
89  }
90 
91  // We want to find a column such that g sends it to variables not already
92  // in the orbitope matrix.
93  //
94  // Note(user): This relies on the cycle in each permutation to be ordered by
95  // smaller element first. This way we don't have to account any row
96  // permutation of the orbitope matrix. The code that detect the symmetries
97  // of the problem should already return permutation in this canonical
98  // format.
99  std::vector<int> grow;
100  int matching_column_index = -1;
101  const std::unique_ptr<SparsePermutation>& perm = generators[g];
102  const int num_cycles = perm->NumCycles();
103  for (int i = 0; i < num_cycles; ++i) {
104  // Extract the two elements of this transposition.
105  std::vector<int> tmp;
106  for (const int x : perm->Cycle(i)) tmp.push_back(x);
107  const int a = tmp[0];
108  const int b = tmp[1];
109 
110  // We want one element to appear in matching_column_index and the other to
111  // not appear at all.
112  int num_matches_a = 0;
113  int num_matches_b = 0;
114  int last_match_index = -1;
115  for (int j = 0; j < orbitope[i].size(); ++j) {
116  if (orbitope[i][j] == a) {
117  ++num_matches_a;
118  last_match_index = j;
119  } else if (orbitope[i][j] == b) {
120  ++num_matches_b;
121  last_match_index = j;
122  }
123  }
124  if (last_match_index == -1) break;
125  if (matching_column_index == -1) {
126  matching_column_index = last_match_index;
127  }
128  if (matching_column_index != last_match_index) break;
129  if (num_matches_a == 0 && num_matches_b == 1) {
130  if (a >= in_matrix.size() || !in_matrix[a]) grow.push_back(a);
131  } else if (num_matches_a == 1 && num_matches_b == 0) {
132  if (b >= in_matrix.size() || !in_matrix[b]) grow.push_back(b);
133  } else {
134  break;
135  }
136  }
137 
138  // If grow is of full size, we can extend the orbitope.
139  if (grow.size() == num_cycles) {
140  for (int i = 0; i < orbitope.size(); ++i) {
141  orbitope[i].push_back(grow[i]);
142  if (grow[i] >= in_matrix.size()) in_matrix.resize(grow[i] + 1, false);
143  in_matrix[grow[i]] = true;
144  }
145  }
146  }
147 
148  return orbitope;
149 }
150 
151 std::vector<int> GetOrbits(
152  int n, const std::vector<std::unique_ptr<SparsePermutation>>& generators) {
153  MergingPartition union_find;
154  union_find.Reset(n);
155  for (const std::unique_ptr<SparsePermutation>& perm : generators) {
156  const int num_cycles = perm->NumCycles();
157  for (int i = 0; i < num_cycles; ++i) {
158  // Note that there is currently no random access api like cycle[j].
159  int first;
160  bool is_first = true;
161  for (const int x : perm->Cycle(i)) {
162  if (is_first) {
163  first = x;
164  is_first = false;
165  } else {
166  union_find.MergePartsOf(first, x);
167  }
168  }
169  }
170  }
171 
172  int num_parts = 0;
173  std::vector<int> orbits(n, -1);
174  for (int i = 0; i < n; ++i) {
175  if (union_find.NumNodesInSamePartAs(i) == 1) continue;
176  const int root = union_find.GetRootAndCompressPath(i);
177  if (orbits[root] == -1) orbits[root] = num_parts++;
178  orbits[i] = orbits[root];
179  }
180  return orbits;
181 }
182 
183 std::vector<int> GetOrbitopeOrbits(
184  int n, const std::vector<std::vector<int>>& orbitope) {
185  std::vector<int> orbits(n, -1);
186  for (int i = 0; i < orbitope.size(); ++i) {
187  for (int j = 0; j < orbitope[i].size(); ++j) {
188  CHECK_EQ(orbits[orbitope[i][j]], -1);
189  orbits[orbitope[i][j]] = i;
190  }
191  }
192  return orbits;
193 }
194 
195 } // namespace sat
196 } // namespace operations_research
int64_t min
Definition: alldiff_cst.cc:139
int MergePartsOf(int node1, int node2)
int64_t b
int64_t a
std::vector< int > GetOrbitopeOrbits(int n, const std::vector< std::vector< int >> &orbitope)
std::vector< std::vector< int > > BasicOrbitopeExtraction(const std::vector< std::unique_ptr< SparsePermutation >> &generators)
std::vector< int > GetOrbits(int n, const std::vector< std::unique_ptr< SparsePermutation >> &generators)
Collection of objects used to extend the Constraint Solver library.
#define VLOG(verboselevel)
Definition: vlog.h:39