Google OR-Tools v9.15
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 ORTOOLS_SAT_LINEAR_PROPAGATION_H_
15#define ORTOOLS_SAT_LINEAR_PROPAGATION_H_
16
17#include <stdint.h>
18
19#include <deque>
20#include <functional>
21#include <limits>
22#include <string>
23#include <utility>
24#include <vector>
25
26#include "absl/base/attributes.h"
27#include "absl/container/inlined_vector.h"
28#include "absl/log/check.h"
29#include "absl/types/span.h"
33#include "ortools/sat/integer.h"
35#include "ortools/sat/model.h"
39#include "ortools/sat/util.h"
40#include "ortools/util/bitset.h"
41#include "ortools/util/rev.h"
44
45namespace operations_research {
46namespace sat {
47
48// Helper class to decide on the constraint propagation order.
49//
50// Each constraint might push some variables which might in turn make other
51// constraint tighter. In general, it seems better to make sure we push first
52// constraints that are not affected by other variables and delay the
53// propagation of constraint that we know will become tigher. This also likely
54// simplifies the reasons.
55//
56// Note that we can have cycle in this graph, and that this is not necessarily a
57// conflict.
59 public:
61 ModelRandomGenerator* random, TimeLimit* time_limit,
62 std::function<absl::Span<const IntegerVariable>(int)> id_to_vars)
63 : random_(random),
64 time_limit_(time_limit),
65 id_to_vars_func_(std::move(id_to_vars)) {}
66
67 void Resize(int num_vars, int num_ids) {
68 var_has_entry_.Resize(IntegerVariable(num_vars));
69 var_to_id_.resize(num_vars);
70 var_to_lb_.resize(num_vars);
71 var_to_pos_.resize(num_vars);
72
73 in_ids_.resize(num_ids);
74 }
75
76 void Register(int id, IntegerVariable var, IntegerValue lb) {
77 DCHECK_LT(id, in_ids_.size());
78 DCHECK_LT(var.value(), var_to_id_.size());
79 if (var_has_entry_[var]) {
80 if (var_to_lb_[var] >= lb) return;
81 } else {
82 var_has_entry_.Set(var);
83 var_to_pos_[var] = to_clear_.size();
84 to_clear_.push_back(var);
85 }
86 var_to_lb_[var] = lb;
87 var_to_id_[var] = id;
88 if (!in_ids_[id]) {
89 in_ids_.Set(id);
90
91 // All new ids are likely not in a cycle, we want to test them first.
92 ids_.push_front(id);
93 }
94 }
95
96 void Clear() {
97 for (const IntegerVariable var : to_clear_) {
98 var_has_entry_.Clear(var);
99 }
100 to_clear_.clear();
101 for (const int id : ids_) {
102 in_ids_.Clear(id);
103 }
104 ids_.clear();
105 }
106
107 // Return -1 if there is none.
108 // This returns a constraint with min degree.
109 //
110 // TODO(user): fix quadratic or even linear algo? We can use
111 // var_to_ids_func_() to maintain the degree. But note that since we reorder
112 // constraints and because we expect mainly degree zero, this seems to be
113 // faster.
114 int NextId() {
115 if (ids_.empty()) return -1;
116
117 int best_id = 0;
118 int best_num_vars = 0;
119 int best_degree = std::numeric_limits<int>::max();
120 int64_t work_done = 0;
121 const int size = ids_.size();
122 const auto var_has_entry = var_has_entry_.const_view();
123 for (int i = 0; i < size; ++i) {
124 const int id = ids_.front();
125 ids_.pop_front();
126 DCHECK(in_ids_[id]);
127
128 // By degree, we mean the number of variables of the constraint that do
129 // not have yet their lower bounds up to date; they will be pushed by
130 // other constraints as we propagate them. If possible, we want to delay
131 // the propagation of a constraint with positive degree until all involved
132 // lower bounds are up to date (i.e. degree == 0).
133 int degree = 0;
134 absl::Span<const IntegerVariable> vars = id_to_vars_func_(id);
135 work_done += vars.size();
136 for (const IntegerVariable var : vars) {
137 if (var_has_entry[var]) {
138 if (var_has_entry[NegationOf(var)] &&
139 var_to_id_[NegationOf(var)] == id) {
140 // We have two constraints, this one (id) push NegationOf(var), and
141 // var_to_id_[var] push var. So whichever order we choose, the first
142 // constraint will need to be scanned at least twice. Lets not count
143 // this situation in the degree.
144 continue;
145 }
146
147 DCHECK_NE(var_to_id_[var], id);
148 ++degree;
149 }
150 }
151
152 // We select the min-degree and prefer lower constraint size.
153 // We however stop at the first degree zero.
154 if (degree < best_degree ||
155 (degree == best_degree && vars.size() < best_num_vars)) {
156 best_degree = degree;
157 best_num_vars = vars.size();
158 best_id = id;
159 if (best_degree == 0) {
160 in_ids_.Clear(best_id);
161 return best_id;
162 }
163 }
164
165 ids_.push_back(id);
166 }
167
168 if (work_done > 100) {
169 time_limit_->AdvanceDeterministicTime(static_cast<double>(work_done) *
170 5e-9);
171 }
172
173 // We didn't find any degree zero, we scanned the whole queue.
174 // Extract best_id while keeping the order stable.
175 //
176 // We tried to randomize the order, it does add more variance but also seem
177 // worse overall.
178 int new_size = 0;
179 for (const int id : ids_) {
180 if (id == best_id) continue;
181 ids_[new_size++] = id;
182 }
183 ids_.resize(new_size);
184 in_ids_.Clear(best_id);
185 return best_id;
186 }
187
188 void UpdateBound(IntegerVariable var, IntegerValue lb) {
189 if (!var_has_entry_[var]) return;
190 if (lb < var_to_lb_[var]) return;
191
192 var_has_entry_.Clear(var);
193 const int pos = var_to_pos_[var];
194 to_clear_[pos] = to_clear_.back();
195 var_to_pos_[to_clear_.back()] = pos;
196 to_clear_.pop_back();
197 }
198
199 bool IsEmpty() const { return ids_.empty(); }
200
201 bool VarShouldBePushedById(IntegerVariable var, int id) {
202 if (!var_has_entry_[var]) return false;
203 if (var_to_id_[var] != id) return false;
204 return true;
205 }
206
207 public:
210 std::function<absl::Span<const IntegerVariable>(int)> id_to_vars_func_;
211
212 // For each variable we only keep the constraint id that pushes it further.
213 // In case of tie, we only keep the first to be registered.
218 std::vector<IntegerVariable> to_clear_;
219
220 // Set/queue of constraints to be propagated.
221 int start_ = 0;
223 std::deque<int> ids_;
224};
225
226// This is meant to supersede both IntegerSumLE and the PrecedencePropagator.
227//
228// TODO(user): This is a work in progress and is currently incomplete:
229// - Lack more incremental support for faster propag.
230// - Lack detection and propagation of at least one of these linear is true
231// which can be used to propagate more bound if a variable appear in all these
232// constraint.
236 public:
237 explicit LinearPropagator(Model* model);
238 ~LinearPropagator() override;
239 bool Propagate() final;
240 void SetLevel(int level) final;
241
242 std::string LazyReasonName() const override { return "LinearPropagator"; }
243
244 // Adds a new constraint to the propagator.
245 // We support adding constraint at a positive level:
246 // - This will push new propagation right away.
247 // - This will returns false if the constraint is currently a conflict.
248 bool AddConstraint(absl::Span<const Literal> enforcement_literals,
249 absl::Span<const IntegerVariable> vars,
250 absl::Span<const IntegerValue> coeffs,
251 IntegerValue upper_bound);
252
253 // For LazyReasonInterface.
254 void Explain(int id, IntegerLiteral to_explain, IntegerReason* reason) final;
255
257 push_affine_ub_for_binary_relations_ = true;
258 }
259
260 private:
261 // We try to pack the struct as much as possible. Using a maximum size of
262 // 1 << 29 should be okay since we split long constraint anyway. Technically
263 // we could use int16_t or even int8_t if we wanted, but we just need to make
264 // sure we do split ALL constraints, not just the one from the initial model.
265 //
266 // TODO(user): We could also move some less often used fields out. like
267 // initial size and enf_id that are only needed when we push something.
268 struct ConstraintInfo {
269 unsigned int enf_status : 2;
270 // With Visual Studio or minGW, using bool here breaks the struct packing.
271 unsigned int all_coeffs_are_one : 1;
272 unsigned int initial_size : 29; // Const. The size including all terms.
273
274 EnforcementId enf_id; // Const. The id in enforcement_propagator_.
275 int start; // Const. The start of the constraint in the buffers.
276 int rev_size; // The size of the non-fixed terms.
277 IntegerValue rev_rhs; // The current rhs, updated on fixed terms.
278 };
279
280 static_assert(sizeof(ConstraintInfo) == 24,
281 "ERROR_ConstraintInfo_is_not_well_compacted");
282
283 absl::Span<IntegerValue> GetCoeffs(const ConstraintInfo& info);
284 absl::Span<IntegerVariable> GetVariables(const ConstraintInfo& info);
285
286 // Called when the lower bound of a variable changed. The id is the constraint
287 // id that caused this change or -1 if it comes from an external source.
288 void OnVariableChange(IntegerVariable var, IntegerValue lb, int id);
289
290 // Returns false on conflict.
291 ABSL_MUST_USE_RESULT bool PropagateOneConstraint(int id);
292 ABSL_MUST_USE_RESULT bool PropagateInfeasibleConstraint(int id,
293 IntegerValue slack);
294 ABSL_MUST_USE_RESULT bool ReportConflictingCycle();
295 ABSL_MUST_USE_RESULT bool DisassembleSubtree(int root_id, int num_tight);
296
297 // This loops over the given constraint and returns the coefficient of var and
298 // NegationOf(next_var). Both should be non-zero (Checked).
299 //
300 // If the slack of this constraint was tight for next_var, then pushing var
301 // will push next_var again depending on these coefficients.
302 std::pair<IntegerValue, IntegerValue> GetCycleCoefficients(
303 int id, IntegerVariable var, IntegerVariable next_var);
304
305 // Returns (slack, num_to_push) of the given constraint.
306 // If slack < 0 we have a conflict or might push the enforcement.
307 // If slack >= 0 the first num_to_push variables can be pushed.
308 std::pair<IntegerValue, int> AnalyzeConstraint(int id);
309
310 void ClearPropagatedBy();
311 void CanonicalizeConstraint(int id);
312 void AddToQueueIfNeeded(int id);
313 void SetPropagatedBy(IntegerVariable var, int id);
314 std::string ConstraintDebugString(int id);
315
316 // External class needed.
317 Trail* trail_;
318 IntegerTrail* integer_trail_;
319 EnforcementPropagator* enforcement_propagator_;
320 EnforcementHelper* enforcement_helper_;
321 GenericLiteralWatcher* watcher_;
322 TimeLimit* time_limit_;
323 RevIntRepository* rev_int_repository_;
324 RevIntegerValueRepository* rev_integer_value_repository_;
325 EnforcedLinear2Bounds* precedences_;
326 Linear2BoundsFromLinear3* linear3_bounds_;
327 ModelRandomGenerator* random_;
328 SharedStatistics* shared_stats_ = nullptr;
329 const int watcher_id_;
330
331 bool push_affine_ub_for_binary_relations_ = false;
332
333 // To know when we backtracked. See SetLevel().
334 int previous_level_ = 0;
335
336 // The key to our incrementality. This will be cleared once the propagation
337 // is done, and automatically updated by the integer_trail_ with all the
338 // IntegerVariable that changed since the last clear.
339 SparseBitset<IntegerVariable> modified_vars_;
340
341 // Per constraint info used during propagation. Note that we keep pointer for
342 // the rev_size/rhs there, so we do need a deque.
343 std::deque<ConstraintInfo> infos_;
344 std::vector<IntegerValue> initial_rhs_;
345
346 // Buffer of the constraints data.
347 std::vector<IntegerVariable> variables_buffer_;
348 std::vector<IntegerValue> coeffs_buffer_;
349 std::vector<IntegerValue> buffer_of_ones_;
350
351 // Filled by PropagateOneConstraint().
352 std::vector<IntegerValue> max_variations_;
353
354 // For reasons computation. Parallel vectors.
355 std::vector<IntegerLiteral> integer_reason_;
356 std::vector<IntegerValue> reason_coeffs_;
357 std::vector<Literal> literal_reason_;
358
359 // Queue of constraint to propagate.
360 Bitset64<int> in_queue_;
361 std::deque<int> propagation_queue_;
362
363 // This only contain constraint that currently push some bounds.
364 ConstraintPropagationOrder order_;
365
366 // Unenforced constraints are marked as "in_queue_" but not actually added
367 // to the propagation_queue_.
368 int rev_unenforced_size_ = 0;
369 std::vector<int> unenforced_constraints_;
370
371 // Watchers.
374 var_to_constraint_ids_;
375
376 // For an heuristic similar to Tarjan contribution to Bellman-Ford algorithm.
377 // We mark for each variable the last constraint that pushed it, and also keep
378 // the count of propagated variable for each constraint.
379 SparseBitset<IntegerVariable> propagated_by_was_set_;
381 std::vector<int> id_to_propagation_count_;
382
383 // Used by DissasembleSubtreeAndAddToQueue().
384 struct DissasembleQueueEntry {
385 int id;
386 IntegerVariable var;
387 IntegerValue increase;
388 };
389 std::vector<DissasembleQueueEntry> disassemble_queue_;
390 std::vector<DissasembleQueueEntry> disassemble_branch_;
391 std::vector<std::pair<IntegerVariable, IntegerValue>> disassemble_candidates_;
392
393 // This is used to update the deterministic time.
394 int64_t num_terms_for_dtime_update_ = 0;
395
396 // Stats.
397 int64_t num_pushes_ = 0;
398 int64_t num_enforcement_pushes_ = 0;
399 int64_t num_cycles_ = 0;
400 int64_t num_failed_cycles_ = 0;
401 int64_t num_short_reasons_ = 0;
402 int64_t num_long_reasons_ = 0;
403 int64_t num_scanned_ = 0;
404 int64_t num_explored_in_disassemble_ = 0;
405 int64_t num_delayed_ = 0;
406 int64_t num_bool_aborts_ = 0;
407 int64_t num_loop_aborts_ = 0;
408 int64_t num_ignored_ = 0;
409};
410
411} // namespace sat
412} // namespace operations_research
413
414#endif // ORTOOLS_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_
util_intops::StrongVector< IntegerVariable, int > var_to_pos_
void Register(int id, IntegerVariable var, IntegerValue lb)
bool AddConstraint(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coeffs, IntegerValue upper_bound)
void Explain(int id, IntegerLiteral to_explain, IntegerReason *reason) final
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
OR-Tools root namespace.
STL namespace.