Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
symmetry.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <cstdint>
17#include <memory>
18#include <vector>
19
20#include "absl/algorithm/container.h"
21#include "absl/log/check.h"
22#include "absl/log/log.h"
23#include "absl/types/span.h"
27#include "ortools/util/stats.h"
29
30namespace operations_research {
31namespace sat {
32
34 : SatPropagator("SymmetryPropagator"),
35 stats_("SymmetryPropagator"),
36 num_propagations_(0),
37 num_conflicts_(0) {}
38
41 LOG(INFO) << stats_.StatString();
42 LOG(INFO) << "num propagations by symmetry: " << num_propagations_;
43 LOG(INFO) << "num conflicts by symmetry: " << num_conflicts_;
44 });
45}
46
48 std::unique_ptr<SparsePermutation> permutation) {
49 if (permutation->NumCycles() == 0) return;
50 SCOPED_TIME_STAT(&stats_);
51 DCHECK_EQ(propagation_trail_index_, 0);
52 if (permutation->Size() > images_.size()) {
53 images_.resize(permutation->Size());
54 }
55 for (int c = 0; c < permutation->NumCycles(); ++c) {
56 int e = permutation->LastElementInCycle(c);
57 for (const int image : permutation->Cycle(c)) {
58 DCHECK_GE(LiteralIndex(e), 0);
59 DCHECK_LE(LiteralIndex(e), images_.size());
60 const int permutation_index = permutations_.size();
61 images_[LiteralIndex(e)].push_back(
62 ImageInfo(permutation_index, Literal(LiteralIndex(image))));
63 e = image;
64 }
65 }
66 permutation_trails_.push_back(std::vector<AssignedLiteralInfo>());
67 permutation_trails_.back().reserve(permutation->Support().size());
68 permutations_.emplace_back(permutation.release());
69}
70
71bool SymmetryPropagator::PropagateNext(Trail* trail) {
72 SCOPED_TIME_STAT(&stats_);
73 const Literal true_literal = (*trail)[propagation_trail_index_];
74 if (true_literal.Index() < images_.size()) {
75 const std::vector<ImageInfo>& images = images_[true_literal.Index()];
76 for (int image_index = 0; image_index < images.size(); ++image_index) {
77 const int p_index = images[image_index].permutation_index;
78
79 // TODO(user): some optim ideas: no need to enqueue if a decision image is
80 // already assigned to false. But then the Untrail() is more involved.
81 std::vector<AssignedLiteralInfo>* p_trail =
82 &(permutation_trails_[p_index]);
83 if (Enqueue(*trail, true_literal, images[image_index].image, p_trail)) {
84 continue;
85 }
86
87 // We have a non-symmetric literal and its image is not already assigned
88 // to true.
89 const AssignedLiteralInfo& non_symmetric =
90 (*p_trail)[p_trail->back().first_non_symmetric_info_index_so_far];
91
92 // If the first non-symmetric literal is a decision, then we can't deduce
93 // anything. Otherwise, it is either a conflict or a propagation.
94 const BooleanVariable non_symmetric_var =
95 non_symmetric.literal.Variable();
96 const AssignmentInfo& assignment_info = trail->Info(non_symmetric_var);
97 if (trail->AssignmentType(non_symmetric_var) ==
99 continue;
100 }
101 const absl::Span<const Literal> initial_reason =
102 trail->Reason(non_symmetric.literal.Variable());
103 if (absl::c_any_of(initial_reason, [this](Literal literal) {
104 return literal.Index() >= num_literals_with_knonw_symmetry_;
105 })) {
106 continue;
107 }
108 if (trail->Assignment().LiteralIsFalse(non_symmetric.image)) {
109 // Conflict.
110 ++num_conflicts_;
111
112 // Set the conflict on the trail.
113 // Note that we need to fetch a reason for this.
114 std::vector<Literal>* conflict = trail->MutableConflict();
115 Permute(p_index, initial_reason, conflict);
116 conflict->push_back(non_symmetric.image);
117 for (Literal literal : *conflict) {
118 DCHECK(trail->Assignment().LiteralIsFalse(literal)) << literal;
119 }
120
121 // Backtrack over all the enqueues we just did.
122 for (; image_index >= 0; --image_index) {
123 permutation_trails_[images[image_index].permutation_index].pop_back();
124 }
125 return false;
126 } else {
127 // Propagation.
128 if (trail->Index() >= reasons_.size()) {
129 reasons_.resize(trail->Index() + 1);
130 }
131 reasons_[trail->Index()] = {assignment_info.trail_index, p_index};
132 trail->Enqueue(non_symmetric.image, propagator_id_);
133 ++num_propagations_;
134 }
135 }
136 }
138 return true;
139}
140
142 const int old_index = trail->Index();
143 while (trail->Index() == old_index && propagation_trail_index_ < old_index) {
144 if (!PropagateNext(trail)) return false;
145 }
146 return true;
147}
148
149void SymmetryPropagator::Untrail(const Trail& trail, int trail_index) {
150 SCOPED_TIME_STAT(&stats_);
151 while (propagation_trail_index_ > trail_index) {
153 const Literal true_literal = trail[propagation_trail_index_];
154 if (true_literal.Index() < images_.size()) {
155 for (ImageInfo& info : images_[true_literal.Index()]) {
156 permutation_trails_[info.permutation_index].pop_back();
157 }
158 }
159 }
160}
161
162absl::Span<const Literal> SymmetryPropagator::Reason(
163 const Trail& trail, int trail_index, int64_t /*conflict_id*/) const {
164 SCOPED_TIME_STAT(&stats_);
165 const ReasonInfo& reason_info = reasons_[trail_index];
166 std::vector<Literal>* reason = trail.GetEmptyVectorToStoreReason(trail_index);
167 Permute(reason_info.symmetry_index,
168 trail.Reason(trail[reason_info.source_trail_index].Variable()),
169 reason);
170 return *reason;
171}
172
173bool SymmetryPropagator::Enqueue(const Trail& trail, Literal literal,
174 Literal image,
175 std::vector<AssignedLiteralInfo>* p_trail) {
176 // Small optimization to get the trail index of literal.
177 const int literal_trail_index = propagation_trail_index_;
178 DCHECK_EQ(literal_trail_index, trail.Info(literal.Variable()).trail_index);
179
180 // Push the new AssignedLiteralInfo on the permutation trail. Note that we
181 // don't know yet its first_non_symmetric_info_index_so_far but we know that
182 // they are increasing, so we can restart by the one of the previous
183 // AssignedLiteralInfo.
184 p_trail->push_back(AssignedLiteralInfo(
185 literal, image,
186 p_trail->empty()
187 ? 0
188 : p_trail->back().first_non_symmetric_info_index_so_far));
189 int* index = &(p_trail->back().first_non_symmetric_info_index_so_far);
190
191 // Compute first_non_symmetric_info_index_so_far.
192 while (*index < p_trail->size() &&
193 trail.Assignment().LiteralIsTrue((*p_trail)[*index].image)) {
194 // This AssignedLiteralInfo is symmetric for the full solver assignment.
195 // We test if it is also symmetric for the assignment so far:
196 if (trail.Info((*p_trail)[*index].image.Variable()).trail_index >
197 literal_trail_index) {
198 // It isn't, so we can stop the function here. We will continue the loop
199 // when this function is called again with an higher trail_index.
200 return true;
201 }
202 ++(*index);
203 }
204 return *index == p_trail->size();
205}
206
207void SymmetryPropagator::Permute(int index, absl::Span<const Literal> input,
208 std::vector<Literal>* output) const {
209 SCOPED_TIME_STAT(&stats_);
210
211 // Initialize tmp_literal_mapping_ (resize it if needed).
212 DCHECK_GE(index, 0);
213 DCHECK_LT(index, permutations_.size());
214 const SparsePermutation& permutation = *(permutations_[index].get());
215 if (permutation.Size() > tmp_literal_mapping_.size()) {
216 tmp_literal_mapping_.resize(permutation.Size());
217 for (LiteralIndex i(0); i < tmp_literal_mapping_.size(); ++i) {
218 tmp_literal_mapping_[i] = Literal(i);
219 }
220 }
221 for (int c = 0; c < permutation.NumCycles(); ++c) {
222 int e = permutation.LastElementInCycle(c);
223 for (const int image : permutation.Cycle(c)) {
224 tmp_literal_mapping_[LiteralIndex(e)] = Literal(LiteralIndex(image));
225 e = image;
226 }
227 }
228
229 // Permute the input into the output.
230 output->clear();
231 for (const Literal literal : input) {
232 if (literal.Index() < tmp_literal_mapping_.size()) {
233 output->push_back(tmp_literal_mapping_[literal.Index()]);
234 } else {
235 output->push_back(literal);
236 }
237 }
238
239 // Clean up.
240 for (const int e : permutation.Support()) {
241 tmp_literal_mapping_[LiteralIndex(e)] = Literal(LiteralIndex(e));
242 }
243}
244
245} // namespace sat
246} // namespace operations_research
const std::vector< int > & Support() const
LiteralIndex Index() const
Definition sat_base.h:92
BooleanVariable Variable() const
Definition sat_base.h:88
SatPropagator(const std::string &name)
Definition sat_base.h:786
void AddSymmetry(std::unique_ptr< SparsePermutation > permutation)
Definition symmetry.cc:47
void Untrail(const Trail &trail, int trail_index) final
Definition symmetry.cc:149
absl::Span< const Literal > Reason(const Trail &trail, int trail_index, int64_t conflict_id) const final
Definition symmetry.cc:162
void Permute(int index, absl::Span< const Literal > input, std::vector< Literal > *output) const
Definition symmetry.cc:207
std::vector< Literal > * MutableConflict()
Definition sat_base.h:613
int AssignmentType(BooleanVariable var) const
Definition sat_base.h:942
void Enqueue(Literal true_literal, int propagator_id)
Definition sat_base.h:350
absl::Span< const Literal > Reason(BooleanVariable var, int64_t conflict_id=-1) const
Definition sat_base.h:951
const AssignmentInfo & Info(BooleanVariable var) const
Definition sat_base.h:655
std::vector< Literal > * GetEmptyVectorToStoreReason(int trail_index) const
Definition sat_base.h:560
const VariablesAssignment & Assignment() const
Definition sat_base.h:654
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:203
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:206
OR-Tools root namespace.
static int input(yyscan_t yyscanner)
#define IF_STATS_ENABLED(instructions)
Definition stats.h:412
#define SCOPED_TIME_STAT(stats)
Definition stats.h:419