Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
probing.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_PROBING_H_
15#define ORTOOLS_SAT_PROBING_H_
16
17#include <cstdint>
18#include <functional>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/container/btree_map.h"
24#include "absl/container/btree_set.h"
25#include "absl/container/flat_hash_map.h"
26#include "absl/strings/str_cat.h"
27#include "absl/strings/string_view.h"
28#include "absl/types/span.h"
30#include "ortools/sat/clause.h"
32#include "ortools/sat/integer.h"
35#include "ortools/sat/model.h"
38#include "ortools/sat/util.h"
39#include "ortools/util/bitset.h"
42
43namespace operations_research {
44namespace sat {
45
46class TrailCopy;
47
48class Prober {
49 public:
50 explicit Prober(Model* model);
51 ~Prober();
52
53 // Fixes Booleans variables to true/false and see what is propagated. This
54 // can:
55 //
56 // - Fix some Boolean variables (if we reach a conflict while probing).
57 //
58 // - Infer new direct implications. We add them directly to the
59 // BinaryImplicationGraph and they can later be used to detect equivalent
60 // literals, expand at most ones clique, etc...
61 //
62 // - Tighten the bounds of integer variables. If we probe the two possible
63 // values of a Boolean (b=0 and b=1), we get for each integer variables two
64 // propagated domain D_0 and D_1. The level zero domain can then be
65 // intersected with D_0 U D_1. This can restrict the lower/upper bounds of a
66 // variable, but it can also create holes in the domain! This will detect
67 // common cases like an integer variable in [0, 10] that actually only take
68 // two values [0] or [10] depending on one Boolean.
69 //
70 // Returns false if the problem was proved INFEASIBLE during probing.
71 //
72 // TODO(user): For now we process the Boolean in their natural order, this is
73 // not the most efficient.
74 //
75 // TODO(user): This might generate a lot of new direct implications. We might
76 // not want to add them directly to the BinaryImplicationGraph and could
77 // instead use them directly to detect equivalent literal like in
78 // ProbeAndFindEquivalentLiteral(). The situation is not clear.
79 //
80 // TODO(user): More generally, we might want to register any literal => bound
81 // in the IntegerEncoder. This would allow to remember them and use them in
82 // other part of the solver (cuts, lifting, ...).
83 //
84 // TODO(user): Rename to include Integer in the name and distinguish better
85 // from FailedLiteralProbing() below.
86 bool ProbeBooleanVariables(double deterministic_time_limit);
87
88 // Same as above method except it probes only on the variables given in
89 // 'bool_vars'.
90 bool ProbeBooleanVariables(double deterministic_time_limit,
91 absl::Span<const BooleanVariable> bool_vars);
92
93 bool ProbeOneVariable(BooleanVariable b);
94
95 // Probes the given problem DNF (disjunction of conjunctions). Since one of
96 // the conjunction must be true, we might be able to fix literal or improve
97 // integer bounds if all conjunction propagate the same thing.
98 enum DnfType {
99 // DNF is an existing clause 'dnf_clause' = (l1) OR ... (ln), minus its
100 // literals which are already assigned.
102 // DNF is the tautology "either at least one of n literals is true, or all
103 // of them are false": (l1) OR ... (ln) OR (not(l1) AND ... not(ln)). The
104 // single literal conjunctions must be listed first.
106 // DNF is the tautology "one of the 2^n possible assignments of n Boolean
107 // variables is true". The n variables must be in the same order in each
108 // conjunction, and their assignment in the i-th conjunction must be the
109 // binary representation of i. For instance, if the variables are b0 and b1,
110 // the conjunctions must be (not(b0) AND not(b1)), (not(b0) AND b1),
111 // (b0 AND not(b1)), and (b0 AND b1), in this order.
113 };
114 bool ProbeDnf(absl::string_view name,
115 absl::Span<const std::vector<Literal>> dnf, DnfType type,
116 const SatClause* dnf_clause = nullptr);
117
118 // Statistics.
119 // They are reset each time ProbleBooleanVariables() is called.
120 // Note however that we do not reset them on a call to ProbeOneVariable().
121 int num_decisions() const { return num_decisions_; }
122 int num_new_literals_fixed() const { return num_new_literals_fixed_; }
123 int num_new_binary_clauses() const { return num_new_binary_; }
124
125 // Register a callback that will be called on each "propagation".
126 // One can inspect the VariablesAssignment to see what are the inferred
127 // literals.
128 void SetPropagationCallback(std::function<void(Literal decision)> f) {
129 callback_ = f;
130 }
131
132 private:
133 bool ProbeOneVariableInternal(BooleanVariable b);
134
135 // Computes the LRAT proofs that all the `propagated_literals` can be fixed to
136 // true, and fixes them.
137 bool FixProbedDnfLiterals(
138 absl::Span<const std::vector<Literal>> dnf,
139 const absl::btree_set<LiteralIndex>& propagated_literals, DnfType type,
140 ClauseId dnf_clause_id, absl::Span<const Literal> dnf_clause_literals);
141
142 // Computes the LRAT proof that `propagated_lit` can be fixed to true, and
143 // fixes it. `conjunctions` must have the property described for
144 // DnfType::kAtLeastOneCombination. `clause_ids` must contain the IDs of the
145 // LRAT clauses "conjunctions[i] => propagated_lit" (some IDs can be
146 // kNoClauseId, if a conjunction contains `propagated_lit`). Deletes all
147 // `clause_ids` and replaces these IDs with kNoClauseId values.
148 bool FixLiteralImpliedByAnAtLeastOneCombinationDnf(
149 absl::Span<const std::vector<Literal>> conjunctions,
150 absl::Span<ClauseId> clause_ids, Literal propagated_lit);
151
152 // Model owned classes.
153 const Trail& trail_;
154 const VariablesAssignment& assignment_;
155 IntegerTrail* integer_trail_;
156 ImpliedBounds* implied_bounds_;
157 ProductDetector* product_detector_;
158 SatSolver* sat_solver_;
159 TimeLimit* time_limit_;
160 BinaryImplicationGraph* implication_graph_;
161 ClauseManager* clause_manager_;
162 ClauseIdGenerator* clause_id_generator_;
163 LratProofHandler* lrat_proof_handler_;
164 TrailCopy* trail_copy_;
165 const bool drat_enabled_;
166
167 // To detect literal x that must be true because b => x and not(b) => x.
168 // When probing b, we add all propagated literal to propagated, and when
169 // probing not(b) we check if any are already there.
170 SparseBitset<LiteralIndex> propagated_;
171
172 // Modifications found during probing.
173 std::vector<Literal> to_fix_at_true_;
174 std::vector<IntegerLiteral> new_integer_bounds_;
175 std::vector<Literal> new_literals_implied_by_decision_;
176 std::vector<Literal> new_implied_or_fixed_literals_;
177 absl::btree_set<LiteralIndex> new_propagated_literals_;
178 absl::btree_set<LiteralIndex> always_propagated_literals_;
179 absl::btree_map<IntegerVariable, IntegerValue> new_propagated_bounds_;
180 absl::btree_map<IntegerVariable, IntegerValue> always_propagated_bounds_;
181
182 absl::flat_hash_map<std::pair<Literal, Literal>, ClauseId>
183 tmp_binary_clause_ids_;
184 std::vector<ClauseId> tmp_clause_ids_;
185 std::vector<Literal> tmp_literals_;
186 CompactVectorVector<int, ClauseId> tmp_dnf_clause_ids_;
187
188 // Probing statistics.
189 int num_decisions_ = 0;
190 int num_new_holes_ = 0;
191 int num_new_binary_ = 0;
192 int num_new_integer_bounds_ = 0;
193 int num_new_literals_fixed_ = 0;
194 int num_lrat_clauses_ = 0;
195 int num_lrat_proof_clauses_ = 0;
196 int num_unneeded_lrat_clauses_ = 0;
197
198 std::function<void(Literal decision)> callback_ = nullptr;
199
200 // Logger.
201 SolverLogger* logger_;
202};
203
204// Try to randomly tweak the search and stop at the first conflict each time.
205// This can sometimes find feasible solution, but more importantly, it is a form
206// of probing that can sometimes find small and interesting conflicts or fix
207// variables. This seems to work well on the SAT14/app/rook-* problems and
208// do fix more variables if run before probing.
209//
210// If a feasible SAT solution is found (i.e. all Boolean assigned), then this
211// abort and leave the solver with the full solution assigned.
212//
213// Returns false iff the problem is UNSAT.
214bool LookForTrivialSatSolution(double deterministic_time_limit, Model* model,
215 SolverLogger* logger);
216
217// Options for the FailedLiteralProbing() code below.
218//
219// A good reference for the algorithms involved here is the paper "Revisiting
220// Hyper Binary Resolution" Marijn J. H. Heule, Matti Jarvisalo, Armin Biere,
221// http://www.cs.utexas.edu/~marijn/cpaior2013.pdf
223 // The probing will consume all this deterministic time or stop if nothing
224 // else can be deduced and everything has been probed until fix-point. The
225 // fix point depend on the extract_binay_clauses option:
226 // - If false, we will just stop when no more failed literal can be found.
227 // - If true, we will do more work and stop when all failed literal have been
228 // found and all hyper binary resolution have been performed.
229 //
230 // TODO(user): We can also provide a middle ground and probe all failed
231 // literal but do not extract all binary clauses.
232 //
233 // Note that the fix-point is unique, modulo the equivalent literal detection
234 // we do. And if we add binary clauses, modulo the transitive reduction of the
235 // binary implication graph.
236 //
237 // To be fast, we only use the binary clauses in the binary implication graph
238 // for the equivalence detection. So the power of the equivalence detection
239 // changes if the extract_binay_clauses option is true or not.
240 //
241 // TODO(user): The fix point is not yet reached since we don't currently
242 // simplify non-binary clauses with these equivalence, but we will.
244
245 // This is also called hyper binary resolution. Basically, we make sure that
246 // the binary implication graph is augmented with all the implication of the
247 // form a => b that can be derived by fixing 'a' at level zero and doing a
248 // propagation using all constraints. Note that we only add clauses that
249 // cannot be derived by the current implication graph.
250 //
251 // With these extra clause the power of the equivalence literal detection
252 // using only the binary implication graph with increase. Note that it is
253 // possible to do exactly the same thing without adding these binary clause
254 // first. This is what is done by yet another probing algorithm (currently in
255 // simplification.cc).
256 //
257 // TODO(user): Note that adding binary clause before/during the SAT presolve
258 // is currently not always a good idea. This is because we don't simplify the
259 // other clause as much as we could. Also, there can be up to a quadratic
260 // number of clauses added this way, which might slow down things a lot. But
261 // then because of the deterministic limit, we usually cannot add too much
262 // clauses, even for huge problems, since we will reach the limit before that.
264
265 // Use a version of the "Tree look" algorithm as explained in the paper above.
266 // This is usually faster and more efficient. Note that when extracting binary
267 // clauses it might currently produce more "redundant" one in the sense that a
268 // transitive reduction of the binary implication graph after all hyper binary
269 // resolution have been performed may need to do more work.
270 bool use_tree_look = true;
271
272 // There is two slightly different implementation of the tree-look algo.
273 //
274 // TODO(user): Decide which one is better, currently the difference seems
275 // small but the queue seems slightly faster.
276 bool use_queue = true;
277
278 // If we detect as we probe that a new binary clause subsumes one of the
279 // non-binary clause, we will replace the long clause by the binary one. This
280 // is orthogonal to the extract_binary_clauses parameters which will add all
281 // binary clauses but not necessarily check for subsumption.
283
284 // We assume this is also true if --v 1 is activated.
285 bool log_info = false;
286
287 std::string ToString() const {
288 return absl::StrCat("deterministic_limit: ", deterministic_limit,
289 " extract_binary_clauses: ", extract_binary_clauses,
290 " use_tree_look: ", use_tree_look,
291 " use_queue: ", use_queue);
292 }
293};
294
295// Similar to ProbeBooleanVariables() but different :-)
296//
297// First, this do not consider integer variable. It doesn't do any disjunctive
298// reasoning (i.e. changing the domain of an integer variable by intersecting
299// it with the union of what happen when x is fixed and not(x) is fixed).
300//
301// However this should be more efficient and just work better for pure Boolean
302// problems. On integer problems, we might also want to run this one first,
303// and then do just one quick pass of ProbeBooleanVariables().
304//
305// Note that this by itself just do one "round", look at the code in the
306// Inprocessing class that call this interleaved with other reductions until a
307// fix point is reached.
308//
309// This can fix a lot of literals via failed literal detection, that is when
310// we detect that x => not(x) via propagation after taking x as a decision. It
311// also use the strongly connected component algorithm to detect equivalent
312// literals.
313//
314// It will add any detected binary clause (via hyper binary resolution) to
315// the implication graph. See the option comments for more details.
317 public:
318 explicit FailedLiteralProbing(Model* model);
319
320 bool DoOneRound(ProbingOptions options);
321
322 private:
323 struct SavedNextLiteral {
324 LiteralIndex literal_index; // kNoLiteralIndex if we need to backtrack.
325 int rank; // Cached position_in_order, we prefer lower positions.
326
327 bool operator<(const SavedNextLiteral& o) const { return rank < o.rank; }
328 };
329
330 // Returns true if we can skip this candidate decision.
331 // This factor out some code used by the functions below.
332 bool SkipCandidate(Literal last_decision, Literal candidate);
333
334 // Sets `next_decision` to the unassigned literal which implies the last
335 // decision and which comes first in the probing order (which itself can be
336 // the topological order of the implication graph, or the reverse).
337 bool ComputeNextDecisionInOrder(LiteralIndex& next_decision);
338
339 // Sets `next_decision` to the first unassigned literal we find which implies
340 // the last decision, in no particular order.
341 bool GetNextDecisionInNoParticularOrder(LiteralIndex& next_decision);
342
343 // Sets `next_decision` to the first unassigned literal in probing_order (if
344 // there is no last decision we can use any literal as first decision).
345 bool GetFirstDecision(LiteralIndex& next_decision);
346
347 // Enqueues `next_decision`. Backjumps and sets `next_decision` to false in
348 // case of conflict. Returns false if the problem was proved UNSAT.
349 bool EnqueueDecisionAndBackjumpOnConflict(LiteralIndex next_decision,
350 bool use_queue,
351 int& first_new_trail_index);
352
353 // If we can extract a binary clause that subsume the reason clause, we do add
354 // the binary and remove the subsumed clause.
355 //
356 // TODO(user): We could be slightly more generic and subsume some clauses that
357 // do not contain last_decision.Negated().
358 void MaybeSubsumeWithBinaryClause(Literal last_decision, Literal l);
359
360 // Functions to add "last_decision => l" to the repository if not already
361 // done. The Maybe() version just calls Extract() if ShouldExtract() is true.
362 bool ShouldExtractImplication(Literal l);
363 void ExtractImplication(Literal last_decision, Literal l,
364 bool lrat_only = false);
365 void MaybeExtractImplication(Literal last_decision, Literal l);
366
367 // Extracts an implication "`last_decision` => l" for each literal l in
368 // `literals`. This is more efficient than calling ExtractImplication() for
369 // each literal when LRAT is enabled.
370 void ExtractImplications(Literal last_decision,
371 absl::Span<const Literal> literals);
372
373 // Inspect the watcher list for last_decision, If we have a blocking
374 // literal at true (implied by last decision), then we have subsumptions.
375 //
376 // The intuition behind this is that if a binary clause (a,b) subsume a
377 // clause, and we watch a.Negated() for this clause with a blocking
378 // literal b, then this watch entry will never change because we always
379 // propagate binary clauses first and the blocking literal will always be
380 // true. So after many propagations, we hope to have such configuration
381 // which is quite cheap to test here.
382 void SubsumeWithBinaryClauseUsingBlockingLiteral(Literal last_decision);
383
384 // Adds 'not(literal)' to `to_fix_`, assuming that 'literal' directly implies
385 // the current decision, which itself implies all the previous decisions, with
386 // some of them propagating 'not(literal)'.
387 void AddFailedLiteralToFix(Literal literal);
388
389 // Fixes all the literals in to_fix_, and finish propagation.
390 bool ProcessLiteralsToFix();
391
392 // Deletes the temporary LRAT clauses in trail_implication_clauses_ for all
393 // trail indices greater than the current trail index.
394 void DeleteTemporaryLratImplicationsAfterBacktrack();
395
396 SatSolver* sat_solver_;
397 BinaryImplicationGraph* implication_graph_;
398 TimeLimit* time_limit_;
399 const Trail& trail_;
400 const VariablesAssignment& assignment_;
401 ClauseManager* clause_manager_;
402 ClauseIdGenerator* clause_id_generator_;
403 LratProofHandler* lrat_proof_handler_;
404 int binary_propagator_id_;
405 int clause_propagator_id_;
406
407 int num_variables_;
408 std::vector<LiteralIndex> probing_order_;
409 int order_index_ = 0;
411
412 // This is only needed when options.use_queue is true.
413 std::vector<SavedNextLiteral> queue_;
415
416 // This is only needed when options use_queue is false;
418
419 // We delay fixing of already assigned literals once we go back to level 0.
420 std::vector<Literal> to_fix_;
421 // For each literal in to_fix_, the ID of the corresponding LRAT unit clause.
422 std::vector<ClauseId> to_fix_unit_id_;
423 // The literals for which we want to extract "last_decision => l" clauses.
424 std::vector<Literal> binary_clauses_to_extract_;
425
426 // For each literal 'l' in the trail, whether a binary clause "d => l" has
427 // been extracted, with 'd' the decision at the same level as 'l'.
428 std::vector<bool> binary_clause_extracted_;
429
430 // For each literal on the trail, the ID of the LRAT clause stating that this
431 // literal is implied by the previous decisions on the trail (or kNoClauseId
432 // if there is no such clause), plus a Boolean indicating whether this clause
433 // is temporary (i.e., is not an extracted binary clause).
434 std::vector<std::pair<ClauseId, bool>> trail_implication_clauses_;
435
436 // Temporary data structures used for LRAT proofs.
437 std::vector<ClauseId> tmp_clause_ids_;
439 std::vector<int> tmp_heap_;
440 std::vector<Literal> tmp_marked_literals_;
441
442 // Stats.
443 int64_t num_probed_ = 0;
444 int64_t num_explicit_fix_ = 0;
445 int64_t num_conflicts_ = 0;
446 int64_t num_new_binary_ = 0;
447 int64_t num_subsumed_ = 0;
448 int64_t num_lrat_clauses_ = 0;
449 int64_t num_lrat_proof_clauses_ = 0;
450};
451
452} // namespace sat
453} // namespace operations_research
454
455#endif // ORTOOLS_SAT_PROBING_H_
Definition model.h:345
bool DoOneRound(ProbingOptions options)
Definition probing.cc:1186
bool ProbeBooleanVariables(double deterministic_time_limit)
Definition probing.cc:284
bool ProbeOneVariable(BooleanVariable b)
Definition probing.cc:536
void SetPropagationCallback(std::function< void(Literal decision)> f)
Definition probing.h:128
bool ProbeDnf(absl::string_view name, absl::Span< const std::vector< Literal > > dnf, DnfType type, const SatClause *dnf_clause=nullptr)
Definition probing.cc:653
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model, SolverLogger *logger)
Definition probing.cc:1084
OR-Tools root namespace.