Google OR-Tools v9.14
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] in the working model. The definition of variables i is also
46// moved to its new index. If mapping[i] < 0 the variable can be ignored if
47// possible. If it is not possible, then we will use a new index for it (at the
48// end) and the mapping will be updated to reflect that.
49//
50// The image of the mapping should be dense in [0, reverse_mapping->size()).
51void ApplyVariableMapping(PresolveContext* context, absl::Span<int> mapping,
52 std::vector<int>* reverse_mapping);
53
54// Presolves the initial content of presolved_model.
55//
56// This also creates a mapping model that encode the correspondence between the
57// two problems. This works as follow:
58// - The first variables of mapping_model are in one to one correspondence with
59// the variables of the initial model.
60// - The presolved_model variables are in one to one correspondence with the
61// variable at the indices given by postsolve_mapping in the mapping model.
62// - Fixing one of the two sets of variables and solving the model will assign
63// the other set to a feasible solution of the other problem. Moreover, the
64// objective value of these solutions will be the same. Note that solving such
65// problems will take little time in practice because the propagation will
66// basically do all the work.
67//
68// Note(user): an optimization model can be transformed into a decision problem,
69// if for instance the objective is fixed, or independent from the rest of the
70// problem.
71//
72// TODO(user): Identify disconnected components and returns a vector of
73// presolved model? If we go this route, it may be nicer to store the indices
74// inside the model. We can add a IntegerVariableProto::initial_index;
76 public:
78 std::vector<int>* postsolve_mapping);
79
80 // We returns the status of the problem after presolve:
81 // - UNKNOWN if everything was ok.
82 // - INFEASIBLE if the model was proven so during presolve
83 // - MODEL_INVALID if the model caused some issues, like if we are not able
84 // to scale a floating point objective with enough precision.
85 CpSolverStatus Presolve();
86
87 // Executes presolve method for the given constraint. Public for testing only.
88 bool PresolveOneConstraint(int c);
89
90 // Visible for testing.
93 // Detects variable that must take different values.
95
96 private:
97 // A simple helper that logs the rules applied so far and return INFEASIBLE.
98 CpSolverStatus InfeasibleStatus();
99
100 // If there is a large proportion of fixed variables, remap the whole proto
101 // before we start the presolve.
102 bool MaybeRemoveFixedVariables(std::vector<int>* postsolve_mapping);
103
104 // Runs the inner loop of the presolver.
105 bool ProcessChangedVariables(std::vector<bool>* in_queue,
106 std::deque<int>* queue);
107 void PresolveToFixPoint();
108
109 // Runs the probing.
110 void Probe();
111
112 // Runs the expansion and fix constraints that became non-canonical.
113 void ExpandCpModelAndCanonicalizeConstraints();
114
115 // Presolve functions.
116 //
117 // They should return false only if the constraint <-> variable graph didn't
118 // change. This is just an optimization, returning true is always correct.
119 //
120 // Invariant about UNSAT: All these functions should abort right away if
121 // context_.IsUnsat() is true. And the only way to change the status to unsat
122 // is through ABSL_MUST_USE_RESULT function that should also abort right away
123 // the current code. This way we shouldn't keep doing computation on an
124 // inconsistent state.
125 // TODO(user): Make these public and unit test.
126 bool PresolveAllDiff(ConstraintProto* ct);
127 bool PresolveAutomaton(ConstraintProto* ct);
128 bool PresolveElement(int c, ConstraintProto* ct);
129 bool PresolveIntDiv(int c, ConstraintProto* ct);
130 bool PresolveIntMod(int c, ConstraintProto* ct);
131 bool PresolveIntProd(ConstraintProto* ct);
132 bool PresolveInterval(int c, ConstraintProto* ct);
133 bool PresolveInverse(ConstraintProto* ct);
134 bool DivideLinMaxByGcd(int c, ConstraintProto* ct);
135 bool PresolveLinMax(int c, ConstraintProto* ct);
136 bool PresolveLinMaxWhenAllBoolean(ConstraintProto* ct);
137 bool PropagateAndReduceAffineMax(ConstraintProto* ct);
138 bool PropagateAndReduceIntAbs(ConstraintProto* ct);
139 bool PropagateAndReduceLinMax(ConstraintProto* ct);
140 bool PresolveTable(ConstraintProto* ct);
141 void DetectDuplicateIntervals(
142 int c, google::protobuf::RepeatedField<int32_t>* intervals);
143 bool PresolveCumulative(ConstraintProto* ct);
144 bool PresolveNoOverlap(ConstraintProto* ct);
145 bool PresolveNoOverlap2D(int c, ConstraintProto* ct);
146 bool PresolveReservoir(ConstraintProto* ct);
147
148 bool PresolveCircuit(ConstraintProto* ct);
149 bool PresolveRoutes(ConstraintProto* ct);
150
151 bool PresolveAtMostOrExactlyOne(ConstraintProto* ct);
152 bool PresolveAtMostOne(ConstraintProto* ct);
153 bool PresolveExactlyOne(ConstraintProto* ct);
154
155 bool PresolveBoolAnd(ConstraintProto* ct);
156 bool PresolveBoolOr(ConstraintProto* ct);
157 bool PresolveBoolXor(ConstraintProto* ct);
158 bool PresolveEnforcementLiteral(ConstraintProto* ct);
159
160 // Regroups terms and substitute affine relations.
161 // Returns true if the set of variables in the expression changed.
162 bool CanonicalizeLinearExpression(const ConstraintProto& ct,
163 LinearExpressionProto* proto);
164 bool CanonicalizeLinearArgument(const ConstraintProto& ct,
165 LinearArgumentProto* proto);
166
167 // For the linear constraints, we have more than one function.
168 ABSL_MUST_USE_RESULT bool CanonicalizeLinear(ConstraintProto* ct,
169 bool* changed);
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 // We have an hash-map of know relation between two variables.
392 // In particular, this will include all known precedences a <= b.
393 //
394 // We reuse an IntegerVariable/IntegerValue based class via
395 // GetLinearExpression2FromProto() only visible in the .cc.
396 BestBinaryRelationBounds known_linear2_;
397
398 struct IntervalConstraintEq {
399 const CpModelProto* working_model;
400 bool operator()(int a, int b) const;
401 };
402
403 struct IntervalConstraintHash {
404 const CpModelProto* working_model;
405 std::size_t operator()(int ct_idx) const;
406 };
407
408 // Used by DetectDuplicateIntervals() and RemoveEmptyConstraints(). Note that
409 // changing the interval constraints of the model will change the hash and
410 // invalidate this hash map.
411 absl::flat_hash_map<int, int, IntervalConstraintHash, IntervalConstraintEq>
412 interval_representative_;
413};
414
415// Convenient wrapper to call the full presolve.
416CpSolverStatus PresolveCpModel(PresolveContext* context,
417 std::vector<int>* postsolve_mapping);
418
419// Returns the index of duplicate constraints in the given proto in the first
420// element of each pair. The second element of each pair is the "representative"
421// that is the first constraint in the proto in a set of duplicate constraints.
422//
423// Empty constraints are ignored. We also do a bit more:
424// - We ignore names when comparing constraint.
425// - For linear constraints, we ignore the domain. This is because we can
426// just merge them if the constraints are the same.
427// - We return the special kObjectiveConstraint (< 0) representative if a linear
428// constraint is parallel to the objective and has no enforcement literals.
429// The domain of such constraint can just be merged with the objective domain.
430//
431// If ignore_enforcement is true, we ignore enforcement literal, but do not
432// do the linear domain or objective special cases. This allow to cover some
433// other cases like:
434// - enforced constraint duplicate of non-enforced one.
435// - Two enforced constraints with singleton enforcement (vpphard).
436//
437// Visible here for testing. This is meant to be called at the end of the
438// presolve where constraints have been canonicalized.
439std::vector<std::pair<int, int>> FindDuplicateConstraints(
440 const CpModelProto& model_proto, bool ignore_enforcement = false);
441
442} // namespace sat
443} // namespace operations_research
444
445#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.
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.
void ApplyVariableMapping(PresolveContext *context, absl::Span< int > mapping, std::vector< int > *reverse_mapping)
In SWIG mode, we don't want anything besides these top-level includes.