Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_propagation.h
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
14#ifndef OR_TOOLS_SAT_LINEAR_PROPAGATION_H_
15#define OR_TOOLS_SAT_LINEAR_PROPAGATION_H_
16
17#include <stdint.h>
18
19#include <deque>
20#include <functional>
21#include <limits>
22#include <ostream>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "absl/base/attributes.h"
28#include "absl/container/inlined_vector.h"
29#include "absl/strings/string_view.h"
30#include "absl/types/span.h"
32#include "ortools/sat/integer.h"
33#include "ortools/sat/model.h"
38#include "ortools/util/bitset.h"
39#include "ortools/util/rev.h"
42
43namespace operations_research {
44namespace sat {
45
47
48// An enforced constraint can be in one of these 4 states.
49// Note that we rely on the integer encoding to take 2 bits for optimization.
51 // One enforcement literal is false.
52 IS_FALSE = 0,
53 // More than two literals are unassigned.
55 // All enforcement literals are true but one.
56 CAN_PROPAGATE = 2,
57 // All enforcement literals are true.
58 IS_ENFORCED = 3,
59};
60
61std::ostream& operator<<(std::ostream& os, const EnforcementStatus& e);
62
63// This is meant as an helper to deal with enforcement for any constraint.
65 public:
67
68 // SatPropagator interface.
69 bool Propagate(Trail* trail) final;
70 void Untrail(const Trail& trail, int trail_index) final;
71
72 // Adds a new constraint to the class and register a callback that will
73 // be called on status change. Note that we also call the callback with the
74 // initial status if different from CANNOT_PROPAGATE when added.
75 //
76 // It is better to not call this for empty enforcement list, but you can. A
77 // negative id means the level zero status will never change, and only the
78 // first call to callback() should be necessary, we don't save it.
79 EnforcementId Register(
80 absl::Span<const Literal> enforcement,
81 std::function<void(EnforcementId, EnforcementStatus)> callback = nullptr);
82
83 // Add the enforcement reason to the given vector.
84 void AddEnforcementReason(EnforcementId id,
85 std::vector<Literal>* reason) const;
86
87 // Try to propagate when the enforced constraint is not satisfiable.
88 // This is currently in O(enforcement_size).
89 ABSL_MUST_USE_RESULT bool PropagateWhenFalse(
90 EnforcementId id, absl::Span<const Literal> literal_reason,
91 absl::Span<const IntegerLiteral> integer_reason);
92
93 EnforcementStatus Status(EnforcementId id) const { return statuses_[id]; }
94
95 // Recompute the status from the current assignment.
96 // This should only used in DCHECK().
97 EnforcementStatus DebugStatus(EnforcementId id);
98
99 // Returns the enforcement literals of the given id.
100 absl::Span<const Literal> GetEnforcementLiterals(EnforcementId id) const {
101 if (id < 0) return {};
102 return GetSpan(id);
103 }
104
105 private:
106 absl::Span<Literal> GetSpan(EnforcementId id);
107 absl::Span<const Literal> GetSpan(EnforcementId id) const;
108 void ChangeStatus(EnforcementId id, EnforcementStatus new_status);
109
110 // Returns kNoLiteralIndex if nothing need to change or a new literal to
111 // watch. This also calls the registered callback.
112 LiteralIndex ProcessIdOnTrue(Literal watched, EnforcementId id);
113
114 // External classes.
115 const Trail& trail_;
116 const VariablesAssignment& assignment_;
117 IntegerTrail* integer_trail_;
118 RevIntRepository* rev_int_repository_;
119
120 // All enforcement will be copied there, and we will create Span out of this.
121 // Note that we don't store the span so that we are not invalidated on buffer_
122 // resizing.
124 std::vector<Literal> buffer_;
125
128 EnforcementId, std::function<void(EnforcementId, EnforcementStatus)>>
129 callbacks_;
130
131 // Used to restore status and call callback on untrail.
132 std::vector<std::pair<EnforcementId, EnforcementStatus>> untrail_stack_;
133 int rev_stack_size_ = 0;
134 int64_t rev_stamp_ = 0;
135
136 // We use a two watcher scheme.
138 watcher_;
139
140 std::vector<Literal> temp_literals_;
141 std::vector<Literal> temp_reason_;
142};
143
144// Helper class to decide on the constraint propagation order.
145//
146// Each constraint might push some variables which might in turn make other
147// constraint tighter. In general, it seems better to make sure we push first
148// constraints that are not affected by other variables and delay the
149// propagation of constraint that we know will become tigher.
150//
151// Note that we can have cycle in this graph, and that this is not necessarily a
152// conflict.
154 public:
156 ModelRandomGenerator* random,
157 std::function<absl::Span<const IntegerVariable>(int)> id_to_vars)
158 : random_(random), id_to_vars_func_(std::move(id_to_vars)) {}
159
160 void Resize(int num_vars, int num_ids) {
161 var_has_entry_.Resize(IntegerVariable(num_vars));
162 var_to_id_.resize(num_vars);
163 var_to_lb_.resize(num_vars);
164 var_to_pos_.resize(num_vars);
165
166 in_ids_.Resize(num_ids);
167 }
168
169 void Register(int id, IntegerVariable var, IntegerValue lb) {
170 DCHECK_LT(id, in_ids_.size());
171 DCHECK_LT(var.value(), var_to_id_.size());
172 if (var_has_entry_[var]) {
173 if (var_to_lb_[var] >= lb) return;
174 } else {
176 var_to_pos_[var] = to_clear_.size();
177 to_clear_.push_back(var);
178 }
179 var_to_lb_[var] = lb;
180 var_to_id_[var] = id;
181 if (!in_ids_[id]) {
182 in_ids_.Set(id);
183
184 // All new ids are likely not in a cycle, we want to test them first.
185 ids_.push_front(id);
186 }
187 }
188
189 void Clear() {
190 for (const IntegerVariable var : to_clear_) {
192 }
193 to_clear_.clear();
194 for (const int id : ids_) {
195 in_ids_.Clear(id);
196 }
197 ids_.clear();
198 }
199
200 // Return -1 if there is none.
201 // This returns a constraint with min degree.
202 //
203 // TODO(user): fix quadratic algo? We can use var_to_ids_func_() to maintain
204 // the degree. But note that with the start_ optim and because we expect
205 // mainly degree zero, this seems to be faster.
206 int NextId() {
207 if (ids_.empty()) return -1;
208
209 int best_id = 0;
210 int best_num_vars = 0;
211 int best_degree = std::numeric_limits<int>::max();
212 const int size = ids_.size();
213 const auto var_has_entry = var_has_entry_.const_view();
214 for (int i = 0; i < size; ++i) {
215 const int id = ids_.front();
216 ids_.pop_front();
217 DCHECK(in_ids_[id]);
218
219 int degree = 0;
220 absl::Span<const IntegerVariable> vars = id_to_vars_func_(id);
221 for (const IntegerVariable var : vars) {
222 if (var_has_entry[var]) ++degree;
223 }
224
225 // We select the min-degree and prefer lower constraint size.
226 // We however stop at the first degree zero.
227 if (degree < best_degree ||
228 (degree == best_degree && vars.size() < best_num_vars)) {
229 best_degree = degree;
230 best_num_vars = vars.size();
231 best_id = id;
232 if (best_degree == 0) {
233 in_ids_.Clear(best_id);
234 return best_id;
235 }
236 }
237
238 ids_.push_back(id);
239 }
240
241 // We didn't find any degree zero, we scanned the whole queue.
242 // Extract best_id while keeping the order stable.
243 //
244 // We tried to randomize the order, it does add more variance but also seem
245 // worse overall.
246 int new_size = 0;
247 for (const int id : ids_) {
248 if (id == best_id) continue;
249 ids_[new_size++] = id;
250 }
251 ids_.resize(new_size);
252 in_ids_.Clear(best_id);
253 return best_id;
254 }
255
256 void UpdateBound(IntegerVariable var, IntegerValue lb) {
257 if (!var_has_entry_[var]) return;
258 if (lb < var_to_lb_[var]) return;
259
261 const int pos = var_to_pos_[var];
262 to_clear_[pos] = to_clear_.back();
263 var_to_pos_[to_clear_.back()] = pos;
264 to_clear_.pop_back();
265 }
266
267 bool IsEmpty() const { return ids_.empty(); }
268
269 bool VarShouldBePushedById(IntegerVariable var, int id) {
270 if (!var_has_entry_[var]) return false;
271 if (var_to_id_[var] != id) return false;
272 return true;
273 }
274
275 public:
277 std::function<absl::Span<const IntegerVariable>(int)> id_to_vars_func_;
278
279 // For each variable we only keep the constraint id that pushes it further.
280 // In case of tie, we only keep the first to be registered.
285 std::vector<IntegerVariable> to_clear_;
286
287 // Set/queue of constraints to be propagated.
288 int start_ = 0;
290 std::deque<int> ids_;
291};
292
293// This is meant to supersede both IntegerSumLE and the PrecedencePropagator.
294//
295// TODO(user): This is a work in progress and is currently incomplete:
296// - Lack more incremental support for faster propag.
297// - Lack detection and propagation of at least one of these linear is true
298// which can be used to propagate more bound if a variable appear in all these
299// constraint.
303 public:
304 explicit LinearPropagator(Model* model);
305 ~LinearPropagator() override;
306 bool Propagate() final;
307 void SetLevel(int level) final;
308
309 // Adds a new constraint to the propagator.
310 // We support adding constraint at a positive level:
311 // - This will push new propagation right away.
312 // - This will returns false if the constraint is currently a conflict.
313 bool AddConstraint(absl::Span<const Literal> enforcement_literals,
314 absl::Span<const IntegerVariable> vars,
315 absl::Span<const IntegerValue> coeffs,
316 IntegerValue upper_bound);
317
318 // For LazyReasonInterface.
319 void Explain(int id, IntegerValue propagation_slack,
320 IntegerVariable var_to_explain, int trail_index,
321 std::vector<Literal>* literals_reason,
322 std::vector<int>* trail_indices_reason) final;
323
324 private:
325 // We try to pack the struct as much as possible. Using a maximum size of
326 // 1 << 29 should be okay since we split long constraint anyway. Technically
327 // we could use int16_t or even int8_t if we wanted, but we just need to make
328 // sure we do split ALL constraints, not just the one from the initial model.
329 //
330 // TODO(user): We could also move some less often used fields out. like
331 // initial size and enf_id that are only needed when we push something.
332 struct ConstraintInfo {
333 unsigned int enf_status : 2;
334 // With Visual Studio or minGW, using bool here breaks the struct packing.
335 unsigned int all_coeffs_are_one : 1;
336 unsigned int initial_size : 29; // Const. The size including all terms.
337
338 EnforcementId enf_id; // Const. The id in enforcement_propagator_.
339 int start; // Const. The start of the constraint in the buffers.
340 int rev_size; // The size of the non-fixed terms.
341 IntegerValue rev_rhs; // The current rhs, updated on fixed terms.
342 };
343
344 static_assert(sizeof(ConstraintInfo) == 24,
345 "ERROR_ConstraintInfo_is_not_well_compacted");
346
347 absl::Span<IntegerValue> GetCoeffs(const ConstraintInfo& info);
348 absl::Span<IntegerVariable> GetVariables(const ConstraintInfo& info);
349
350 // Called when the lower bound of a variable changed. The id is the constraint
351 // id that caused this change or -1 if it comes from an external source.
352 void OnVariableChange(IntegerVariable var, IntegerValue lb, int id);
353
354 // Returns false on conflict.
355 ABSL_MUST_USE_RESULT bool PropagateOneConstraint(int id);
356 ABSL_MUST_USE_RESULT bool PropagateInfeasibleConstraint(int id,
357 IntegerValue slack);
358 ABSL_MUST_USE_RESULT bool ReportConflictingCycle();
359 ABSL_MUST_USE_RESULT bool DisassembleSubtree(int root_id, int num_tight);
360
361 // This loops over the given constraint and returns the coefficient of var and
362 // NegationOf(next_var). Both should be non-zero (Checked).
363 //
364 // If the slack of this constraint was tight for next_var, then pushing var
365 // will push next_var again depending on these coefficients.
366 std::pair<IntegerValue, IntegerValue> GetCycleCoefficients(
367 int id, IntegerVariable var, IntegerVariable next_var);
368
369 // Returns (slack, num_to_push) of the given constraint.
370 // If slack < 0 we have a conflict or might push the enforcement.
371 // If slack >= 0 the first num_to_push variables can be pushed.
372 std::pair<IntegerValue, int> AnalyzeConstraint(int id);
373
374 void ClearPropagatedBy();
375 void CanonicalizeConstraint(int id);
376 void AddToQueueIfNeeded(int id);
377 void SetPropagatedBy(IntegerVariable var, int id);
378 std::string ConstraintDebugString(int id);
379
380 // External class needed.
381 Trail* trail_;
382 IntegerTrail* integer_trail_;
383 EnforcementPropagator* enforcement_propagator_;
384 GenericLiteralWatcher* watcher_;
385 TimeLimit* time_limit_;
386 RevIntRepository* rev_int_repository_;
387 RevIntegerValueRepository* rev_integer_value_repository_;
388 PrecedenceRelations* precedences_;
389 ModelRandomGenerator* random_;
390 SharedStatistics* shared_stats_ = nullptr;
391 const int watcher_id_;
392
393 // To know when we backtracked. See SetLevel().
394 int previous_level_ = 0;
395
396 // The key to our incrementality. This will be cleared once the propagation
397 // is done, and automatically updated by the integer_trail_ with all the
398 // IntegerVariable that changed since the last clear.
399 SparseBitset<IntegerVariable> modified_vars_;
400
401 // Per constraint info used during propagation. Note that we keep pointer for
402 // the rev_size/rhs there, so we do need a deque.
403 std::deque<ConstraintInfo> infos_;
404 std::vector<IntegerValue> initial_rhs_;
405
406 // Buffer of the constraints data.
407 std::vector<IntegerVariable> variables_buffer_;
408 std::vector<IntegerValue> coeffs_buffer_;
409 std::vector<IntegerValue> buffer_of_ones_;
410
411 // Filled by PropagateOneConstraint().
412 std::vector<IntegerValue> max_variations_;
413
414 // For reasons computation. Parallel vectors.
415 std::vector<IntegerLiteral> integer_reason_;
416 std::vector<IntegerValue> reason_coeffs_;
417 std::vector<Literal> literal_reason_;
418
419 // Queue of constraint to propagate.
420 Bitset64<int> in_queue_;
421 std::deque<int> propagation_queue_;
422
423 // This only contain constraint that currently push some bounds.
425
426 // Unenforced constraints are marked as "in_queue_" but not actually added
427 // to the propagation_queue_.
428 int rev_unenforced_size_ = 0;
429 std::vector<int> unenforced_constraints_;
430
431 // Watchers.
434 var_to_constraint_ids_;
435
436 // For an heuristic similar to Tarjan contribution to Bellman-Ford algorithm.
437 // We mark for each variable the last constraint that pushed it, and also keep
438 // the count of propagated variable for each constraint.
439 SparseBitset<IntegerVariable> propagated_by_was_set_;
441 std::vector<int> id_to_propagation_count_;
442
443 // Used by DissasembleSubtreeAndAddToQueue().
444 struct DissasembleQueueEntry {
445 int id;
446 IntegerVariable var;
447 IntegerValue increase;
448 };
449 std::vector<DissasembleQueueEntry> disassemble_queue_;
450 std::vector<DissasembleQueueEntry> disassemble_branch_;
451 std::vector<std::pair<IntegerVariable, IntegerValue>> disassemble_candidates_;
452
453 // This is used to update the deterministic time.
454 int64_t num_terms_for_dtime_update_ = 0;
455
456 // Stats.
457 int64_t num_pushes_ = 0;
458 int64_t num_enforcement_pushes_ = 0;
459 int64_t num_cycles_ = 0;
460 int64_t num_failed_cycles_ = 0;
461 int64_t num_short_reasons_ = 0;
462 int64_t num_long_reasons_ = 0;
463 int64_t num_scanned_ = 0;
464 int64_t num_explored_in_disassemble_ = 0;
465 int64_t num_delayed_ = 0;
466 int64_t num_bool_aborts_ = 0;
467 int64_t num_loop_aborts_ = 0;
468 int64_t num_ignored_ = 0;
469};
470
471} // namespace sat
472} // namespace operations_research
473
474#endif // OR_TOOLS_SAT_LINEAR_PROPAGATION_H_
IntegerValue size
IndexType size() const
Returns how many bits this Bitset64 can hold.
Definition bitset.h:468
void Clear(IndexType i)
Sets the bit at position i to 0.
Definition bitset.h:510
void Resize(IndexType size)
Definition bitset.h:479
ConstView const_view() const
Definition bitset.h:464
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:548
void UpdateBound(IntegerVariable var, IntegerValue lb)
util_intops::StrongVector< IntegerVariable, IntegerValue > var_to_lb_
util_intops::StrongVector< IntegerVariable, int > var_to_id_
bool VarShouldBePushedById(IntegerVariable var, int id)
std::function< absl::Span< const IntegerVariable >(int)> id_to_vars_func_
ConstraintPropagationOrder(ModelRandomGenerator *random, std::function< absl::Span< const IntegerVariable >(int)> id_to_vars)
int start_
Set/queue of constraints to be propagated.
util_intops::StrongVector< IntegerVariable, int > var_to_pos_
void Register(int id, IntegerVariable var, IntegerValue lb)
This is meant as an helper to deal with enforcement for any constraint.
bool Propagate(Trail *trail) final
SatPropagator interface.
EnforcementStatus Status(EnforcementId id) const
void AddEnforcementReason(EnforcementId id, std::vector< Literal > *reason) const
Add the enforcement reason to the given vector.
ABSL_MUST_USE_RESULT bool PropagateWhenFalse(EnforcementId id, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
void Untrail(const Trail &trail, int trail_index) final
EnforcementId Register(absl::Span< const Literal > enforcement, std::function< void(EnforcementId, EnforcementStatus)> callback=nullptr)
absl::Span< const Literal > GetEnforcementLiterals(EnforcementId id) const
Returns the enforcement literals of the given id.
EnforcementStatus DebugStatus(EnforcementId id)
bool AddConstraint(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coeffs, IntegerValue upper_bound)
Adds a new constraint to the propagator.
void Explain(int id, IntegerValue propagation_slack, IntegerVariable var_to_explain, int trail_index, std::vector< Literal > *literals_reason, std::vector< int > *trail_indices_reason) final
For LazyReasonInterface.
Base class for CP like propagators.
Definition integer.h:1414
Base class for all the SAT constraints.
Definition sat_base.h:533
Simple class to add statistics by name and print them at the end.
void resize(size_type new_size)
IntVar * var
double upper_bound
GRBmodel * model
MPCallback * callback
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
@ IS_ENFORCED
All enforcement literals are true.
@ CANNOT_PROPAGATE
More than two literals are unassigned.
@ IS_FALSE
One enforcement literal is false.
@ CAN_PROPAGATE
All enforcement literals are true but one.
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
#define DEFINE_STRONG_INDEX_TYPE(index_type_name)