Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
symmetry_util.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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
21#include "absl/log/check.h"
22#include "absl/types/span.h"
26
27namespace operations_research {
28namespace sat {
29
30std::vector<std::vector<int>> BasicOrbitopeExtraction(
31 absl::Span<const std::unique_ptr<SparsePermutation>> generators) {
32 // Count the number of permutations that are compositions of 2-cycle and
33 // regroup them according to the number of cycles.
34 std::vector<std::vector<int>> num_cycles_to_2cyclers;
35 for (int g = 0; g < generators.size(); ++g) {
36 const std::unique_ptr<SparsePermutation>& perm = generators[g];
37 bool contain_only_2cycles = true;
38 const int num_cycles = perm->NumCycles();
39 for (int i = 0; i < num_cycles; ++i) {
40 if (perm->Cycle(i).size() != 2) {
41 contain_only_2cycles = false;
42 break;
43 }
44 }
45 if (!contain_only_2cycles) continue;
46 if (num_cycles >= num_cycles_to_2cyclers.size()) {
47 num_cycles_to_2cyclers.resize(num_cycles + 1);
48 }
49 num_cycles_to_2cyclers[num_cycles].push_back(g);
50 }
51
52 // Heuristic: we try to grow the orbitope that has the most potential for
53 // fixing variables.
54 //
55 // TODO(user): We could grow each and keep the real maximum.
56 int best = -1;
57 int best_score = 0;
58 for (int i = 0; i < num_cycles_to_2cyclers.size(); ++i) {
59 if (num_cycles_to_2cyclers[i].size() > 1) {
60 const int num_perms = num_cycles_to_2cyclers[i].size() + 1;
61 VLOG(1) << "Potential orbitope: " << i << " x " << num_perms;
62 const int64_t score = std::min(i, num_perms);
63 if (score > best_score) {
64 best = i;
65 best_score = score;
66 }
67 }
68 }
69
70 std::vector<std::vector<int>> orbitope;
71 if (best == -1) return orbitope;
72
73 // We will track the element already added so we never have duplicates.
74 std::vector<bool> in_matrix;
75
76 // Greedily grow the orbitope.
77 orbitope.resize(best);
78 for (const int g : num_cycles_to_2cyclers[best]) {
79 // Start using the first permutation.
80 if (orbitope[0].empty()) {
81 const std::unique_ptr<SparsePermutation>& perm = generators[g];
82 const int num_cycles = perm->NumCycles();
83 for (int i = 0; i < num_cycles; ++i) {
84 for (const int x : perm->Cycle(i)) {
85 orbitope[i].push_back(x);
86 if (x >= in_matrix.size()) in_matrix.resize(x + 1, false);
87 in_matrix[x] = true;
88 }
89 }
90 continue;
91 }
92
93 // We want to find a column such that g sends it to variables not already
94 // in the orbitope matrix.
95 //
96 // Note(user): This relies on the cycle in each permutation to be ordered by
97 // smaller element first. This way we don't have to account any row
98 // permutation of the orbitope matrix. The code that detect the symmetries
99 // of the problem should already return permutation in this canonical
100 // format.
101 std::vector<int> grow;
102 int matching_column_index = -1;
103 const std::unique_ptr<SparsePermutation>& perm = generators[g];
104 const int num_cycles = perm->NumCycles();
105 for (int i = 0; i < num_cycles; ++i) {
106 // Extract the two elements of this transposition.
107 std::vector<int> tmp;
108 for (const int x : perm->Cycle(i)) tmp.push_back(x);
109 const int a = tmp[0];
110 const int b = tmp[1];
111
112 // We want one element to appear in matching_column_index and the other to
113 // not appear at all.
114 int num_matches_a = 0;
115 int num_matches_b = 0;
116 int last_match_index = -1;
117 for (int j = 0; j < orbitope[i].size(); ++j) {
118 if (orbitope[i][j] == a) {
119 ++num_matches_a;
120 last_match_index = j;
121 } else if (orbitope[i][j] == b) {
122 ++num_matches_b;
123 last_match_index = j;
124 }
125 }
126 if (last_match_index == -1) break;
127 if (matching_column_index == -1) {
128 matching_column_index = last_match_index;
129 }
130 if (matching_column_index != last_match_index) break;
131 if (num_matches_a == 0 && num_matches_b == 1) {
132 if (a >= in_matrix.size() || !in_matrix[a]) grow.push_back(a);
133 } else if (num_matches_a == 1 && num_matches_b == 0) {
134 if (b >= in_matrix.size() || !in_matrix[b]) grow.push_back(b);
135 } else {
136 break;
137 }
138 }
139
140 // If grow is of full size, we can extend the orbitope.
141 if (grow.size() == num_cycles) {
142 for (int i = 0; i < orbitope.size(); ++i) {
143 orbitope[i].push_back(grow[i]);
144 if (grow[i] >= in_matrix.size()) in_matrix.resize(grow[i] + 1, false);
145 in_matrix[grow[i]] = true;
146 }
147 }
148 }
149
150 return orbitope;
151}
152
153std::vector<int> GetOrbits(
154 int n, absl::Span<const std::unique_ptr<SparsePermutation>> generators) {
155 MergingPartition union_find;
156 union_find.Reset(n);
157 for (const std::unique_ptr<SparsePermutation>& perm : generators) {
158 const int num_cycles = perm->NumCycles();
159 for (int i = 0; i < num_cycles; ++i) {
160 // Note that there is currently no random access api like cycle[j].
161 int first;
162 bool is_first = true;
163 for (const int x : perm->Cycle(i)) {
164 if (is_first) {
165 first = x;
166 is_first = false;
167 } else {
168 union_find.MergePartsOf(first, x);
169 }
170 }
171 }
172 }
173
174 int num_parts = 0;
175 std::vector<int> orbits(n, -1);
176 for (int i = 0; i < n; ++i) {
177 if (union_find.NumNodesInSamePartAs(i) == 1) continue;
178 const int root = union_find.GetRootAndCompressPath(i);
179 if (orbits[root] == -1) orbits[root] = num_parts++;
180 orbits[i] = orbits[root];
181 }
182 return orbits;
183}
184
185std::vector<int> GetOrbitopeOrbits(
186 int n, absl::Span<const std::vector<int>> orbitope) {
187 std::vector<int> orbits(n, -1);
188 for (int i = 0; i < orbitope.size(); ++i) {
189 for (int j = 0; j < orbitope[i].size(); ++j) {
190 CHECK_EQ(orbits[orbitope[i][j]], -1);
191 orbits[orbitope[i][j]] = i;
192 }
193 }
194 return orbits;
195}
196
197} // namespace sat
198} // namespace operations_research
IntegerValue size
int64_t a
Definition table.cc:44
std::vector< int > GetOrbitopeOrbits(int n, absl::Span< const std::vector< int > > orbitope)
std::vector< int > GetOrbits(int n, absl::Span< const std::unique_ptr< SparsePermutation > > generators)
std::vector< std::vector< int > > BasicOrbitopeExtraction(absl::Span< const std::unique_ptr< SparsePermutation > > generators)
In SWIG mode, we don't want anything besides these top-level includes.
const Variable x
Definition qp_tests.cc:127