Google OR-Tools v9.12
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.
153//
154// Note that we can have cycle in this graph, and that this is not necessarily a
155// conflict.
157 public:
159 ModelRandomGenerator* random,
160 std::function<absl::Span<const IntegerVariable>(int)> id_to_vars)
161 : random_(random), id_to_vars_func_(std::move(id_to_vars)) {}
162
163 void Resize(int num_vars, int num_ids) {
164 var_has_entry_.Resize(IntegerVariable(num_vars));
165 var_to_id_.resize(num_vars);
166 var_to_lb_.resize(num_vars);
167 var_to_pos_.resize(num_vars);
168
169 in_ids_.Resize(num_ids);
170 }
171
172 void Register(int id, IntegerVariable var, IntegerValue lb) {
173 DCHECK_LT(id, in_ids_.size());
174 DCHECK_LT(var.value(), var_to_id_.size());
175 if (var_has_entry_[var]) {
176 if (var_to_lb_[var] >= lb) return;
177 } else {
178 var_has_entry_.Set(var);
179 var_to_pos_[var] = to_clear_.size();
180 to_clear_.push_back(var);
181 }
182 var_to_lb_[var] = lb;
183 var_to_id_[var] = id;
184 if (!in_ids_[id]) {
185 in_ids_.Set(id);
186
187 // All new ids are likely not in a cycle, we want to test them first.
188 ids_.push_front(id);
189 }
190 }
191
192 void Clear() {
193 for (const IntegerVariable var : to_clear_) {
194 var_has_entry_.Clear(var);
195 }
196 to_clear_.clear();
197 for (const int id : ids_) {
198 in_ids_.Clear(id);
199 }
200 ids_.clear();
201 }
202
203 // Return -1 if there is none.
204 // This returns a constraint with min degree.
205 //
206 // TODO(user): fix quadratic algo? We can use var_to_ids_func_() to maintain
207 // the degree. But note that with the start_ optim and because we expect
208 // mainly degree zero, this seems to be faster.
209 int NextId() {
210 if (ids_.empty()) return -1;
211
212 int best_id = 0;
213 int best_num_vars = 0;
214 int best_degree = std::numeric_limits<int>::max();
215 const int size = ids_.size();
216 const auto var_has_entry = var_has_entry_.const_view();
217 for (int i = 0; i < size; ++i) {
218 const int id = ids_.front();
219 ids_.pop_front();
220 DCHECK(in_ids_[id]);
221
222 int degree = 0;
223 absl::Span<const IntegerVariable> vars = id_to_vars_func_(id);
224 for (const IntegerVariable var : vars) {
225 if (var_has_entry[var]) ++degree;
226 }
227
228 // We select the min-degree and prefer lower constraint size.
229 // We however stop at the first degree zero.
230 if (degree < best_degree ||
231 (degree == best_degree && vars.size() < best_num_vars)) {
232 best_degree = degree;
233 best_num_vars = vars.size();
234 best_id = id;
235 if (best_degree == 0) {
236 in_ids_.Clear(best_id);
237 return best_id;
238 }
239 }
240
241 ids_.push_back(id);
242 }
243
244 // We didn't find any degree zero, we scanned the whole queue.
245 // Extract best_id while keeping the order stable.
246 //
247 // We tried to randomize the order, it does add more variance but also seem
248 // worse overall.
249 int new_size = 0;
250 for (const int id : ids_) {
251 if (id == best_id) continue;
252 ids_[new_size++] = id;
253 }
254 ids_.resize(new_size);
255 in_ids_.Clear(best_id);
256 return best_id;
257 }
258
259 void UpdateBound(IntegerVariable var, IntegerValue lb) {
260 if (!var_has_entry_[var]) return;
261 if (lb < var_to_lb_[var]) return;
262
263 var_has_entry_.Clear(var);
264 const int pos = var_to_pos_[var];
265 to_clear_[pos] = to_clear_.back();
266 var_to_pos_[to_clear_.back()] = pos;
267 to_clear_.pop_back();
268 }
269
270 bool IsEmpty() const { return ids_.empty(); }
271
272 bool VarShouldBePushedById(IntegerVariable var, int id) {
273 if (!var_has_entry_[var]) return false;
274 if (var_to_id_[var] != id) return false;
275 return true;
276 }
277
278 public:
280 std::function<absl::Span<const IntegerVariable>(int)> id_to_vars_func_;
281
282 // For each variable we only keep the constraint id that pushes it further.
283 // In case of tie, we only keep the first to be registered.
288 std::vector<IntegerVariable> to_clear_;
289
290 // Set/queue of constraints to be propagated.
291 int start_ = 0;
293 std::deque<int> ids_;
294};
295
296// This is meant to supersede both IntegerSumLE and the PrecedencePropagator.
297//
298// TODO(user): This is a work in progress and is currently incomplete:
299// - Lack more incremental support for faster propag.
300// - Lack detection and propagation of at least one of these linear is true
301// which can be used to propagate more bound if a variable appear in all these
302// constraint.
306 public:
307 explicit LinearPropagator(Model* model);
308 ~LinearPropagator() override;
309 bool Propagate() final;
310 void SetLevel(int level) final;
311
312 // Adds a new constraint to the propagator.
313 // We support adding constraint at a positive level:
314 // - This will push new propagation right away.
315 // - This will returns false if the constraint is currently a conflict.
316 bool AddConstraint(absl::Span<const Literal> enforcement_literals,
317 absl::Span<const IntegerVariable> vars,
318 absl::Span<const IntegerValue> coeffs,
319 IntegerValue upper_bound);
320
321 // For LazyReasonInterface.
322 void Explain(int id, IntegerValue propagation_slack,
323 IntegerVariable var_to_explain, int trail_index,
324 std::vector<Literal>* literals_reason,
325 std::vector<int>* trail_indices_reason) final;
326
327 private:
328 // We try to pack the struct as much as possible. Using a maximum size of
329 // 1 << 29 should be okay since we split long constraint anyway. Technically
330 // we could use int16_t or even int8_t if we wanted, but we just need to make
331 // sure we do split ALL constraints, not just the one from the initial model.
332 //
333 // TODO(user): We could also move some less often used fields out. like
334 // initial size and enf_id that are only needed when we push something.
335 struct ConstraintInfo {
336 unsigned int enf_status : 2;
337 // With Visual Studio or minGW, using bool here breaks the struct packing.
338 unsigned int all_coeffs_are_one : 1;
339 unsigned int initial_size : 29; // Const. The size including all terms.
340
341 EnforcementId enf_id; // Const. The id in enforcement_propagator_.
342 int start; // Const. The start of the constraint in the buffers.
343 int rev_size; // The size of the non-fixed terms.
344 IntegerValue rev_rhs; // The current rhs, updated on fixed terms.
345 };
346
347 static_assert(sizeof(ConstraintInfo) == 24,
348 "ERROR_ConstraintInfo_is_not_well_compacted");
349
350 absl::Span<IntegerValue> GetCoeffs(const ConstraintInfo& info);
351 absl::Span<IntegerVariable> GetVariables(const ConstraintInfo& info);
352
353 // Called when the lower bound of a variable changed. The id is the constraint
354 // id that caused this change or -1 if it comes from an external source.
355 void OnVariableChange(IntegerVariable var, IntegerValue lb, int id);
356
357 // Returns false on conflict.
358 ABSL_MUST_USE_RESULT bool PropagateOneConstraint(int id);
359 ABSL_MUST_USE_RESULT bool PropagateInfeasibleConstraint(int id,
360 IntegerValue slack);
361 ABSL_MUST_USE_RESULT bool ReportConflictingCycle();
362 ABSL_MUST_USE_RESULT bool DisassembleSubtree(int root_id, int num_tight);
363
364 // This loops over the given constraint and returns the coefficient of var and
365 // NegationOf(next_var). Both should be non-zero (Checked).
366 //
367 // If the slack of this constraint was tight for next_var, then pushing var
368 // will push next_var again depending on these coefficients.
369 std::pair<IntegerValue, IntegerValue> GetCycleCoefficients(
370 int id, IntegerVariable var, IntegerVariable next_var);
371
372 // Returns (slack, num_to_push) of the given constraint.
373 // If slack < 0 we have a conflict or might push the enforcement.
374 // If slack >= 0 the first num_to_push variables can be pushed.
375 std::pair<IntegerValue, int> AnalyzeConstraint(int id);
376
377 void ClearPropagatedBy();
378 void CanonicalizeConstraint(int id);
379 void AddToQueueIfNeeded(int id);
380 void SetPropagatedBy(IntegerVariable var, int id);
381 std::string ConstraintDebugString(int id);
382
383 // External class needed.
384 Trail* trail_;
385 IntegerTrail* integer_trail_;
386 EnforcementPropagator* enforcement_propagator_;
387 GenericLiteralWatcher* watcher_;
388 TimeLimit* time_limit_;
389 RevIntRepository* rev_int_repository_;
390 RevIntegerValueRepository* rev_integer_value_repository_;
391 PrecedenceRelations* precedences_;
392 ModelRandomGenerator* random_;
393 SharedStatistics* shared_stats_ = nullptr;
394 const int watcher_id_;
395
396 // To know when we backtracked. See SetLevel().
397 int previous_level_ = 0;
398
399 // The key to our incrementality. This will be cleared once the propagation
400 // is done, and automatically updated by the integer_trail_ with all the
401 // IntegerVariable that changed since the last clear.
402 SparseBitset<IntegerVariable> modified_vars_;
403
404 // Per constraint info used during propagation. Note that we keep pointer for
405 // the rev_size/rhs there, so we do need a deque.
406 std::deque<ConstraintInfo> infos_;
407 std::vector<IntegerValue> initial_rhs_;
408
409 // Buffer of the constraints data.
410 std::vector<IntegerVariable> variables_buffer_;
411 std::vector<IntegerValue> coeffs_buffer_;
412 std::vector<IntegerValue> buffer_of_ones_;
413
414 // Filled by PropagateOneConstraint().
415 std::vector<IntegerValue> max_variations_;
416
417 // For reasons computation. Parallel vectors.
418 std::vector<IntegerLiteral> integer_reason_;
419 std::vector<IntegerValue> reason_coeffs_;
420 std::vector<Literal> literal_reason_;
421
422 // Queue of constraint to propagate.
423 Bitset64<int> in_queue_;
424 std::deque<int> propagation_queue_;
425
426 // This only contain constraint that currently push some bounds.
428
429 // Unenforced constraints are marked as "in_queue_" but not actually added
430 // to the propagation_queue_.
431 int rev_unenforced_size_ = 0;
432 std::vector<int> unenforced_constraints_;
433
434 // Watchers.
437 var_to_constraint_ids_;
438
439 // For an heuristic similar to Tarjan contribution to Bellman-Ford algorithm.
440 // We mark for each variable the last constraint that pushed it, and also keep
441 // the count of propagated variable for each constraint.
442 SparseBitset<IntegerVariable> propagated_by_was_set_;
444 std::vector<int> id_to_propagation_count_;
445
446 // Used by DissasembleSubtreeAndAddToQueue().
447 struct DissasembleQueueEntry {
448 int id;
449 IntegerVariable var;
450 IntegerValue increase;
451 };
452 std::vector<DissasembleQueueEntry> disassemble_queue_;
453 std::vector<DissasembleQueueEntry> disassemble_branch_;
454 std::vector<std::pair<IntegerVariable, IntegerValue>> disassemble_candidates_;
455
456 // This is used to update the deterministic time.
457 int64_t num_terms_for_dtime_update_ = 0;
458
459 // Stats.
460 int64_t num_pushes_ = 0;
461 int64_t num_enforcement_pushes_ = 0;
462 int64_t num_cycles_ = 0;
463 int64_t num_failed_cycles_ = 0;
464 int64_t num_short_reasons_ = 0;
465 int64_t num_long_reasons_ = 0;
466 int64_t num_scanned_ = 0;
467 int64_t num_explored_in_disassemble_ = 0;
468 int64_t num_delayed_ = 0;
469 int64_t num_bool_aborts_ = 0;
470 int64_t num_loop_aborts_ = 0;
471 int64_t num_ignored_ = 0;
472};
473
474} // namespace sat
475} // namespace operations_research
476
477#endif // OR_TOOLS_SAT_LINEAR_PROPAGATION_H_
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.
SatPropagator(const std::string &name)
Definition sat_base.h:535
Simple class to add statistics by name and print them at the end.
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)