Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
solution_crush.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_SOLUTION_CRUSH_H_
15#define OR_TOOLS_SAT_SOLUTION_CRUSH_H_
16
17#include <cstdint>
18#include <memory>
19#include <optional>
20#include <utility>
21#include <vector>
22
23#include "absl/container/flat_hash_map.h"
24#include "absl/container/inlined_vector.h"
25#include "absl/types/span.h"
27#include "ortools/sat/cp_model.pb.h"
30#include "ortools/sat/util.h"
32
33namespace operations_research {
34namespace sat {
35
36// Transforms (or "crushes") solutions of the initial model into solutions of
37// the presolved model.
38//
39// Note that partial solution crushing is not a priority: as of Jan 2025, most
40// methods of this class do nothing if some solution values are missing to
41// perform their work. If one just want to complete a partial solution to a full
42// one for convenience, it should be relatively easy to first solve a
43// feasibility model where all hinted variables are fixed, and use the solution
44// to that problem as a starting hint.
45//
46// Note also that if the initial "solution" is incomplete or infeasible, the
47// crushed "solution" might contain values outside of the domain of their
48// variables. Consider for instance two constraints "b => v=1" and "!b => v=2",
49// presolved into "v = b+1", with SetVarToLinearConstraintSolution called to set
50// b's value from v's value. If the initial solution is infeasible, with v=0,
51// this will set b to -1, which is outside of its [0,1] domain.
53 public:
54 SolutionCrush() = default;
55
56 // SolutionCrush is neither copyable nor movable.
57 SolutionCrush(const SolutionCrush&) = delete;
61
62 bool SolutionIsLoaded() const { return solution_is_loaded_; }
63
64 // Visible for testing.
65 absl::Span<const int64_t> GetVarValues() const { return var_values_; }
66
67 // Sets the given values in the solution. `solution` must be a map from
68 // variable indices to variable values. This must be called only once, before
69 // any other method.
70 void LoadSolution(int num_vars,
71 const absl::flat_hash_map<int, int64_t>& solution);
72
73 // Resizes the solution to contain `new_size` variables. Does not change the
74 // value of existing variables, and does not set any value for the new
75 // variables.
76 // WARNING: the methods below do not automatically resize the solution. To set
77 // the value of a new variable with one of them, call this method first.
78 void Resize(int new_size);
79
80 // Sets the value of `literal` to "`var`'s value == `value`". Does nothing if
81 // `literal` already has a value.
82 void MaybeSetLiteralToValueEncoding(int literal, int var, int64_t value);
83
84 // Sets the value of `var` to the value of the given linear expression, if all
85 // the variables in this expression have a value. `linear` must be a list of
86 // (variable index, coefficient) pairs.
88 int var, absl::Span<const std::pair<int, int64_t>> linear,
89 int64_t offset = 0);
90
91 // Sets the value of `var` to the value of the given linear expression.
92 // The two spans must have the same size.
93 void SetVarToLinearExpression(int var, absl::Span<const int> vars,
94 absl::Span<const int64_t> coeffs,
95 int64_t offset = 0);
96
97 // Sets the value of `var` to 1 if the value of at least one literal in
98 // `clause` is equal to 1 (or to 0 otherwise). `clause` must be a list of
99 // literal indices.
100 void SetVarToClause(int var, absl::Span<const int> clause);
101
102 // Sets the value of `var` to 1 if the value of all the literals in
103 // `conjunction` is 1 (or to 0 otherwise). `conjunction` must be a list of
104 // literal indices.
105 void SetVarToConjunction(int var, absl::Span<const int> conjunction);
106
107 // Sets the value of `var` to `value` if the value of the given linear
108 // expression is not in `domain` (or does nothing otherwise). `linear` must be
109 // a list of (variable index, coefficient) pairs.
111 int var, int64_t value, absl::Span<const std::pair<int, int64_t>> linear,
112 const Domain& domain);
113
114 // Sets the value of `literal` to `value` if the value of the given linear
115 // expression is not in `domain` (or does nothing otherwise). `linear` must be
116 // a list of (variable index, coefficient) pairs.
118 int literal, bool value, absl::Span<const std::pair<int, int64_t>> linear,
119 const Domain& domain);
120
121 // Sets the value of `var` to `value` if the value of `condition_lit` is true.
122 void SetVarToValueIf(int var, int64_t value, int condition_lit);
123
124 // Sets the value of `var` to the value `expr` if the value of `condition_lit`
125 // is true.
126 void SetVarToLinearExpressionIf(int var, const LinearExpressionProto& expr,
127 int condition_lit);
128
129 // Sets the value of `literal` to `value` if the value of `condition_lit` is
130 // true.
131 void SetLiteralToValueIf(int literal, bool value, int condition_lit);
132
133 // Sets the value of `var` to `value_if_true` if the value of all the
134 // `condition_lits` literals is true, and to `value_if_false` otherwise.
135 void SetVarToConditionalValue(int var, absl::Span<const int> condition_lits,
136 int64_t value_if_true, int64_t value_if_false);
137
138 // If one literal does not have a value, and the other one does, sets the
139 // value of the latter to the value of the former. If both literals have a
140 // value, sets the value of `lit1` to the value of `lit2`.
141 void MakeLiteralsEqual(int lit1, int lit2);
142
143 // If `var` already has a value, updates it to be within the given domain.
144 // Otherwise, if the domain is fixed, sets the value of `var` to this fixed
145 // value. Otherwise does nothing.
146 void SetOrUpdateVarToDomain(int var, const Domain& domain);
147
148 // Updates the value of the given literals to false if their current values
149 // are different (or does nothing otherwise).
150 void UpdateLiteralsToFalseIfDifferent(int lit1, int lit2);
151
152 // Decrements the value of `lit` and increments the value of `dominating_lit`
153 // if their values are equal to 1 and 0, respectively.
154 void UpdateLiteralsWithDominance(int lit, int dominating_lit);
155
156 // Decrements the value of `ref` by the minimum amount necessary to be in
157 // [`min_value`, `max_value`], and increments the value of one or more
158 // `dominating_refs` by the same total amount (or less if it is not possible
159 // to exactly match this amount), while staying within their respective
160 // domains. The value of a negative reference index r is the opposite of the
161 // value of the variable PositiveRef(r).
162 //
163 // `min_value` must be the minimum value of `ref`'s current domain D, and
164 // `max_value` must be in D.
166 int ref, int64_t min_value, int64_t max_value,
167 absl::Span<const std::pair<int, Domain>> dominating_refs);
168
169 // If `var`'s value != `value` finds another variable in the orbit of `var`
170 // that can take that value, and permute the solution (using the symmetry
171 // `generators`) so that this other variable is at position var. If no other
172 // variable can be found, does nothing.
174 int var, bool value,
175 absl::Span<const std::unique_ptr<SparsePermutation>> generators);
176
177 // Sets the value of the i-th variable in `vars` so that the given constraint
178 // "dotproduct(coeffs, vars values) = rhs" is satisfied, if all the other
179 // variables have a value. i is equal to `var_index` if set. Otherwise it is
180 // the index of the variable without a value (if there is not exactly one,
181 // this method does nothing).
182 void SetVarToLinearConstraintSolution(std::optional<int> var_index,
183 absl::Span<const int> vars,
184 absl::Span<const int64_t> coeffs,
185 int64_t rhs);
186
187 // Sets the value of the variables in `level_vars` and in `circuit` if all the
188 // variables in `reservoir` have a value. This assumes that there is one level
189 // variable and one circuit node per element in `reservoir` (in the same
190 // order) -- plus one last node representing the start and end of the circuit.
191 void SetReservoirCircuitVars(const ReservoirConstraintProto& reservoir,
192 int64_t min_level, int64_t max_level,
193 absl::Span<const int> level_vars,
194 const CircuitConstraintProto& circuit);
195
196 // Sets the value of `var` to "`time_i`'s value <= `time_j`'s value &&
197 // `active_i`'s value == true && `active_j`'s value == true".
199 const LinearExpressionProto& time_i,
200 const LinearExpressionProto& time_j,
201 int active_i, int active_j);
202
203 // Sets the value of `div_var` and `prod_var` if all the variables in the
204 // IntMod `ct` constraint have a value, assuming that this "target = x % m"
205 // constraint is expanded into "div_var = x / m", "prod_var = div_var * m",
206 // and "target = x - prod_var" constraints. If `ct` is not enforced, sets the
207 // values of `div_var` and `prod_var` to `default_div_value` and
208 // `default_prod_value`, respectively.
209 void SetIntModExpandedVars(const ConstraintProto& ct, int div_var,
210 int prod_var, int64_t default_div_value,
211 int64_t default_prod_value);
212
213 // Sets the value of as many variables in `prod_vars` as possible (depending
214 // on how many expressions in `int_prod` have a value), assuming that the
215 // `int_prod` constraint "target = x_0 * x_1 * ... * x_n" is expanded into
216 // "prod_var_1 = x_0 * x1",
217 // "prod_var_2 = prod_var_1 * x_2",
218 // ...,
219 // "prod_var_(n-1) = prod_var_(n-2) * x_(n-1)",
220 // and "target = prod_var_(n-1) * x_n" constraints.
221 void SetIntProdExpandedVars(const LinearArgumentProto& int_prod,
222 absl::Span<const int> prod_vars);
223
224 // Sets the value of `enforcement_lits` if all the variables in `lin_max`
225 // have a value, assuming that the `lin_max` constraint "target = max(x_0,
226 // x_1, ..., x_(n-1))" is expanded into "enforcement_lits[i] => target <= x_i"
227 // constraints, with at most one enforcement value equal to true.
228 // `enforcement_lits` must have as many elements as `lin_max`.
229 void SetLinMaxExpandedVars(const LinearArgumentProto& lin_max,
230 absl::Span<const int> enforcement_lits);
231
232 // Represents `var` = "automaton is in state `state` at time `time`".
233 struct StateVar {
234 int var;
235 int time;
236 int64_t state;
237 };
238
239 // Represents `var` = "automaton takes the transition labeled
240 // `transition_label` from state `transition_tail` at time `time`".
242 int var;
243 int time;
246 };
247
248 // Sets the value of `state_vars` and `transition_vars` if all the variables
249 // in `automaton` have a value.
251 const AutomatonConstraintProto& automaton,
252 absl::Span<const StateVar> state_vars,
253 absl::Span<const TransitionVar> transition_vars);
254
255 // Represents `lit` = "for all i, the value of the i-th column var of a table
256 // constraint is in the `var_values`[i] set (unless this set is empty).".
258 int lit;
259 // TODO(user): use a vector of (var, value) pairs instead?
260 std::vector<absl::InlinedVector<int64_t, 2>> var_values;
261 };
262
263 // Sets the value of the `new_row_lits` literals if all the variables in
264 // `column_vars` and `existing_row_lits` have a value. For each `row_lits`,
265 // `column_values` must have the same size as `column_vars`. This method
266 // assumes that exactly one of `existing_row_lits` and `new_row_lits` must be
267 // true.
268 void SetTableExpandedVars(absl::Span<const int> column_vars,
269 absl::Span<const int> existing_row_lits,
270 absl::Span<const TableRowLiteral> new_row_lits);
271
272 // Sets the value of `bucket_lits` if all the variables in `linear` have a
273 // value, assuming that they are expanded from the complex linear constraint
274 // (i.e. one whose domain has two or more intervals). The value of
275 // `bucket_lits`[i] is set to 1 iff the value of the linear expression is in
276 // the i-th interval of the domain.
278 const LinearConstraintProto& linear, absl::Span<const int> bucket_lits);
279
280 // Stores the solution as a hint in the given model.
281 void StoreSolutionAsHint(CpModelProto& model) const;
282
283 // Given a list of N disjoint packing areas (each described by a union of
284 // rectangles) and a list of M boxes (described by their x and y interval
285 // constraints in the `model` proto), sets the value of the literals in
286 // `box_in_area_lits` with whether the box i intersects area j.
294 const CpModelProto& model, absl::Span<const int> x_intervals,
295 absl::Span<const int> y_intervals,
296 absl::Span<const BoxInAreaLiteral> box_in_area_lits);
297
298 private:
299 bool HasValue(int var) const { return var_has_value_[var]; }
300
301 int64_t GetVarValue(int var) const { return var_values_[var]; }
302
303 bool GetLiteralValue(int lit) const {
304 const int var = PositiveRef(lit);
305 return RefIsPositive(lit) ? GetVarValue(var) : !GetVarValue(var);
306 }
307
308 std::optional<int64_t> GetRefValue(int ref) const {
309 const int var = PositiveRef(ref);
310 if (!HasValue(var)) return std::nullopt;
311 return RefIsPositive(ref) ? GetVarValue(var) : -GetVarValue(var);
312 }
313
314 std::optional<int64_t> GetExpressionValue(
315 const LinearExpressionProto& expr) const {
316 int64_t result = expr.offset();
317 for (int i = 0; i < expr.vars().size(); ++i) {
318 if (expr.coeffs(i) == 0) continue;
319 if (!HasValue(expr.vars(i))) return std::nullopt;
320 result += expr.coeffs(i) * GetVarValue(expr.vars(i));
321 }
322 return result;
323 }
324
325 void SetVarValue(int var, int64_t value) {
326 var_has_value_[var] = true;
327 var_values_[var] = value;
328 }
329
330 void SetLiteralValue(int lit, bool value) {
331 SetVarValue(PositiveRef(lit), RefIsPositive(lit) == value ? 1 : 0);
332 }
333
334 void SetRefValue(int ref, int value) {
335 SetVarValue(PositiveRef(ref), RefIsPositive(ref) ? value : -value);
336 }
337
338 void PermuteVariables(const SparsePermutation& permutation);
339
340 bool solution_is_loaded_ = false;
341 std::vector<bool> var_has_value_;
342 // This contains all the solution values or zero if a solution is not loaded.
343 std::vector<int64_t> var_values_;
344};
345
346} // namespace sat
347} // namespace operations_research
348
349#endif // OR_TOOLS_SAT_SOLUTION_CRUSH_H_
void LoadSolution(int num_vars, const absl::flat_hash_map< int, int64_t > &solution)
void SetIntModExpandedVars(const ConstraintProto &ct, int div_var, int prod_var, int64_t default_div_value, int64_t default_prod_value)
void SetLinearWithComplexDomainExpandedVars(const LinearConstraintProto &linear, absl::Span< const int > bucket_lits)
void StoreSolutionAsHint(CpModelProto &model) const
Stores the solution as a hint in the given model.
absl::Span< const int64_t > GetVarValues() const
Visible for testing.
void SetLinMaxExpandedVars(const LinearArgumentProto &lin_max, absl::Span< const int > enforcement_lits)
void SetVarToConjunction(int var, absl::Span< const int > conjunction)
void SetReservoirCircuitVars(const ReservoirConstraintProto &reservoir, int64_t min_level, int64_t max_level, absl::Span< const int > level_vars, const CircuitConstraintProto &circuit)
void SetVarToValueIf(int var, int64_t value, int condition_lit)
Sets the value of var to value if the value of condition_lit is true.
void AssignVariableToPackingArea(const CompactVectorVector< int, Rectangle > &areas, const CpModelProto &model, absl::Span< const int > x_intervals, absl::Span< const int > y_intervals, absl::Span< const BoxInAreaLiteral > box_in_area_lits)
void SetAutomatonExpandedVars(const AutomatonConstraintProto &automaton, absl::Span< const StateVar > state_vars, absl::Span< const TransitionVar > transition_vars)
SolutionCrush & operator=(SolutionCrush &&)=delete
void SetVarToValueIfLinearConstraintViolated(int var, int64_t value, absl::Span< const std::pair< int, int64_t > > linear, const Domain &domain)
void SetVarToReifiedPrecedenceLiteral(int var, const LinearExpressionProto &time_i, const LinearExpressionProto &time_j, int active_i, int active_j)
void MakeLiteralsEqual(int lit1, int lit2)
void UpdateLiteralsWithDominance(int lit, int dominating_lit)
void SetLiteralToValueIfLinearConstraintViolated(int literal, bool value, absl::Span< const std::pair< int, int64_t > > linear, const Domain &domain)
void UpdateRefsWithDominance(int ref, int64_t min_value, int64_t max_value, absl::Span< const std::pair< int, Domain > > dominating_refs)
SolutionCrush & operator=(const SolutionCrush &)=delete
void SetIntProdExpandedVars(const LinearArgumentProto &int_prod, absl::Span< const int > prod_vars)
void SetVarToLinearExpressionIf(int var, const LinearExpressionProto &expr, int condition_lit)
void MaybeSetLiteralToValueEncoding(int literal, int var, int64_t value)
SolutionCrush(SolutionCrush &&)=delete
void SetOrUpdateVarToDomain(int var, const Domain &domain)
void MaybeUpdateVarWithSymmetriesToValue(int var, bool value, absl::Span< const std::unique_ptr< SparsePermutation > > generators)
void SetVarToLinearExpression(int var, absl::Span< const std::pair< int, int64_t > > linear, int64_t offset=0)
void UpdateLiteralsToFalseIfDifferent(int lit1, int lit2)
void SetLiteralToValueIf(int literal, bool value, int condition_lit)
void SetTableExpandedVars(absl::Span< const int > column_vars, absl::Span< const int > existing_row_lits, absl::Span< const TableRowLiteral > new_row_lits)
void SetVarToConditionalValue(int var, absl::Span< const int > condition_lits, int64_t value_if_true, int64_t value_if_false)
void SetVarToClause(int var, absl::Span< const int > clause)
SolutionCrush(const SolutionCrush &)=delete
SolutionCrush is neither copyable nor movable.
void SetVarToLinearConstraintSolution(std::optional< int > var_index, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t rhs)
In SWIG mode, we don't want anything besides these top-level includes.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
Represents var = "automaton is in state `state` at time `time`".
std::vector< absl::InlinedVector< int64_t, 2 > > var_values