Google OR-Tools v9.11
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-2024 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 <functional>
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 "ortools/sat/clause.h"
29#include "ortools/sat/cp_model.pb.h"
34#include "ortools/sat/sat_parameters.pb.h"
35#include "ortools/sat/util.h"
39
40namespace operations_research {
41namespace sat {
42
43// Replaces all the instance of a variable i (and the literals referring to it)
44// by mapping[i]. The definition of variables i is also moved to its new index.
45// Variables with a negative mapping value are ignored and it is an error if
46// such variable is referenced anywhere (this is CHECKed).
47//
48// The image of the mapping should be dense in [0, new_num_variables), this is
49// also CHECKed.
50void ApplyVariableMapping(const std::vector<int>& mapping,
51 const PresolveContext& context);
52
53// Presolves the initial content of presolved_model.
54//
55// This also creates a mapping model that encode the correspondence between the
56// two problems. This works as follow:
57// - The first variables of mapping_model are in one to one correspondence with
58// the variables of the initial model.
59// - The presolved_model variables are in one to one correspondence with the
60// variable at the indices given by postsolve_mapping in the mapping model.
61// - Fixing one of the two sets of variables and solving the model will assign
62// the other set to a feasible solution of the other problem. Moreover, the
63// objective value of these solutions will be the same. Note that solving such
64// problems will take little time in practice because the propagation will
65// basically do all the work.
66//
67// Note(user): an optimization model can be transformed into a decision problem,
68// if for instance the objective is fixed, or independent from the rest of the
69// problem.
70//
71// TODO(user): Identify disconnected components and returns a vector of
72// presolved model? If we go this route, it may be nicer to store the indices
73// inside the model. We can add a IntegerVariableProto::initial_index;
75 public:
77 std::vector<int>* postsolve_mapping);
78
79 // We returns the status of the problem after presolve:
80 // - UNKNOWN if everything was ok.
81 // - INFEASIBLE if the model was proven so during presolve
82 // - MODEL_INVALID if the model caused some issues, like if we are not able
83 // to scale a floating point objective with enough precision.
84 CpSolverStatus Presolve();
85
86 // Executes presolve method for the given constraint. Public for testing only.
87 bool PresolveOneConstraint(int c);
88
89 // Public for testing only.
91
92 private:
93 // A simple helper that logs the rules applied so far and return INFEASIBLE.
94 CpSolverStatus InfeasibleStatus();
95
96 // At the end of presolve, the mapping model is initialized to contains all
97 // the variable from the original model + the one created during presolve
98 // expand. It also contains the tightened domains.
99 void InitializeMappingModelVariables();
100
101 // Runs the inner loop of the presolver.
102 bool ProcessChangedVariables(std::vector<bool>* in_queue,
103 std::deque<int>* queue);
104 void PresolveToFixPoint();
105
106 // Runs the probing.
107 void Probe();
108
109 // Presolve functions.
110 //
111 // They should return false only if the constraint <-> variable graph didn't
112 // change. This is just an optimization, returning true is always correct.
113 //
114 // Invariant about UNSAT: All these functions should abort right away if
115 // context_.IsUnsat() is true. And the only way to change the status to unsat
116 // is through ABSL_MUST_USE_RESULT function that should also abort right away
117 // the current code. This way we shouldn't keep doing computation on an
118 // inconsistent state.
119 // TODO(user): Make these public and unit test.
120 bool PresolveAllDiff(ConstraintProto* ct);
121 bool PresolveAutomaton(ConstraintProto* ct);
122 bool PresolveElement(ConstraintProto* ct);
123 bool PresolveIntDiv(int c, ConstraintProto* ct);
124 bool PresolveIntMod(int c, ConstraintProto* ct);
125 bool PresolveIntProd(ConstraintProto* ct);
126 bool PresolveInterval(int c, ConstraintProto* ct);
127 bool PresolveInverse(ConstraintProto* ct);
128 bool DivideLinMaxByGcd(int c, ConstraintProto* ct);
129 bool PresolveLinMax(ConstraintProto* ct);
130 bool PresolveLinMaxWhenAllBoolean(ConstraintProto* ct);
131 bool PropagateAndReduceAffineMax(ConstraintProto* ct);
132 bool PropagateAndReduceIntAbs(ConstraintProto* ct);
133 bool PropagateAndReduceLinMax(ConstraintProto* ct);
134 bool PresolveTable(ConstraintProto* ct);
135 void DetectDuplicateIntervals(
136 int c, google::protobuf::RepeatedField<int32_t>* intervals);
137 bool PresolveCumulative(ConstraintProto* ct);
138 bool PresolveNoOverlap(ConstraintProto* ct);
139 bool PresolveNoOverlap2D(int c, ConstraintProto* ct);
140 bool PresolveReservoir(ConstraintProto* ct);
141
142 bool PresolveCircuit(ConstraintProto* ct);
143 bool PresolveRoutes(ConstraintProto* ct);
144
145 bool PresolveAtMostOrExactlyOne(ConstraintProto* ct);
146 bool PresolveAtMostOne(ConstraintProto* ct);
147 bool PresolveExactlyOne(ConstraintProto* ct);
148
149 bool PresolveBoolAnd(ConstraintProto* ct);
150 bool PresolveBoolOr(ConstraintProto* ct);
151 bool PresolveBoolXor(ConstraintProto* ct);
152 bool PresolveEnforcementLiteral(ConstraintProto* ct);
153
154 // Regroups terms and substitute affine relations.
155 // Returns true if the set of variables in the expression changed.
156 bool CanonicalizeLinearExpression(const ConstraintProto& ct,
157 LinearExpressionProto* proto);
158 bool CanonicalizeLinearArgument(const ConstraintProto& ct,
159 LinearArgumentProto* proto);
160
161 // For the linear constraints, we have more than one function.
162 bool CanonicalizeLinear(ConstraintProto* ct);
163 bool PropagateDomainsInLinear(int ct_index, ConstraintProto* ct);
164 bool RemoveSingletonInLinear(ConstraintProto* ct);
165 bool PresolveSmallLinear(ConstraintProto* ct);
166 bool PresolveLinearOfSizeOne(ConstraintProto* ct);
167 bool PresolveLinearOfSizeTwo(ConstraintProto* ct);
168 bool PresolveLinearOnBooleans(ConstraintProto* ct);
169 bool PresolveDiophantine(ConstraintProto* ct);
170 bool AddVarAffineRepresentativeFromLinearEquality(int target_index,
171 ConstraintProto* ct);
172 bool PresolveLinearEqualityWithModulo(ConstraintProto* ct);
173
174 // If a constraint is of the form "a * expr_X + expr_Y" and expr_Y can only
175 // take small values compared to a, depending on the bounds, the constraint
176 // can be equivalent to a constraint on expr_X only.
177 //
178 // For instance "10'001 X + 9'999 Y <= 105'000, with X, Y in [0, 100]" can
179 // be rewritten as X + Y <= 10 ! This can easily happen after scaling to
180 // integer cofficient a floating point constraint.
181 void TryToReduceCoefficientsOfLinearConstraint(int c, ConstraintProto* ct);
182
183 // This detects and converts constraints of the form:
184 // "X = sum Boolean * value", with "sum Boolean <= 1".
185 //
186 // Note that it is not super fast, so it shouldn't be called too often.
187 void ExtractEncodingFromLinear();
188 bool ProcessEncodingFromLinear(int linear_encoding_ct_index,
189 const ConstraintProto& at_most_or_exactly_one,
190 int64_t* num_unique_terms,
191 int64_t* num_multiple_terms);
192
193 // Remove duplicate constraints. This also merge domain of linear constraints
194 // with duplicate linear expressions.
195 void DetectDuplicateConstraints();
196 void DetectDuplicateConstraintsWithDifferentEnforcements(
197 const CpModelMapping* mapping = nullptr,
198 BinaryImplicationGraph* implication_graph = nullptr,
199 Trail* trail = nullptr);
200
201 // Detects variable that must take different values.
202 void DetectDifferentVariables();
203
204 // Detects if a linear constraint is "included" in another one, and do
205 // related presolve.
206 void DetectDominatedLinearConstraints();
207
208 // Precomputes info about at most one, and use it to presolve linear
209 // constraints. It can be interesting to know for a given linear constraint
210 // that a subset of its variables are in at most one relation.
211 void ProcessAtMostOneAndLinear();
212 void ProcessOneLinearWithAmo(int ct_index, ConstraintProto* ct,
213 ActivityBoundHelper* helper);
214
215 // SetPPC is short for set packing, partitioning and covering constraints.
216 // These are sum of booleans <=, = and >= 1 respectively.
217 // We detect inclusion of these constraint which allows a few simplifications.
218 void ProcessSetPPC();
219
220 // Detect if one constraints has a subset of enforcement of another.
221 void DetectIncludedEnforcement();
222
223 // Removes dominated constraints or fixes some variables for given pair of
224 // setppc constraints included in each other.
225 bool ProcessSetPPCSubset(int subset_c, int superset_c,
226 absl::flat_hash_set<int>* tmp_set,
227 bool* remove_subset, bool* remove_superset,
228 bool* stop_processing_superset);
229
230 // Run SAT specific presolve code.
231 // Returns false on UNSAT.
232 bool PresolvePureSatPart();
233
234 // Extracts AtMostOne constraint from Linear constraint.
235 void ExtractAtMostOneFromLinear(ConstraintProto* ct);
236
237 // Returns true if the constraint changed.
238 bool DivideLinearByGcd(ConstraintProto* ct);
239
240 void ExtractEnforcementLiteralFromLinearConstraint(int ct_index,
241 ConstraintProto* ct);
242 void LowerThanCoeffStrengthening(bool from_lower_bound, int64_t min_magnitude,
243 int64_t rhs, ConstraintProto* ct);
244
245 // Extracts cliques from bool_and and small at_most_one constraints and
246 // transforms them into maximal cliques.
247 void TransformIntoMaxCliques();
248
249 // Converts bool_or and at_most_one of size 2 to bool_and.
250 void ConvertToBoolAnd();
251
252 // Try to reformulate the objective in term of "base" variables. This is
253 // mainly useful for core based approach where having more terms in the
254 // objective (but with a same trivial lower bound) should help.
255 void ExpandObjective();
256
257 // This makes a big difference on the flatzinc mznc2017_aes_opt* problems.
258 // Where, with this, the core based approach can find small cores and close
259 // them quickly.
260 //
261 // TODO(user): Is it by chance or there is a underlying deep reason? try to
262 // merge this with what ExpandObjective() is doing.
263 void ShiftObjectiveWithExactlyOnes();
264
265 void MaybeTransferLinear1ToAnotherVariable(int var);
266 void ProcessVariableOnlyUsedInEncoding(int var);
267 void TryToSimplifyDomain(int var);
268
269 void LookAtVariableWithDegreeTwo(int var);
270 void ProcessVariableInTwoAtMostOrExactlyOne(int var);
271
272 void MergeNoOverlapConstraints();
273
274 // Assumes that all [constraint_index, multiple] in block are linear
275 // constraint that contains multiple * common_part and perform the
276 // substitution.
277 //
278 // Returns false if the substitution cannot be performed because the equation
279 // common_part = new_variable is a linear equation with potential overflow.
280 //
281 // TODO(user): I would be great to change the overflow precondition so that
282 // this cannot happen by maybe taking the rhs into account?
283 bool RemoveCommonPart(
284 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
285 const std::vector<std::pair<int, int64_t>>& block,
286 ActivityBoundHelper* helper);
287
288 // Try to identify many linear constraints that share a common linear
289 // expression. We have two slightly different heuristic.
290 //
291 // TODO(user): consolidate them.
292 void FindAlmostIdenticalLinearConstraints();
293 void FindBigAtMostOneAndLinearOverlap(ActivityBoundHelper* helper);
294 void FindBigHorizontalLinearOverlap(ActivityBoundHelper* helper);
295 void FindBigVerticalLinearOverlap(ActivityBoundHelper* helper);
296
297 // Heuristic to merge clauses that differ in only one literal.
298 // The idea is to regroup a bunch of clauses into a single bool_and.
299 // This serves a bunch of purpose:
300 // - Smaller model.
301 // - Stronger dual reasoning since less locks.
302 // - If the negation of the rhs of the bool_and are in at most one, we will
303 // have a stronger LP relaxation.
304 //
305 // TODO(user): If the merge procedure is successful we might want to develop
306 // a custom propagators for such bool_and. It should in theory be more
307 // efficient than the two watcher literal scheme for clauses. Investigate!
308 void MergeClauses();
309
310 // Boths function are responsible for dealing with affine relations.
311 // The second one returns false on UNSAT.
312 void EncodeAllAffineRelations();
313 bool PresolveAffineRelationIfAny(int var);
314
315 bool ExploitEquivalenceRelations(int c, ConstraintProto* ct);
316
317 ABSL_MUST_USE_RESULT bool RemoveConstraint(ConstraintProto* ct);
318 ABSL_MUST_USE_RESULT bool MarkConstraintAsFalse(ConstraintProto* ct);
319
320 std::vector<int>* postsolve_mapping_;
321 PresolveContext* context_;
322 SolverLogger* logger_;
323 TimeLimit* time_limit_;
324
325 // Used by CanonicalizeLinearExpressionInternal().
326 std::vector<std::pair<int, int64_t>> tmp_terms_;
327
328 // Used by DetectAndProcessAtMostOneInLinear().
329 std::vector<std::array<int64_t, 2>> conditional_mins_;
330 std::vector<std::array<int64_t, 2>> conditional_maxs_;
331
332 // Used by ProcessSetPPCSubset() and DetectAndProcessAtMostOneInLinear() to
333 // propagate linear with an at_most_one or exactly_one included inside.
334 absl::flat_hash_map<int, int> temp_map_;
335 absl::flat_hash_set<int> temp_set_;
336 ConstraintProto temp_ct_;
337
338 // Use by TryToReduceCoefficientsOfLinearConstraint().
339 struct RdEntry {
340 int64_t magnitude;
341 int64_t max_variation;
342 int index;
343 };
344 std::vector<RdEntry> rd_entries_;
345 std::vector<int> rd_vars_;
346 std::vector<int64_t> rd_coeffs_;
347 std::vector<int64_t> rd_magnitudes_;
348 std::vector<int64_t> rd_lbs_;
349 std::vector<int64_t> rd_ubs_;
350 std::vector<int64_t> rd_divisors_;
351 MaxBoundedSubsetSum lb_feasible_;
352 MaxBoundedSubsetSum lb_infeasible_;
353 MaxBoundedSubsetSum ub_feasible_;
354 MaxBoundedSubsetSum ub_infeasible_;
355
356 struct IntervalConstraintEq {
357 const CpModelProto* working_model;
358 bool operator()(int a, int b) const;
359 };
360
361 struct IntervalConstraintHash {
362 const CpModelProto* working_model;
363 std::size_t operator()(int ct_idx) const;
364 };
365
366 // Used by DetectDuplicateIntervals() and RemoveEmptyConstraints(). Note that
367 // changing the interval constraints of the model will change the hash and
368 // invalidate this hash map.
369 absl::flat_hash_map<int, int, IntervalConstraintHash, IntervalConstraintEq>
370 interval_representative_;
371};
372
373// This helper class perform copy with simplification from a model and a
374// partial assignment to another model. The purpose is to minimize the size of
375// the copied model, as well as to reduce the pressure on the memory sub-system.
376//
377// It is currently used by the LNS part, but could be used with any other scheme
378// that generates partial assignments.
380 public:
382
383 // Copies all constraints from in_model to working model of the context.
384 //
385 // During the process, it will read variable domains from the context, and
386 // simplify constraints to minimize the size of the copied model.
387 // Thus it is important that the context->working_model already have the
388 // variables part copied.
389 //
390 // It returns false iff the model is proven infeasible.
391 //
392 // It does not clear the constraints part of the working model of the context.
393 //
394 // Note(user): If first_copy is true, we will reorder the scheduling
395 // constraint so that they only use reference to previously defined intervals.
396 // This allow to be more efficient later in a few preprocessing steps.
398 const CpModelProto& in_model, bool first_copy = false,
399 std::function<bool(int)> active_constraints = nullptr);
400
401 // Copy variables from the in_model to the working model.
402 // It reads the 'ignore_names' parameters from the context, and keeps or
403 // deletes names accordingly.
404 void ImportVariablesAndMaybeIgnoreNames(const CpModelProto& in_model);
405
406 // Setup new variables from a vector of domains.
407 // Inactive variables will be fixed to their lower bound.
408 void CreateVariablesFromDomains(const std::vector<Domain>& domains);
409
410 private:
411 // Overwrites the out_model to be unsat. Returns false.
412 // The arguments are used to log which constraint caused unsat.
413 bool CreateUnsatModel(int c, const ConstraintProto& ct);
414
415 // Returns false if the constraint is never enforced and can be skipped.
416 bool PrepareEnforcementCopy(const ConstraintProto& ct);
417 bool PrepareEnforcementCopyWithDup(const ConstraintProto& ct);
418 void FinishEnforcementCopy(ConstraintProto* ct);
419
420 // All these functions return false if the constraint is found infeasible.
421 bool CopyBoolOr(const ConstraintProto& ct);
422 bool CopyBoolOrWithDupSupport(const ConstraintProto& ct);
423 bool FinishBoolOrCopy();
424
425 bool CopyBoolAnd(const ConstraintProto& ct);
426 bool CopyBoolAndWithDupSupport(const ConstraintProto& ct);
427
428 bool CopyLinear(const ConstraintProto& ct);
429 bool CopyAtMostOne(const ConstraintProto& ct);
430 bool CopyExactlyOne(const ConstraintProto& ct);
431
432 // If we "copy" an interval for a first time, we make sure to create the
433 // linear constraint between the start, size and end. This allow to simplify
434 // the input proto and client side code.
435 bool CopyInterval(const ConstraintProto& ct, int c, bool ignore_names);
436 void AddLinearConstraintForInterval(const ConstraintProto& ct);
437
438 // These function remove unperformed intervals. Note that they requires
439 // interval to appear before (validated) as they test unperformed by testing
440 // if interval_mapping_ is empty.
441 void CopyAndMapNoOverlap(const ConstraintProto& ct);
442 void CopyAndMapNoOverlap2D(const ConstraintProto& ct);
443 void CopyAndMapCumulative(const ConstraintProto& ct);
444
445 PresolveContext* context_;
446
447 // Temp vectors.
448 std::vector<int> non_fixed_variables_;
449 std::vector<int64_t> non_fixed_coefficients_;
450 absl::flat_hash_map<int, int> interval_mapping_;
451 int starting_constraint_index_ = 0;
452
453 std::vector<int> temp_enforcement_literals_;
454 absl::flat_hash_set<int> temp_enforcement_literals_set_;
455
456 std::vector<int> temp_literals_;
457 absl::flat_hash_set<int> temp_literals_set_;
458};
459
460// Copy in_model to the model in the presolve context.
461// It performs on the fly simplification, and returns false if the
462// model is proved infeasible. If reads the parameters 'ignore_names' and keeps
463// or deletes variables and constraints names accordingly.
464//
465// This should only be called on the first copy of the user given model.
466// Note that this reorder all constraints that use intervals last. We loose the
467// user-defined order, but hopefully that should not matter too much.
468bool ImportModelWithBasicPresolveIntoContext(const CpModelProto& in_model,
470
471// Same as ImportModelWithBasicPresolveIntoContext() except that variable
472// domains are read from domains.
474 const CpModelProto& in_model, const std::vector<Domain>& domains,
475 std::function<bool(int)> active_constraints, PresolveContext* context);
476
477// Copies the non constraint, non variables part of the model.
479 const CpModelProto& in_model, PresolveContext* context);
480
481// Convenient wrapper to call the full presolve.
483 std::vector<int>* postsolve_mapping);
484
485// Returns the index of duplicate constraints in the given proto in the first
486// element of each pair. The second element of each pair is the "representative"
487// that is the first constraint in the proto in a set of duplicate constraints.
488//
489// Empty constraints are ignored. We also do a bit more:
490// - We ignore names when comparing constraint.
491// - For linear constraints, we ignore the domain. This is because we can
492// just merge them if the constraints are the same.
493// - We return the special kObjectiveConstraint (< 0) representative if a linear
494// constraint is parallel to the objective and has no enforcement literals.
495// The domain of such constraint can just be merged with the objective domain.
496//
497// If ignore_enforcement is true, we ignore enforcement literal, but do not
498// do the linear domain or objective special cases. This allow to cover some
499// other cases like:
500// - enforced constraint duplicate of non-enforced one.
501// - Two enforced constraints with singleton enforcement (vpphard).
502//
503// Visible here for testing. This is meant to be called at the end of the
504// presolve where constraints have been canonicalized.
505std::vector<std::pair<int, int>> FindDuplicateConstraints(
506 const CpModelProto& model_proto, bool ignore_enforcement = false);
507
508} // namespace sat
509} // namespace operations_research
510
511#endif // OR_TOOLS_SAT_CP_MODEL_PRESOLVE_H_
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool PresolveOneConstraint(int c)
Executes presolve method for the given constraint. Public for testing only.
void RemoveEmptyConstraints()
Public for testing only.
void ImportVariablesAndMaybeIgnoreNames(const CpModelProto &in_model)
void CreateVariablesFromDomains(const std::vector< Domain > &domains)
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, bool first_copy=false, std::function< bool(int)> active_constraints=nullptr)
int64_t a
Definition table.cc:44
CpModelProto proto
The output proto.
bool ignore_enforcement
const Constraint * ct
IntVar * var
GurobiMPCallbackContext * context
int ct_index
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto, bool ignore_enforcement)
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, const std::vector< Domain > &domains, std::function< bool(int)> active_constraints, PresolveContext *context)
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
Copies the non constraint, non variables part of the model.
In SWIG mode, we don't want anything besides these top-level includes.