OR-Tools  9.2
cp_model_postsolve.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 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 
15 
16 #include <cstdint>
17 #include <limits>
18 
20 
21 namespace operations_research {
22 namespace sat {
23 
24 // This postsolve is "special". If the clause is not satisfied, we fix the
25 // first literal in the clause to true (even if it was fixed to false). This
26 // allows to handle more complex presolve operations used by the SAT presolver.
27 //
28 // Also, any "free" Boolean should be fixed to some value for the subsequent
29 // postsolve steps.
30 void PostsolveClause(const ConstraintProto& ct, std::vector<Domain>* domains) {
31  const int size = ct.bool_or().literals_size();
32  CHECK_NE(size, 0);
33  bool satisfied = false;
34  for (int i = 0; i < size; ++i) {
35  const int ref = ct.bool_or().literals(i);
36  const int var = PositiveRef(ref);
37  if ((*domains)[var].IsFixed()) {
38  if ((*domains)[var].FixedValue() == (RefIsPositive(ref) ? 1 : 0)) {
39  satisfied = true;
40  }
41  } else {
42  // We still need to assign free variable. Any value should work.
43  (*domains)[PositiveRef(ref)] = Domain(0);
44  }
45  }
46  if (satisfied) return;
47 
48  // Change the value of the first variable (which was chosen at presolve).
49  const int first_ref = ct.bool_or().literals(0);
50  (*domains)[PositiveRef(first_ref)] = Domain(RefIsPositive(first_ref) ? 1 : 0);
51 }
52 
54  std::vector<Domain>* domains) {
55  bool satisfied = false;
56  std::vector<int> free_variables;
57  for (const int ref : ct.exactly_one().literals()) {
58  const int var = PositiveRef(ref);
59  if ((*domains)[var].IsFixed()) {
60  if ((*domains)[var].FixedValue() == (RefIsPositive(ref) ? 1 : 0)) {
61  CHECK(!satisfied) << "Two variables at one in exactly one.";
62  satisfied = true;
63  }
64  } else {
65  free_variables.push_back(ref);
66  }
67  }
68  if (!satisfied) {
69  // Fix one at true.
70  CHECK(!free_variables.empty()) << "All zero in exactly one";
71  const int ref = free_variables.back();
72  (*domains)[PositiveRef(ref)] = Domain(RefIsPositive(ref) ? 1 : 0);
73  free_variables.pop_back();
74  }
75 
76  // Fix any free variable left at false.
77  for (const int ref : free_variables) {
78  (*domains)[PositiveRef(ref)] = Domain(RefIsPositive(ref) ? 0 : 1);
79  }
80 }
81 
82 // For now we set the first unset enforcement literal to false.
83 // There must be one.
85  std::vector<Domain>* domains) {
86  CHECK(!ct.enforcement_literal().empty());
87  bool has_free_enforcement_literal = false;
88  for (const int enf : ct.enforcement_literal()) {
89  if ((*domains)[PositiveRef(enf)].IsFixed()) continue;
90  has_free_enforcement_literal = true;
91  if (RefIsPositive(enf)) {
92  (*domains)[enf] = Domain(0);
93  } else {
94  (*domains)[PositiveRef(enf)] = Domain(1);
95  }
96  break;
97  }
98  if (!has_free_enforcement_literal) {
99  LOG(FATAL)
100  << "Unsatisfied linear constraint with no free enforcement literal: "
101  << ct.ShortDebugString();
102  }
103 }
104 
105 // Here we simply assign all non-fixed variable to a feasible value. Which
106 // should always exists by construction.
107 void PostsolveLinear(const ConstraintProto& ct, std::vector<Domain>* domains) {
108  int64_t fixed_activity = 0;
109  const int size = ct.linear().vars().size();
110  std::vector<int> free_vars;
111  std::vector<int64_t> free_coeffs;
112  for (int i = 0; i < size; ++i) {
113  const int var = ct.linear().vars(i);
114  const int64_t coeff = ct.linear().coeffs(i);
115  CHECK_LT(var, domains->size());
116  if (coeff == 0) continue;
117  if ((*domains)[var].IsFixed()) {
118  fixed_activity += (*domains)[var].FixedValue() * coeff;
119  } else {
120  free_vars.push_back(var);
121  free_coeffs.push_back(coeff);
122  }
123  }
124  if (free_vars.empty()) {
125  const Domain rhs = ReadDomainFromProto(ct.linear());
126  if (!rhs.Contains(fixed_activity)) {
128  }
129  return;
130  }
131 
132  // Fast track for the most common case.
133  const Domain initial_rhs = ReadDomainFromProto(ct.linear());
134  if (free_vars.size() == 1) {
135  const int var = free_vars[0];
136  const Domain domain = initial_rhs.AdditionWith(Domain(-fixed_activity))
137  .InverseMultiplicationBy(free_coeffs[0])
138  .IntersectionWith((*domains)[var]);
139  if (domain.IsEmpty()) {
141  return;
142  }
143  (*domains)[var] = Domain(domain.SmallestValue());
144  return;
145  }
146 
147  // The postsolve code is a bit involved if there is more than one free
148  // variable, we have to postsolve them one by one.
149  //
150  // Here we recompute the same domains as during the presolve. Everything is
151  // like if we where substiting the variable one by one:
152  // terms[i] + fixed_activity \in rhs_domains[i]
153  // In the reverse order.
154  std::vector<Domain> rhs_domains;
155  rhs_domains.push_back(initial_rhs);
156  for (int i = 0; i + 1 < free_vars.size(); ++i) {
157  // Note that these should be exactly the same computation as the one done
158  // during presolve and should be exact. However, we have some tests that do
159  // not comply, so we don't check exactness here. Also, as long as we don't
160  // get empty domain below, and the complexity of the domain do not explode
161  // here, we should be fine.
162  Domain term = (*domains)[free_vars[i]].MultiplicationBy(-free_coeffs[i]);
163  rhs_domains.push_back(term.AdditionWith(rhs_domains.back()));
164  }
165  for (int i = free_vars.size() - 1; i >= 0; --i) {
166  // Choose a value for free_vars[i] that fall into rhs_domains[i] -
167  // fixed_activity. This will crash if the intersection is empty, but it
168  // shouldn't be.
169  const int var = free_vars[i];
170  const int64_t coeff = free_coeffs[i];
171  const Domain domain = rhs_domains[i]
172  .AdditionWith(Domain(-fixed_activity))
173  .InverseMultiplicationBy(coeff)
174  .IntersectionWith((*domains)[var]);
175 
176  // TODO(user): I am not 100% that the algo here might cover all the presolve
177  // case, so if this fail, it might indicate an issue here and not in the
178  // presolve/solver code.
179  CHECK(!domain.IsEmpty()) << ct.ShortDebugString();
180  const int64_t value = domain.SmallestValue();
181  (*domains)[var] = Domain(value);
182 
183  fixed_activity += coeff * value;
184  }
185  DCHECK(initial_rhs.Contains(fixed_activity));
186 }
187 
188 namespace {
189 
190 int64_t EvaluateLinearExpression(const LinearExpressionProto& expr,
191  const std::vector<Domain>& domains) {
192  int64_t value = expr.offset();
193  for (int i = 0; i < expr.vars_size(); ++i) {
194  const int ref = expr.vars(i);
195  const int64_t increment =
196  domains[PositiveRef(expr.vars(i))].FixedValue() * expr.coeffs(i);
197  value += RefIsPositive(ref) ? increment : -increment;
198  }
199  return value;
200 }
201 
202 } // namespace
203 
204 // Compute the max of each expression, and assign it to the target expr (which
205 // must be of the form +ref or -ref);
206 // We only support post-solving the case were the target is unassigned,
207 // but everything else is fixed.
208 void PostsolveLinMax(const ConstraintProto& ct, std::vector<Domain>* domains) {
209  int64_t max_value = std::numeric_limits<int64_t>::min();
210  for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
211  max_value = std::max(max_value, EvaluateLinearExpression(expr, *domains));
212  }
213  const int target_ref = GetSingleRefFromExpression(ct.lin_max().target());
214  const int target_var = PositiveRef(target_ref);
215  (*domains)[target_var] = (*domains)[target_var].IntersectionWith(
216  Domain(RefIsPositive(target_ref) ? max_value : -max_value));
217  CHECK(!(*domains)[target_var].IsEmpty());
218 }
219 
220 // We only support 3 cases in the presolve currently.
221 void PostsolveElement(const ConstraintProto& ct, std::vector<Domain>* domains) {
222  const int index_ref = ct.element().index();
223  const int index_var = PositiveRef(index_ref);
224  const int target_ref = ct.element().target();
225  const int target_var = PositiveRef(target_ref);
226 
227  // Deal with non-fixed target and non-fixed index. This only happen if
228  // whatever the value of the index and selected variable, we can choose a
229  // valid target, so we just fix the index to its min value in this case.
230  if (!(*domains)[target_var].IsFixed() && !(*domains)[index_var].IsFixed()) {
231  const int64_t index_var_value = (*domains)[index_var].Min();
232  (*domains)[index_var] = Domain(index_var_value);
233 
234  // If the selected variable is not fixed, we also need to fix it.
235  const int selected_ref = ct.element().vars(
236  RefIsPositive(index_ref) ? index_var_value : -index_var_value);
237  const int selected_var = PositiveRef(selected_ref);
238  if (!(*domains)[selected_var].IsFixed()) {
239  (*domains)[selected_var] = Domain((*domains)[selected_var].Min());
240  }
241  }
242 
243  // Deal with fixed index.
244  if ((*domains)[index_var].IsFixed()) {
245  const int64_t index_var_value = (*domains)[index_var].FixedValue();
246  const int selected_ref = ct.element().vars(
247  RefIsPositive(index_ref) ? index_var_value : -index_var_value);
248  const int selected_var = PositiveRef(selected_ref);
249  if ((*domains)[selected_var].IsFixed()) {
250  const int64_t selected_value = (*domains)[selected_var].FixedValue();
251  (*domains)[target_var] = (*domains)[target_var].IntersectionWith(
252  Domain(RefIsPositive(target_ref) == RefIsPositive(selected_ref)
253  ? selected_value
254  : -selected_value));
255  DCHECK(!(*domains)[target_var].IsEmpty());
256  } else {
257  const bool same_sign =
258  (selected_var == selected_ref) == (target_var == target_ref);
259  const Domain target_domain = (*domains)[target_var];
260  const Domain selected_domain = same_sign
261  ? (*domains)[selected_var]
262  : (*domains)[selected_var].Negation();
263  const Domain final = target_domain.IntersectionWith(selected_domain);
264  const int64_t value = final.SmallestValue();
265  (*domains)[target_var] =
266  (*domains)[target_var].IntersectionWith(Domain(value));
267  (*domains)[selected_var] = (*domains)[selected_var].IntersectionWith(
268  Domain(same_sign ? value : -value));
269  DCHECK(!(*domains)[target_var].IsEmpty());
270  DCHECK(!(*domains)[selected_var].IsEmpty());
271  }
272  return;
273  }
274 
275  // Deal with fixed target (and constant vars).
276  const int64_t target_value = (*domains)[target_var].FixedValue();
277  int selected_index_value = -1;
278  for (const int64_t v : (*domains)[index_var].Values()) {
279  const int64_t i = index_var == index_ref ? v : -v;
280  if (i < 0 || i >= ct.element().vars_size()) continue;
281 
282  const int ref = ct.element().vars(i);
283  const int var = PositiveRef(ref);
284  const int64_t value = (*domains)[var].FixedValue();
285  if (RefIsPositive(target_ref) == RefIsPositive(ref)) {
286  if (value == target_value) {
287  selected_index_value = i;
288  break;
289  }
290  } else {
291  if (value == -target_value) {
292  selected_index_value = i;
293  break;
294  }
295  }
296  }
297 
298  CHECK_NE(selected_index_value, -1);
299  (*domains)[index_var] = (*domains)[index_var].IntersectionWith(Domain(
300  RefIsPositive(index_ref) ? selected_index_value : -selected_index_value));
301  DCHECK(!(*domains)[index_var].IsEmpty());
302 }
303 
304 void PostsolveResponse(const int64_t num_variables_in_original_model,
305  const CpModelProto& mapping_proto,
306  const std::vector<int>& postsolve_mapping,
307  std::vector<int64_t>* solution) {
308  CHECK_EQ(solution->size(), postsolve_mapping.size());
309 
310  // Read the initial variable domains, either from the fixed solution of the
311  // presolved problems or from the mapping model.
312  std::vector<Domain> domains(mapping_proto.variables_size());
313  for (int i = 0; i < postsolve_mapping.size(); ++i) {
314  CHECK_LE(postsolve_mapping[i], domains.size());
315  domains[postsolve_mapping[i]] = Domain((*solution)[i]);
316  }
317  for (int i = 0; i < domains.size(); ++i) {
318  if (domains[i].IsEmpty()) {
319  domains[i] = ReadDomainFromProto(mapping_proto.variables(i));
320  }
321  CHECK(!domains[i].IsEmpty());
322  }
323 
324  // Process the constraints in reverse order.
325  const int num_constraints = mapping_proto.constraints_size();
326  for (int i = num_constraints - 1; i >= 0; i--) {
327  const ConstraintProto& ct = mapping_proto.constraints(i);
328 
329  // We ignore constraint with an enforcement literal set to false. If the
330  // enforcement is still unclear, we still process this constraint.
331  bool constraint_can_be_ignored = false;
332  for (const int enf : ct.enforcement_literal()) {
333  const int var = PositiveRef(enf);
334  const bool is_false =
335  domains[var].IsFixed() &&
336  RefIsPositive(enf) == (domains[var].FixedValue() == 0);
337  if (is_false) {
338  constraint_can_be_ignored = true;
339  break;
340  }
341  }
342  if (constraint_can_be_ignored) continue;
343 
344  switch (ct.constraint_case()) {
346  PostsolveClause(ct, &domains);
347  break;
349  PostsolveExactlyOne(ct, &domains);
350  break;
352  PostsolveLinear(ct, &domains);
353  break;
355  PostsolveLinMax(ct, &domains);
356  break;
358  PostsolveElement(ct, &domains);
359  break;
360  default:
361  // This should never happen as we control what kind of constraint we
362  // add to the mapping_proto;
363  LOG(FATAL) << "Unsupported constraint: " << ct.ShortDebugString();
364  }
365  }
366 
367  // Fill the response.
368  // Maybe fix some still unfixed variable.
369  solution->clear();
370  CHECK_LE(num_variables_in_original_model, domains.size());
371  for (int i = 0; i < num_variables_in_original_model; ++i) {
372  solution->push_back(domains[i].SmallestValue());
373  }
374 }
375 
376 } // namespace sat
377 } // namespace operations_research
int64_t SmallestValue() const
Returns the value closest to zero.
#define CHECK(condition)
Definition: base/logging.h:495
int64_t min
Definition: alldiff_cst.cc:139
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
const int FATAL
Definition: log_severity.h:32
void PostsolveLinMax(const ConstraintProto &ct, std::vector< Domain > *domains)
#define LOG(severity)
Definition: base/logging.h:420
void PostsolveClause(const ConstraintProto &ct, std::vector< Domain > *domains)
#define CHECK_LT(val1, val2)
Definition: base/logging.h:705
int64_t max
Definition: alldiff_cst.cc:140
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
#define CHECK_LE(val1, val2)
Definition: base/logging.h:704
const ::operations_research::sat::ConstraintProto & constraints(int index) const
void PostsolveResponse(const int64_t num_variables_in_original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, std::vector< int64_t > *solution)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
Definition: integer.h:1665
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
void PostsolveElement(const ConstraintProto &ct, std::vector< Domain > *domains)
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
void PostsolveExactlyOne(const ConstraintProto &ct, std::vector< Domain > *domains)
#define DCHECK(condition)
Definition: base/logging.h:889
We call domain any subset of Int64 = [kint64min, kint64max].
bool Contains(int64_t value) const
Returns true iff value is in Domain.
void SetEnforcementLiteralToFalse(const ConstraintProto &ct, std::vector< Domain > *domains)
Collection of objects used to extend the Constraint Solver library.
bool RefIsPositive(int ref)
IntVar * var
Definition: expr_array.cc:1874
int GetSingleRefFromExpression(const LinearExpressionProto &expr)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool IsEmpty() const
Returns true if this is the empty set.
int64_t value
#define CHECK_NE(val1, val2)
Definition: base/logging.h:703
void PostsolveLinear(const ConstraintProto &ct, std::vector< Domain > *domains)
const Constraint * ct