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