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