OR-Tools  9.3
symmetry_util.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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
24
25namespace operations_research {
26namespace sat {
27
28std::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
151std::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
183std::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
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define VLOG(verboselevel)
Definition: base/logging.h:984
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< int > GetOrbits(int n, const std::vector< std::unique_ptr< SparsePermutation > > &generators)
std::vector< std::vector< int > > BasicOrbitopeExtraction(const std::vector< std::unique_ptr< SparsePermutation > > &generators)
Collection of objects used to extend the Constraint Solver library.
const bool is_first