Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
enforcement.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 <algorithm>
17#include <functional>
18#include <ostream>
19#include <utility>
20#include <vector>
21
22#include "absl/log/check.h"
23#include "absl/types/span.h"
25#include "ortools/sat/model.h"
28
29namespace operations_research {
30namespace sat {
31
32std::ostream& operator<<(std::ostream& os, const EnforcementStatus& e) {
33 switch (e) {
35 os << "IS_FALSE";
36 break;
38 os << "CANNOT_PROPAGATE";
39 break;
41 os << "CAN_PROPAGATE_ENFORCEMENT";
42 break;
44 os << "IS_ENFORCED";
45 break;
46 }
47 return os;
48}
49
51 : SatPropagator("EnforcementPropagator"),
52 trail_(*model->GetOrCreate<Trail>()),
53 assignment_(trail_.Assignment()) {
54 // Sentinel - also start of next Register().
55 starts_.push_back(0);
56}
57
59 rev_int_repository_.SetLevel(trail_.CurrentDecisionLevel());
60 rev_int_repository_.SaveStateWithStamp(&rev_stack_size_, &rev_stamp_);
61 while (propagation_trail_index_ < trail_.Index()) {
62 const Literal literal = trail_[propagation_trail_index_++];
63 if (literal.Index() >= static_cast<int>(watcher_.size())) continue;
64
65 int new_size = 0;
66 auto& watch_list = watcher_[literal.Index()];
67 for (const EnforcementId id : watch_list) {
68 const LiteralIndex index = ProcessIdOnTrue(literal, id);
69 if (index == kNoLiteralIndex) {
70 // We keep the same watcher.
71 watch_list[new_size++] = id;
72 } else {
73 // Change the watcher.
74 CHECK_NE(index, literal.Index());
75 watcher_[index].push_back(id);
76 }
77 }
78 watch_list.resize(new_size);
79
80 // We also mark some constraint false.
81 for (const EnforcementId id : watcher_[literal.NegatedIndex()]) {
82 ChangeStatus(id, EnforcementStatus::IS_FALSE);
83 }
84 }
85 rev_stack_size_ = static_cast<int>(untrail_stack_.size());
86
87 // Compute the enforcement status of any constraint added at a positive level.
88 // This is only needed until we are back to level zero.
89 for (const EnforcementId id : ids_to_fix_until_next_root_level_) {
90 ChangeStatus(id, DebugStatus(id));
91 }
92 if (trail_.CurrentDecisionLevel() == 0) {
93 ids_to_fix_until_next_root_level_.clear();
94 }
95
96 return true;
97}
98
99void EnforcementPropagator::Untrail(const Trail& /*trail*/, int trail_index) {
100 rev_int_repository_.SetLevel(trail_.CurrentDecisionLevel());
101 // Simply revert the status change.
102 const int size = static_cast<int>(untrail_stack_.size());
103 for (int i = size - 1; i >= rev_stack_size_; --i) {
104 const auto [id, status] = untrail_stack_[i];
105 statuses_[id] = status;
106 if (callbacks_[id] != nullptr) callbacks_[id](id, status);
107 }
108 untrail_stack_.resize(rev_stack_size_);
109 propagation_trail_index_ = trail_index;
110}
111
112// Adds a new constraint to the class and returns the constraint id.
113//
114// Note that we accept empty enforcement list so that client code can be used
115// regardless of the presence of enforcement or not. A negative id means the
116// constraint is never enforced, and should be ignored.
118 absl::Span<const Literal> enforcement,
119 std::function<void(EnforcementId, EnforcementStatus)> callback) {
120 int num_true = 0;
121 int num_false = 0;
122 temp_literals_.clear();
123 const int level = trail_.CurrentDecisionLevel();
124 for (const Literal l : enforcement) {
125 // Make sure we always have enough room for the literal and its negation.
126 const int size = std::max(l.Index().value(), l.NegatedIndex().value()) + 1;
127 if (size > static_cast<int>(watcher_.size())) {
128 watcher_.resize(size);
129 }
130 if (assignment_.LiteralIsTrue(l)) {
131 if (level == 0 || trail_.Info(l.Variable()).level == 0) continue;
132 ++num_true;
133 } else if (assignment_.LiteralIsFalse(l)) {
134 ++num_false;
135 }
136 temp_literals_.push_back(l);
137 }
138 gtl::STLSortAndRemoveDuplicates(&temp_literals_);
139
140 // Return special index if always enforced.
141 if (temp_literals_.empty()) {
142 if (callback != nullptr)
143 callback(EnforcementId(-1), EnforcementStatus::IS_ENFORCED);
144 return EnforcementId(-1);
145 }
146
147 const EnforcementId id(static_cast<int>(callbacks_.size()));
148 callbacks_.push_back(std::move(callback));
149
150 CHECK(!temp_literals_.empty());
151 buffer_.insert(buffer_.end(), temp_literals_.begin(), temp_literals_.end());
152 starts_.push_back(buffer_.size()); // Sentinel/next-start.
153
154 // The default status at level zero.
155 statuses_.push_back(temp_literals_.size() == 1
158
159 if (temp_literals_.size() == 1) {
160 watcher_[temp_literals_[0].Index()].push_back(id);
161 } else {
162 // Make sure we watch correct literals.
163 const auto span = GetSpan(id);
164 int num_not_true = 0;
165 for (int i = 0; i < span.size(); ++i) {
166 if (assignment_.LiteralIsTrue(span[i])) continue;
167 std::swap(span[num_not_true], span[i]);
168 ++num_not_true;
169 if (num_not_true == 2) break;
170 }
171
172 // We need to watch one of the literals at highest level.
173 if (num_not_true == 1) {
174 int max_level = trail_.Info(span[1].Variable()).level;
175 for (int i = 2; i < span.size(); ++i) {
176 const int level = trail_.Info(span[i].Variable()).level;
177 if (level > max_level) {
178 max_level = level;
179 std::swap(span[1], span[i]);
180 }
181 }
182 }
183
184 watcher_[span[0].Index()].push_back(id);
185 watcher_[span[1].Index()].push_back(id);
186 }
187
188 // Change status, call callback and set up untrail if the status is different
189 // from EnforcementStatus::CANNOT_PROPAGATE.
190 if (num_false > 0) {
191 ChangeStatus(id, EnforcementStatus::IS_FALSE);
192 } else if (num_true == temp_literals_.size()) {
193 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
194 } else if (num_true + 1 == temp_literals_.size()) {
196 // Because this is the default status, we still need to call the callback.
197 if (temp_literals_.size() == 1) {
198 if (callbacks_[id] != nullptr) {
200 }
201 }
202 }
203
204 // Tricky: if we added something at a positive level, and its status is
205 // not CANNOT_PROPAGATE, then we might need to fix it on backtrack.
206 if (trail_.CurrentDecisionLevel() > 0 &&
207 statuses_[id] != EnforcementStatus::CANNOT_PROPAGATE) {
208 ids_to_fix_until_next_root_level_.push_back(id);
209 }
210
211 return id;
212}
213
214// Add the enforcement reason to the given vector.
216 EnforcementId id, std::vector<Literal>* reason) const {
217 for (const Literal l : GetSpan(id)) {
218 reason->push_back(l.Negated());
219 }
220}
221
222absl::Span<Literal> EnforcementPropagator::GetSpan(EnforcementId id) {
223 if (id < 0) return {};
224 DCHECK_LE(id + 1, starts_.size());
225 const int size = starts_[id + 1] - starts_[id];
226 DCHECK_NE(size, 0);
227 return absl::MakeSpan(&buffer_[starts_[id]], size);
228}
229
230absl::Span<const Literal> EnforcementPropagator::GetSpan(
231 EnforcementId id) const {
232 if (id < 0) return {};
233 DCHECK_LE(id + 1, starts_.size());
234 const int size = starts_[id + 1] - starts_[id];
235 DCHECK_NE(size, 0);
236 return absl::MakeSpan(&buffer_[starts_[id]], size);
237}
238
239LiteralIndex EnforcementPropagator::ProcessIdOnTrue(Literal watched,
240 EnforcementId id) {
241 const EnforcementStatus status = statuses_[id];
242 if (status == EnforcementStatus::IS_FALSE) return kNoLiteralIndex;
243
244 const auto span = GetSpan(id);
245 if (span.size() == 1) {
247 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
248 return kNoLiteralIndex;
249 }
250
251 const int watched_pos = (span[0] == watched) ? 0 : 1;
252 CHECK_EQ(span[watched_pos], watched);
253 if (assignment_.LiteralIsFalse(span[watched_pos ^ 1])) {
254 ChangeStatus(id, EnforcementStatus::IS_FALSE);
255 return kNoLiteralIndex;
256 }
257
258 for (int i = 2; i < span.size(); ++i) {
259 const Literal l = span[i];
260 if (assignment_.LiteralIsFalse(l)) {
261 ChangeStatus(id, EnforcementStatus::IS_FALSE);
262 return kNoLiteralIndex;
263 }
264 if (!assignment_.LiteralIsAssigned(l)) {
265 // Replace the watched literal. Note that if the other watched literal is
266 // true, it should be processed afterwards. We do not change the status
267 std::swap(span[watched_pos], span[i]);
268 return span[watched_pos].Index();
269 }
270 }
271
272 // All literal with index > 1 are true. Two case.
273 if (assignment_.LiteralIsTrue(span[watched_pos ^ 1])) {
274 // All literals are true.
275 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
276 return kNoLiteralIndex;
277 } else {
278 // The other watched literal is the last unassigned
279 CHECK_EQ(status, EnforcementStatus::CANNOT_PROPAGATE);
281 return kNoLiteralIndex;
282 }
283}
284
285void EnforcementPropagator::ChangeStatus(EnforcementId id,
286 EnforcementStatus new_status) {
287 const EnforcementStatus old_status = statuses_[id];
288 if (old_status == new_status) return;
289 if (trail_.CurrentDecisionLevel() != 0) {
290 untrail_stack_.push_back({id, old_status});
291 }
292 statuses_[id] = new_status;
293 if (callbacks_[id] != nullptr) callbacks_[id](id, new_status);
294}
295
297 absl::Span<const Literal> enforcement) const {
298 int num_true = 0;
299 for (const Literal l : enforcement) {
300 if (assignment_.LiteralIsFalse(l)) {
302 }
303 if (assignment_.LiteralIsTrue(l)) ++num_true;
304 }
305 const int size = enforcement.size();
306 if (num_true == size) return EnforcementStatus::IS_ENFORCED;
307 if (num_true + 1 == size) return EnforcementStatus::CAN_PROPAGATE_ENFORCEMENT;
309}
310
312 if (id < 0) return EnforcementStatus::IS_ENFORCED;
313 return Status(GetSpan(id));
314}
315
316} // namespace sat
317} // namespace operations_research
EnforcementStatus Status(EnforcementId id) const
Definition enforcement.h:75
void AddEnforcementReason(EnforcementId id, std::vector< Literal > *reason) const
void Untrail(const Trail &trail, int trail_index) final
EnforcementId Register(absl::Span< const Literal > enforcement, std::function< void(EnforcementId, EnforcementStatus)> callback=nullptr)
EnforcementStatus DebugStatus(EnforcementId id)
LiteralIndex NegatedIndex() const
Definition sat_base.h:93
LiteralIndex Index() const
Definition sat_base.h:92
SatPropagator(const std::string &name)
Definition sat_base.h:786
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
const LiteralIndex kNoLiteralIndex(-1)
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
OR-Tools root namespace.