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