![]() |
Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
|
#include <cp_model_presolve.h>
Public Member Functions | |
CpModelPresolver (PresolveContext *context, std::vector< int > *postsolve_mapping) | |
CpSolverStatus | Presolve () |
bool | PresolveOneConstraint (int c) |
Executes presolve method for the given constraint. Public for testing only. | |
void | RemoveEmptyConstraints () |
Visible for testing. | |
void | DetectDuplicateColumns () |
void | DetectDifferentVariables () |
Detects variable that must take different values. | |
Presolves the initial content of presolved_model.
This also creates a mapping model that encode the correspondence between the two problems. This works as follow:
Note(user): an optimization model can be transformed into a decision problem, if for instance the objective is fixed, or independent from the rest of the problem.
Definition at line 76 of file cp_model_presolve.h.
operations_research::sat::CpModelPresolver::CpModelPresolver | ( | PresolveContext * | context, |
std::vector< int > * | postsolve_mapping ) |
Definition at line 13063 of file cp_model_presolve.cc.
void operations_research::sat::CpModelPresolver::DetectDifferentVariables | ( | ) |
Detects variable that must take different values.
List the variable that are pairwise different, also store in offset[x, y] the offsets such that x >= y + offset.second OR y >= x + offset.first.
Process the fact "v1 - v2 \in Domain".
We have x - y not in exclusion, so x - y > exclusion.Max() --> x > y + exclusion.Max(); OR x - y < exclusion.Min() --> y > x - exclusion.Min();
Try to find identical linear constraint with incompatible domains. This works really well on neos16.mps.gz where we have a <=> x <= y b <=> x >= y and a => not(b), Because of this presolve, we detect that not(a) => b and thus that a and not(b) are equivalent. We can thus simplify the problem to just a => x < y not(a) => x > y
Detect direct encoding of x != y. Note that we also see that from x > y and related.
We assume the constraint was already divided by its gcd.
Detect x != y via lit => x > y && not(lit) => x < y.
We have x - y in domain1 or in domain2, so it must be in the union.
We assume the constraint was already divided by its gcd.
Detect all_different cliques. We reuse the max-clique code from sat.
Note(user): The all diff added here will not be expanded since we run this after expansion. This is fragile though. Not even sure this is what we want.
All variables at false is always a valid solution of the local model, so this should never return UNSAT.
We have an all-diff, but inspect the offsets to see if we have a disjunctive ! Note that this is quadratic, but no more complex than the scan of the model we just did above, since we had one linear constraint per entry.
When this happens, it means this interval can never be before any other. We should probably handle this case better, but for now we abort.
We have one size greater than 1, lets add a no_overlap!
Definition at line 10157 of file cp_model_presolve.cc.
void operations_research::sat::CpModelPresolver::DetectDuplicateColumns | ( | ) |
Our current implementation require almost a full copy. First construct a transpose var to columns (constraint_index, coeff).
We will only support columns that include:
at_most_one/exactly_one/clauses (but with positive variable only).
It is okay to ignore terms (the columns will not be full).
Use kObjectiveConstraint (-1) for the objective.
Now construct the graph.
Find duplicate columns using an hash map. We only consider "full" columns. var -> var_representative using columns hash/comparison.
Process duplicates.
Since columns are the same, we can introduce a new variable = sum all columns. Note that the linear expression will not overflow, but the overflow check also requires that max_sum < int_max/2, which might happen.
In the corner case where there is a lot of holes in the domain, and the sum domain is too complex, we skip. Hopefully this should be rare.
Deal with objective right away.
Lets rescan the model, and remove all variables, replacing them by the sum. We do that in one O(model size) pass.
Deal with bool case.
Deal with linear case.
We removed all occurrence of "var_to_remove" so we can remove them now.
Definition at line 9532 of file cp_model_presolve.cc.
CpSolverStatus operations_research::sat::CpModelPresolver::Presolve | ( | ) |
We returns the status of the problem after presolve:
The presolve works as follow:
First stage: We will process all active constraints until a fix point is reached. During this stage:
Second stage:
If the objective is a floating point one, we scale it.
At this point, we didn't create any new variables, so the integer objective is in term of the orinal problem variables. We save it so that we can expose to the user what exact objective we are actually optimizing.
If there is a large proprotion of fixed variable, lets remap the model before we start the actual presolve. This is useful for LNS in particular.
fixed_postsolve_mapping[i] will contains the original index of the variable that will be at position i after MaybeRemoveFixedVariables(). If the mapping is left empty, it will be set to the identity mapping later by InitializeMappingModelVariables().
Initialize the initial context.working_model domains. Initialize the objective and the constraint <-> variable graph.
If presolve is false, just run expansion.
We still write back the canonical objective has we don't deal well with uninitialized domain or duplicate variables.
We need to append all the variable equivalence that are still used!
Make sure we also have an initialized mapping model as we use this for filling the tightened variables. Even without presolve, we do some trivial presolving during the initial copy of the model, and expansion might do more.
We don't want to run postsolve when the presolve is disabled, but the expansion might have added some constraints to the mapping model. To restore correctness, we merge them with the working model.
Presolve all variable domain once. The PresolveToFixPoint() function will only reprocess domain that changed.
Try to canonicalize the domain, note that we should have detected all affine relations before, so we don't recreate "canononical" variables if they already exist in the model.
Main propagation loop.
Propagate the objective.
Call expansion.
We need to re-evaluate the degree because some presolve rule only run after expansion.
We run the symmetry before more complex presolve rules as many of them are heuristic based and might break the symmetry present in the original problems. This happens for example on the flatzinc wordpress problem.
Both kind of duplications might introduce a lot of symmetries and we want to do that before we even compute them.
If the presolve always keep symmetry, we compute it once and for all.
We distinguish an empty symmetry message meaning that symmetry were computed and there is none, and the absence of symmetry message meaning we don't know.
Runs SAT specific presolve on the pure-SAT part of the problem.
Extract redundant at most one constraint from the linear ones.
Deal with pair of constraints.
These operations might break symmetry. Or at the very least, the newly created variable must be incorporated in the generators.
Heuristic: vertical introduce smaller defining constraint and appear in many constraints, so might be more constrained. We might also still make horizontal rectangle with the variable introduced.
We do that after the duplicate, SAT and SetPPC constraints.
Merge clauses that differ in just one literal. Heuristic use at_most_one to try to tighten the initial LP Relaxation.
The TransformIntoMaxCliques() call above transform all bool and into at most one of size 2. This does the reverse and merge them.
Call the main presolve to remove the fixed variables and do more deductions.
Exit the loop if no operations were performed.
Regroup no-overlaps into max-cliques.
Tries to spread the objective amongst many variables. We re-do a canonicalization with the final linear expression.
If we have fixed variables or created new affine relations, there might be more things to presolve.
We re-do a canonicalization with the final linear expression.
Now that everything that could possibly be fixed was fixed, make sure we don't leave any linear constraint with fixed variables.
Take care of linear constraint with a complex rhs.
Adds all needed affine relation to context_->working_model.
If we have symmetry information, lets filter it.
The strategy variable indices will be remapped in ApplyVariableMapping() but first we use the representative of the affine relations for the variables that are not present anymore.
Canonicalize each expression to use affine representative.
Remove fixed expression and affine corresponding to same variables.
Sync the domains and initialize the mapping model variables.
Remove all the unused variables from the presolved model.
Heuristic: If a variable is removed and has a representative that is not, we "move" the representative to the spot of that variable in the original order. This is to preserve any info encoded in the variable order by the modeler.
Deal with unused variables.
If the variable is not fixed, we have multiple feasible solution for this variable, so we can't remove it if we want all of them.
Tricky. Variables that where not removed by a presolve rule should be fixed first during postsolve, so that more complex postsolve rules can use their values. One way to do that is to fix them here.
We prefer to fix them to zero if possible.
Merge identical constant. Note that the only place were constant are still left are in the circuit and route constraint for fixed arcs.
The mapping might merge variable, so we have to be careful here.
Compact all non-empty constraint at the beginning.
Hack to display the number of deductions stored.
Stats and checks.
This is not supposed to happen, and is more indicative of an error than an INVALID model. But for our no-overflow preconditions, we might run into bad situation that causes the final model to be invalid.
Definition at line 13195 of file cp_model_presolve.cc.
bool operations_research::sat::CpModelPresolver::PresolveOneConstraint | ( | int | c | ) |
Executes presolve method for the given constraint. Public for testing only.
Generic presolve to exploit variable/literal equivalence.
Generic presolve for reified constraint.
Call the presolve function for this constraint if any.
We first propagate the domains before calling this presolve rule.
There is no need to re-do a propagation here, but the constraint size might have been reduced.
If we extracted some enforcement, we redo some presolve.
For 2D, we don't exploit index duplication between x/y so it is not important to do it beforehand. Moreover in some situation PresolveNoOverlap2D() remove a lot of interval, so better to do it afterwards.
Definition at line 8785 of file cp_model_presolve.cc.
void operations_research::sat::CpModelPresolver::RemoveEmptyConstraints | ( | ) |
Visible for testing.
Remove all empty constraints and duplicated intervals. Note that we need to remap the interval references.
Now that they have served their purpose, we also remove dummy constraints, otherwise that causes issue because our model are invalid in tests.
Definition at line 122 of file cp_model_presolve.cc.