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