Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_presolve.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_CP_MODEL_PRESOLVE_H_
15#define ORTOOLS_SAT_CP_MODEL_PRESOLVE_H_
16
17#include <array>
18#include <cstddef>
19#include <cstdint>
20#include <deque>
21#include <string_view>
22#include <utility>
23#include <vector>
24
25#include "absl/base/attributes.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/types/span.h"
29#include "ortools/sat/clause.h"
39#include "ortools/sat/util.h"
42
43namespace operations_research {
44namespace sat {
45
46// Replaces all the instance of a variable i (and the literals referring to it)
47// by mapping[i] in the given cp_model. The definition of variables i is also
48// moved to its new index.
49//
50// If mapping[i] < 0 the variable can be ignored if there are no reference to it
51// at all. If it is not possible (i.e. some field uses it), then we will use a
52// new index for it (at the end) and reverse_mapping will be updated to reflect
53// that. This is the only time we touch reverse_mapping.
54// The image of the mapping should be dense in [0, reverse_mapping->size()).
55//
56// If mapping[i] == mapping[j], the variables will be merged, but it will be the
57// IntegerVariableProto definition of max(i, j) that will be kept in the output.
58// TODO(user): This behavior is not well unit-tested.
59void ApplyVariableMapping(absl::Span<int> mapping, CpModelProto* cp_model,
60 std::vector<int>* reverse_mapping);
61
62// Presolves the initial content of presolved_model.
63//
64// This also creates a mapping model that encode the correspondence between the
65// two problems. This works as follow:
66// - The first variables of mapping_model are in one to one correspondence with
67// the variables of the initial model.
68// - The presolved_model variables are in one to one correspondence with the
69// variable at the indices given by postsolve_mapping in the mapping model.
70// - Fixing one of the two sets of variables and solving the model will assign
71// the other set to a feasible solution of the other problem. Moreover, the
72// objective value of these solutions will be the same. Note that solving such
73// problems will take little time in practice because the propagation will
74// basically do all the work.
75//
76// Note(user): an optimization model can be transformed into a decision problem,
77// if for instance the objective is fixed, or independent from the rest of the
78// problem.
79//
80// TODO(user): Identify disconnected components and returns a vector of
81// presolved model? If we go this route, it may be nicer to store the indices
82// inside the model. We can add a IntegerVariableProto::initial_index;
84 public:
86 std::vector<int>* postsolve_mapping);
87
88 // We returns the status of the problem after presolve:
89 // - UNKNOWN if everything was ok.
90 // - INFEASIBLE if the model was proven so during presolve
91 // - MODEL_INVALID if the model caused some issues, like if we are not able
92 // to scale a floating point objective with enough precision.
94
95 // Executes presolve method for the given constraint. Public for testing only.
96 bool PresolveOneConstraint(int c);
97
98 // Visible for testing.
101 // Detects variable that must take different values.
103
104 private:
105 // A simple helper that logs the rules applied so far and return INFEASIBLE.
106 CpSolverStatus InfeasibleStatus();
107
108 // If there is a large proportion of fixed variables, remap the whole proto
109 // before we start the presolve.
110 bool MaybeRemoveFixedVariables(std::vector<int>* postsolve_mapping);
111
112 // Runs the inner loop of the presolver.
113 bool ProcessChangedVariables(std::vector<bool>* in_queue,
114 std::deque<int>* queue);
115 void PresolveToFixPoint();
116
117 // Runs the probing.
118 void Probe();
119
120 // Runs the expansion and fix constraints that became non-canonical.
121 void ExpandCpModelAndCanonicalizeConstraints();
122
123 // Presolve functions.
124 //
125 // They should return false only if the constraint <-> variable graph didn't
126 // change. This is just an optimization, returning true is always correct.
127 //
128 // Invariant about UNSAT: All these functions should abort right away if
129 // context_.IsUnsat() is true. And the only way to change the status to unsat
130 // is through ABSL_MUST_USE_RESULT function that should also abort right away
131 // the current code. This way we shouldn't keep doing computation on an
132 // inconsistent state.
133 // TODO(user): Make these public and unit test.
134 bool PresolveAllDiff(ConstraintProto* ct);
135 bool PresolveAutomaton(ConstraintProto* ct);
136 bool PresolveElement(int c, ConstraintProto* ct);
137 bool PresolveIntDiv(int c, ConstraintProto* ct);
138 bool PresolveIntMod(int c, ConstraintProto* ct);
139 bool PresolveIntProd(ConstraintProto* ct);
140 bool PresolveInterval(int c, ConstraintProto* ct);
141 bool PresolveInverse(ConstraintProto* ct);
142 bool DivideLinMaxByGcd(int c, ConstraintProto* ct);
143 bool PresolveLinMax(int c, ConstraintProto* ct);
144 bool PresolveLinMaxWhenAllBoolean(ConstraintProto* ct);
145 bool PropagateAndReduceAffineMax(ConstraintProto* ct);
146 bool PropagateAndReduceIntAbs(ConstraintProto* ct);
147 bool PropagateAndReduceLinMax(ConstraintProto* ct);
148 bool PresolveTable(ConstraintProto* ct);
149 void DetectDuplicateIntervals(
150 int c, google::protobuf::RepeatedField<int32_t>* intervals);
151 bool PresolveCumulative(ConstraintProto* ct);
152 bool PresolveNoOverlap(ConstraintProto* ct);
153 bool PresolveNoOverlap2D(int c, ConstraintProto* ct);
154 bool PresolveReservoir(ConstraintProto* ct);
155
156 bool PresolveCircuit(ConstraintProto* ct);
157 bool PresolveRoutes(ConstraintProto* ct);
158
159 bool PresolveAtMostOrExactlyOne(ConstraintProto* ct);
160 bool PresolveAtMostOne(ConstraintProto* ct);
161 bool PresolveExactlyOne(ConstraintProto* ct);
162
163 bool PresolveBoolAnd(ConstraintProto* ct);
164 bool PresolveBoolOr(ConstraintProto* ct);
165 bool PresolveBoolXor(ConstraintProto* ct);
166 bool PresolveEnforcementLiteral(ConstraintProto* ct);
167
168 // Regroups terms and substitute affine relations.
169 // Returns true if the set of variables in the expression changed.
170 bool CanonicalizeLinearExpression(const ConstraintProto& ct,
172 bool CanonicalizeLinearArgument(const ConstraintProto& ct,
173 LinearArgumentProto* proto);
174
175 // For the linear constraints, we have more than one function.
176 ABSL_MUST_USE_RESULT bool CanonicalizeLinear(ConstraintProto* ct,
177 bool* changed);
178 bool PropagateDomainsInLinear(int ct_index, ConstraintProto* ct);
179 bool RemoveSingletonInLinear(ConstraintProto* ct);
180 bool PresolveSmallLinear(ConstraintProto* ct);
181 bool PresolveEmptyLinearConstraint(ConstraintProto* ct);
182 bool PresolveLinearOfSizeOne(ConstraintProto* ct);
183 bool PresolveLinearOfSizeTwo(ConstraintProto* ct);
184 bool PresolveLinearOnBooleans(ConstraintProto* ct);
185 bool PresolveDiophantine(ConstraintProto* ct);
186 bool AddVarAffineRepresentativeFromLinearEquality(int target_index,
187 ConstraintProto* ct);
188 bool PresolveLinearEqualityWithModulo(ConstraintProto* ct);
189 bool PresolveLinear2NeCst(ConstraintProto* ct, int64_t rhs);
190 bool PresolveUnenforcedLinear2EqCst(ConstraintProto* ct, int64_t rhs);
191 bool PresolveEnforcedLinear2EqCst(ConstraintProto* ct, int64_t rhs);
192 bool PresolveLinear2WithBooleans(ConstraintProto* ct);
193
194 // If a constraint is of the form "a * expr_X + expr_Y" and expr_Y can only
195 // take small values compared to a, depending on the bounds, the constraint
196 // can be equivalent to a constraint on expr_X only.
197 //
198 // For instance "10'001 X + 9'999 Y <= 105'000, with X, Y in [0, 100]" can
199 // be rewritten as X + Y <= 10 ! This can easily happen after scaling to
200 // integer cofficient a floating point constraint.
201 void TryToReduceCoefficientsOfLinearConstraint(int c, ConstraintProto* ct);
202
203 // This detects and converts constraints of the form:
204 // "X = sum Boolean * value", with "sum Boolean <= 1".
205 //
206 // Note that it is not super fast, so it shouldn't be called too often.
207 void ExtractEncodingFromLinear();
208 bool ProcessEncodingFromLinear(int linear_encoding_ct_index,
209 const ConstraintProto& at_most_or_exactly_one,
210 int64_t* num_unique_terms,
211 int64_t* num_multiple_terms);
212
213 // Remove duplicate constraints. This also merge domain of linear constraints
214 // with duplicate linear expressions.
215 void DetectDuplicateConstraints();
216 void DetectDuplicateConstraintsWithDifferentEnforcements(
217 const CpModelMapping* mapping = nullptr,
218 BinaryImplicationGraph* implication_graph = nullptr,
219 Trail* trail = nullptr);
220
221 // Detects if a linear constraint is "included" in another one, and do
222 // related presolve.
223 void DetectDominatedLinearConstraints();
224
225 // Detects encodings of the form:
226 // b1 => x \in Domain1
227 // ~b1 => x \in Domain1.Complement()
228 // b2 => x \in Domain2
229 // ~b2 => x \in Domain2.Complement()
230 // b3 => x \in Domain3
231 // ~b3 => x \in Domain3.Complement()
232 // ...
233 // bool_or(b1, b2, ..., bn, y, z, ...)
234
235 // Where the bi do not appear in any other constraints. When we finds this
236 // pattern, we create a new boolean variable `l` and replaces all the
237 // constraints above by three new constraints:
238 // l => x \in Domain1 U Domain2 U ... U Domainn
239 // ~l => x \in (Domain1 U Domain2 U ... U Domainn).Complement()
240 // bool_or(l, y, z, ...),
241 // Note that `l` is equivalent to at least one of the bi to be true, which is
242 // a consequence that it is encoding a domain that is the union of the domains
243 // of the bis.
244 //
245 // It does the same when bool_or is replaced by an at_most_one or exactly_one
246 // but we need to add an extra constraint that
247 // x \notin (Domain_a U Domain_b) for all a != b.
248 void DetectEncodedComplexDomains(PresolveContext* context);
249 bool DetectEncodedComplexDomain(PresolveContext* context, ConstraintProto* ct,
250 const Bitset64<int>& pertinent_bools);
251
252 // Precomputes info about at most one, and use it to presolve linear
253 // constraints. It can be interesting to know for a given linear constraint
254 // that a subset of its variables are in at most one relation.
255 void ProcessAtMostOneAndLinear();
256 void ProcessOneLinearWithAmo(int ct_index, ConstraintProto* ct,
257 ActivityBoundHelper* helper);
258
259 // Presolve a no_overlap_2d constraint where all the non-fixed rectangles are
260 // framed by exactly four fixed rectangles and at most one single box can fit
261 // inside the frame. This is a rather specific situation, but it is fast to
262 // check and happens often in LNS problems.
263 bool PresolveNoOverlap2DFramed(
264 absl::Span<const Rectangle> fixed_boxes,
265 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct);
266
267 // Detects when the space where items of a no_overlap_2d constraint can placed
268 // is disjoint (ie., fixed boxes split the domain). When it is the case, we
269 // can introduce a boolean for each pair <item, component> encoding whether
270 // the item is in the component or not. Then we replace the original
271 // no_overlap_2d constraint by one no_overlap_2d constraint for each
272 // component, with the new booleans as the enforcement_literal of the
273 // intervals. This is equivalent to expanding the original no_overlap_2d
274 // constraint into a bin packing problem with each connected component being a
275 // bin.
276 bool ExpandEncoded2DBinPacking(
277 absl::Span<const Rectangle> fixed_boxes,
278 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct);
279
280 // SetPPC is short for set packing, partitioning and covering constraints.
281 // These are sum of booleans <=, = and >= 1 respectively.
282 // We detect inclusion of these constraint which allows a few simplifications.
283 void ProcessSetPPC();
284
285 // Detect if one constraints has a subset of enforcement of another.
286 void DetectIncludedEnforcement();
287
288 // Removes dominated constraints or fixes some variables for given pair of
289 // setppc constraints included in each other.
290 bool ProcessSetPPCSubset(int subset_c, int superset_c,
291 absl::flat_hash_set<int>* tmp_set,
292 bool* remove_subset, bool* remove_superset,
293 bool* stop_processing_superset);
294
295 // Run SAT specific presolve code.
296 // Returns false on UNSAT.
297 bool PresolvePureSatPart();
298
299 // Extracts AtMostOne constraint from Linear constraint.
300 void ExtractAtMostOneFromLinear(ConstraintProto* ct);
301
302 // Returns true if the constraint changed.
303 bool DivideLinearByGcd(ConstraintProto* ct);
304
305 void ExtractEnforcementLiteralFromLinearConstraint(int ct_index,
306 ConstraintProto* ct);
307 void LowerThanCoeffStrengthening(bool from_lower_bound, int64_t min_magnitude,
308 int64_t rhs, ConstraintProto* ct);
309
310 // Extracts cliques from bool_and and small at_most_one constraints and
311 // transforms them into maximal cliques.
312 void TransformIntoMaxCliques();
313
314 // Checks if there are any clauses that can be transformed to an at most
315 // one constraint.
316 void TransformClausesToExactlyOne();
317
318 // Converts bool_or and at_most_one of size 2 to bool_and.
319 void ConvertToBoolAnd();
320
321 // Sometimes an upper bound on the objective can reduce the domains of many
322 // variables. This "propagates" the objective like a normal linear constraint.
323 bool PropagateObjective();
324
325 // Try to reformulate the objective in term of "base" variables. This is
326 // mainly useful for core based approach where having more terms in the
327 // objective (but with a same trivial lower bound) should help.
328 void ExpandObjective();
329
330 // This makes a big difference on the flatzinc mznc2017_aes_opt* problems.
331 // Where, with this, the core based approach can find small cores and close
332 // them quickly.
333 //
334 // TODO(user): Is it by chance or there is a underlying deep reason? try to
335 // merge this with what ExpandObjective() is doing.
336 void ShiftObjectiveWithExactlyOnes();
337
338 void ProcessVariableOnlyUsedInEncoding(int var);
339 void TryToSimplifyDomain(int var);
340
341 void LookAtVariableWithDegreeTwo(int var);
342 void ProcessVariableInTwoAtMostOrExactlyOne(int var);
343
344 bool MergeCliqueConstraintsHelper(std::vector<std::vector<Literal>>& cliques,
345 std::string_view entry_name,
346 PresolveTimer& timer);
347 bool MergeNoOverlapConstraints();
348 bool MergeNoOverlap2DConstraints();
349
350 // Assumes that all [constraint_index, multiple] in block are linear
351 // constraint that contains multiple * common_part and perform the
352 // substitution.
353 //
354 // Returns false if the substitution cannot be performed because the equation
355 // common_part = new_variable is a linear equation with potential overflow.
356 //
357 // TODO(user): I would be great to change the overflow precondition so that
358 // this cannot happen by maybe taking the rhs into account?
359 bool RemoveCommonPart(
360 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
361 absl::Span<const std::pair<int, int64_t>> block,
362 ActivityBoundHelper* helper);
363
364 // Try to identify many linear constraints that share a common linear
365 // expression. We have two slightly different heuristic.
366 //
367 // TODO(user): consolidate them.
368 void FindAlmostIdenticalLinearConstraints();
369 void FindBigAtMostOneAndLinearOverlap(ActivityBoundHelper* helper);
370 void FindBigHorizontalLinearOverlap(ActivityBoundHelper* helper);
371 void FindBigVerticalLinearOverlap(ActivityBoundHelper* helper);
372
373 // Heuristic to merge clauses that differ in only one literal.
374 // The idea is to regroup a bunch of clauses into a single bool_and.
375 // This serves a bunch of purpose:
376 // - Smaller model.
377 // - Stronger dual reasoning since less locks.
378 // - If the negation of the rhs of the bool_and are in at most one, we will
379 // have a stronger LP relaxation.
380 //
381 // TODO(user): If the merge procedure is successful we might want to develop
382 // a custom propagators for such bool_and. It should in theory be more
383 // efficient than the two watcher literal scheme for clauses. Investigate!
384 void MergeClauses();
385
386 void RunPropagatorsForConstraint(const ConstraintProto& ct);
387
388 // Boths function are responsible for dealing with affine relations.
389 // The second one returns false on UNSAT.
390 void EncodeAllAffineRelations();
391 bool PresolveAffineRelationIfAny(int var);
392
393 bool ExploitEquivalenceRelations(int c, ConstraintProto* ct);
394
395 ABSL_MUST_USE_RESULT bool RemoveConstraint(ConstraintProto* ct);
396 ABSL_MUST_USE_RESULT bool MarkConstraintAsFalse(ConstraintProto* ct,
397 std::string_view reason);
398 ABSL_MUST_USE_RESULT bool MarkOptionalIntervalAsFalse(ConstraintProto* ct);
399
400 std::vector<int>* postsolve_mapping_;
401 PresolveContext* context_;
402 SolutionCrush& solution_crush_;
403 SolverLogger* logger_;
404 TimeLimit* time_limit_;
405
406 // Used by CanonicalizeLinearExpressionInternal().
407 std::vector<std::pair<int, int64_t>> tmp_terms_;
408
409 // Used by DetectAndProcessAtMostOneInLinear().
410 std::vector<std::array<int64_t, 2>> conditional_mins_;
411 std::vector<std::array<int64_t, 2>> conditional_maxs_;
412
413 // Used by ProcessSetPPCSubset() and DetectAndProcessAtMostOneInLinear() to
414 // propagate linear with an at_most_one or exactly_one included inside.
415 absl::flat_hash_map<int, int> temp_map_;
416 absl::flat_hash_set<int> temp_set_;
417 ConstraintProto temp_ct_;
418
419 // Used by RunPropagatorsForConstraint().
420 CpModelProto tmp_model_;
421
422 // Use by TryToReduceCoefficientsOfLinearConstraint().
423 struct RdEntry {
424 int64_t magnitude;
425 int64_t max_variation;
426 int index;
427 };
428 std::vector<RdEntry> rd_entries_;
429 std::vector<int> rd_vars_;
430 std::vector<int64_t> rd_coeffs_;
431 std::vector<int64_t> rd_magnitudes_;
432 std::vector<int64_t> rd_lbs_;
433 std::vector<int64_t> rd_ubs_;
434 std::vector<int64_t> rd_divisors_;
435 MaxBoundedSubsetSum lb_feasible_;
436 MaxBoundedSubsetSum lb_infeasible_;
437 MaxBoundedSubsetSum ub_feasible_;
438 MaxBoundedSubsetSum ub_infeasible_;
439
440 // We have an hash-map of know relation between two variables.
441 // In particular, this will include all known precedences a <= b.
442 //
443 // We reuse an IntegerVariable/IntegerValue based class via
444 // GetLinearExpression2FromProto() only visible in the .cc.
445 BestBinaryRelationBounds known_linear2_;
446
447 struct IntervalConstraintEq {
448 const CpModelProto* working_model;
449 bool operator()(int a, int b) const;
450 };
451
452 struct IntervalConstraintHash {
453 const CpModelProto* working_model;
454 std::size_t operator()(int ct_idx) const;
455 };
456
457 // Used by DetectDuplicateIntervals() and RemoveEmptyConstraints(). Note that
458 // changing the interval constraints of the model will change the hash and
459 // invalidate this hash map.
460 absl::flat_hash_map<int, int, IntervalConstraintHash, IntervalConstraintEq>
461 interval_representative_;
462};
463
464// Convenient wrapper to call the full presolve.
466 std::vector<int>* postsolve_mapping);
467
468// Returns the index of duplicate constraints in the given proto in the first
469// element of each pair. The second element of each pair is the "representative"
470// that is the first constraint in the proto in a set of duplicate constraints.
471//
472// Empty constraints are ignored. We also do a bit more:
473// - We ignore names when comparing constraint.
474// - For linear constraints, we ignore the domain. This is because we can
475// just merge them if the constraints are the same.
476// - We return the special kObjectiveConstraint (< 0) representative if a linear
477// constraint is parallel to the objective and has no enforcement literals.
478// The domain of such constraint can just be merged with the objective domain.
479//
480// If ignore_enforcement is true, we ignore enforcement literal, but do not
481// do the linear domain or objective special cases. This allow to cover some
482// other cases like:
483// - enforced constraint duplicate of non-enforced one.
484// - Two enforced constraints with singleton enforcement (vpphard).
485//
486// Visible here for testing. This is meant to be called at the end of the
487// presolve where constraints have been canonicalized.
488std::vector<std::pair<int, int>> FindDuplicateConstraints(
489 const CpModelProto& model_proto, bool ignore_enforcement = false);
490
491} // namespace sat
492} // namespace operations_research
493
494#endif // ORTOOLS_SAT_CP_MODEL_PRESOLVE_H_
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto, bool ignore_enforcement)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
void ApplyVariableMapping(absl::Span< int > mapping, CpModelProto *cp_model, std::vector< int > *reverse_mapping)
OR-Tools root namespace.