OR-Tools  9.6
symmetry.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 
14 #include "ortools/sat/symmetry.h"
15 
16 #include <memory>
17 #include <vector>
18 
19 #include "absl/types/span.h"
21 #include "ortools/base/logging.h"
23 #include "ortools/sat/sat_base.h"
24 #include "ortools/util/stats.h"
26 
27 namespace operations_research {
28 namespace 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_);
48  DCHECK_EQ(propagation_trail_index_, 0);
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 
68 bool 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) {
111  DCHECK(trail->Assignment().LiteralIsFalse(literal)) << literal;
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 
142 void 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 
155 absl::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 
166 bool 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 
200 void 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
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:77
LiteralIndex Index() const
Definition: sat_base.h:90
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:262
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:403
int AssignmentType(BooleanVariable var) const
Definition: sat_base.h:608
std::vector< Literal > * GetEmptyVectorToStoreReason(int trail_index) const
Definition: sat_base.h:332
std::vector< Literal > * MutableConflict()
Definition: sat_base.h:373
absl::Span< const Literal > Reason(BooleanVariable var) const
Definition: sat_base.h:617
const VariablesAssignment & Assignment() const
Definition: sat_base.h:402
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:164
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:161
int index
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88
static int input(yyscan_t yyscanner)
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:438
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:439
static constexpr int kSearchDecision
Definition: sat_base.h:237