OR-Tools  9.0
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 
53 void PostsolveExactlyOne(const ConstraintProto& ct,
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 // Here we simply assign all non-fixed variable to a feasible value. Which
83 // should always exists by construction.
84 void PostsolveLinear(const ConstraintProto& ct,
85  const std::vector<bool>& prefer_lower_value,
86  std::vector<Domain>* domains) {
87  int64_t fixed_activity = 0;
88  const int size = ct.linear().vars().size();
89  std::vector<int> free_vars;
90  std::vector<int64_t> free_coeffs;
91  for (int i = 0; i < size; ++i) {
92  const int var = ct.linear().vars(i);
93  const int64_t coeff = ct.linear().coeffs(i);
94  CHECK_LT(var, domains->size());
95  if (coeff == 0) continue;
96  if ((*domains)[var].IsFixed()) {
97  fixed_activity += (*domains)[var].FixedValue() * coeff;
98  } else {
99  free_vars.push_back(var);
100  free_coeffs.push_back(coeff);
101  }
102  }
103  if (free_vars.empty()) return;
104 
105  // Fast track for the most common case.
106  const Domain initial_rhs = ReadDomainFromProto(ct.linear());
107  if (free_vars.size() == 1) {
108  const int var = free_vars[0];
109  const Domain domain = initial_rhs.AdditionWith(Domain(-fixed_activity))
110  .InverseMultiplicationBy(free_coeffs[0])
111  .IntersectionWith((*domains)[var]);
112  const int64_t value = prefer_lower_value[var] ? domain.Min() : domain.Max();
113  (*domains)[var] = Domain(value);
114  return;
115  }
116 
117  // The postsolve code is a bit involved if there is more than one free
118  // variable, we have to postsolve them one by one.
119  //
120  // Here we recompute the same domains as during the presolve. Everything is
121  // like if we where substiting the variable one by one:
122  // terms[i] + fixed_activity \in rhs_domains[i]
123  // In the reverse order.
124  std::vector<Domain> rhs_domains;
125  rhs_domains.push_back(initial_rhs);
126  for (int i = 0; i + 1 < free_vars.size(); ++i) {
127  // Note that these should be exactly the same computation as the one done
128  // during presolve and should be exact. However, we have some tests that do
129  // not comply, so we don't check exactness here. Also, as long as we don't
130  // get empty domain below, and the complexity of the domain do not explode
131  // here, we should be fine.
132  Domain term = (*domains)[free_vars[i]].MultiplicationBy(-free_coeffs[i]);
133  rhs_domains.push_back(term.AdditionWith(rhs_domains.back()));
134  }
135  for (int i = free_vars.size() - 1; i >= 0; --i) {
136  // Choose a value for free_vars[i] that fall into rhs_domains[i] -
137  // fixed_activity. This will crash if the intersection is empty, but it
138  // shouldn't be.
139  const int var = free_vars[i];
140  const int64_t coeff = free_coeffs[i];
141  const Domain domain = rhs_domains[i]
142  .AdditionWith(Domain(-fixed_activity))
144  .IntersectionWith((*domains)[var]);
145 
146  // TODO(user): I am not 100% that the algo here might cover all the presolve
147  // case, so if this fail, it might indicate an issue here and not in the
148  // presolve/solver code.
149  CHECK(!domain.IsEmpty()) << ct.ShortDebugString();
150  const int64_t value = prefer_lower_value[var] ? domain.Min() : domain.Max();
151  (*domains)[var] = Domain(value);
152 
153  fixed_activity += coeff * value;
154  }
155  DCHECK(initial_rhs.Contains(fixed_activity));
156 }
157 
158 // We assign any non fixed lhs variables to their minimum value. Then we assign
159 // the target to the max. This should always be feasible.
160 void PostsolveIntMax(const ConstraintProto& ct, std::vector<Domain>* domains) {
161  int64_t m = std::numeric_limits<int64_t>::min();
162  for (const int ref : ct.int_max().vars()) {
163  const int var = PositiveRef(ref);
164  if (!(*domains)[var].IsFixed()) {
165  // Assign to minimum value.
166  const int64_t value =
167  RefIsPositive(ref) ? (*domains)[var].Min() : (*domains)[var].Max();
168  (*domains)[var] = Domain(value);
169  }
170 
171  const int64_t value = (*domains)[var].FixedValue();
172  m = std::max(m, RefIsPositive(ref) ? value : -value);
173  }
174  const int target_ref = ct.int_max().target();
175  const int target_var = PositiveRef(target_ref);
176  if (RefIsPositive(target_ref)) {
177  (*domains)[target_var] = (*domains)[target_var].IntersectionWith(Domain(m));
178  } else {
179  (*domains)[target_var] =
180  (*domains)[target_var].IntersectionWith(Domain(-m));
181  }
182  CHECK(!(*domains)[target_var].IsEmpty());
183 }
184 
185 // We only support 3 cases in the presolve currently.
186 void PostsolveElement(const ConstraintProto& ct, std::vector<Domain>* domains) {
187  const int index_ref = ct.element().index();
188  const int index_var = PositiveRef(index_ref);
189  const int target_ref = ct.element().target();
190  const int target_var = PositiveRef(target_ref);
191 
192  // Deal with non-fixed target and non-fixed index. This only happen if
193  // whatever the value of the index and selected variable, we can choose a
194  // valid target, so we just fix the index to its min value in this case.
195  if (!(*domains)[target_var].IsFixed() && !(*domains)[index_var].IsFixed()) {
196  const int64_t index_value = (*domains)[index_var].Min();
197  (*domains)[index_var] = Domain(index_value);
198 
199  // If the selected variable is not fixed, we also need to fix it.
200  const int selected_ref = ct.element().vars(
201  RefIsPositive(index_ref) ? index_value : -index_value);
202  const int selected_var = PositiveRef(selected_ref);
203  if (!(*domains)[selected_var].IsFixed()) {
204  (*domains)[selected_var] = Domain((*domains)[selected_var].Min());
205  }
206  }
207 
208  // Deal with fixed index (and constant vars).
209  if ((*domains)[index_var].IsFixed()) {
210  const int64_t index_value = (*domains)[index_var].FixedValue();
211  const int selected_ref = ct.element().vars(
212  RefIsPositive(index_ref) ? index_value : -index_value);
213  const int selected_var = PositiveRef(selected_ref);
214  const int64_t selected_value = (*domains)[selected_var].FixedValue();
215  (*domains)[target_var] = (*domains)[target_var].IntersectionWith(
216  Domain(RefIsPositive(target_ref) == RefIsPositive(selected_ref)
217  ? selected_value
218  : -selected_value));
219  DCHECK(!(*domains)[target_var].IsEmpty());
220  return;
221  }
222 
223  // Deal with fixed target (and constant vars).
224  const int64_t target_value = (*domains)[target_var].FixedValue();
225  int selected_index_value = -1;
226  for (int i = 0; i < ct.element().vars().size(); ++i) {
227  const int ref = ct.element().vars(i);
228  const int var = PositiveRef(ref);
229  const int64_t value = (*domains)[var].FixedValue();
230  if (RefIsPositive(target_ref) == RefIsPositive(ref)) {
231  if (value == target_value) {
232  selected_index_value = i;
233  break;
234  }
235  } else {
236  if (value == -target_value) {
237  selected_index_value = i;
238  break;
239  }
240  }
241  }
242 
243  CHECK_NE(selected_index_value, -1);
244  (*domains)[index_var] = (*domains)[index_var].IntersectionWith(Domain(
245  RefIsPositive(index_var) ? selected_index_value : -selected_index_value));
246  DCHECK(!(*domains)[index_var].IsEmpty());
247 }
248 
249 void PostsolveResponse(const int64_t num_variables_in_original_model,
250  const CpModelProto& mapping_proto,
251  const std::vector<int>& postsolve_mapping,
252  CpSolverResponse* response) {
253  // Map back the sufficient assumptions for infeasibility.
254  for (int& ref :
255  *(response->mutable_sufficient_assumptions_for_infeasibility())) {
256  ref = RefIsPositive(ref) ? postsolve_mapping[ref]
257  : NegatedRef(postsolve_mapping[PositiveRef(ref)]);
258  }
259 
260  // Abort if no solution or something is wrong.
261  if (response->status() != CpSolverStatus::FEASIBLE &&
262  response->status() != CpSolverStatus::OPTIMAL) {
263  return;
264  }
265  if (response->solution_size() != postsolve_mapping.size()) return;
266 
267  // Read the initial variable domains, either from the fixed solution of the
268  // presolved problems or from the mapping model.
269  std::vector<Domain> domains(mapping_proto.variables_size());
270  for (int i = 0; i < postsolve_mapping.size(); ++i) {
271  CHECK_LE(postsolve_mapping[i], domains.size());
272  domains[postsolve_mapping[i]] = Domain(response->solution(i));
273  }
274  for (int i = 0; i < domains.size(); ++i) {
275  if (domains[i].IsEmpty()) {
276  domains[i] = ReadDomainFromProto(mapping_proto.variables(i));
277  }
278  CHECK(!domains[i].IsEmpty());
279  }
280 
281  // Some free variable should be fixed towards their good objective direction.
282  //
283  // TODO(user): currently the objective is not part of the mapping_proto, so
284  // this shouldn't matter for our current presolve reduction.
285  CHECK(!mapping_proto.has_objective());
286  std::vector<bool> prefer_lower_value(domains.size(), true);
287  if (mapping_proto.has_objective()) {
288  const int size = mapping_proto.objective().vars().size();
289  for (int i = 0; i < size; ++i) {
290  int var = mapping_proto.objective().vars(i);
291  int64_t coeff = mapping_proto.objective().coeffs(i);
292  if (!RefIsPositive(var)) {
293  var = PositiveRef(var);
294  coeff = -coeff;
295  }
296  prefer_lower_value[i] = (coeff >= 0);
297  }
298  }
299 
300  // Process the constraints in reverse order.
301  const int num_constraints = mapping_proto.constraints_size();
302  for (int i = num_constraints - 1; i >= 0; i--) {
303  const ConstraintProto& ct = mapping_proto.constraints(i);
304 
305  // We should only encounter assigned enforcement literal.
306  bool enforced = true;
307  for (const int ref : ct.enforcement_literal()) {
308  if (domains[PositiveRef(ref)].FixedValue() ==
309  (RefIsPositive(ref) ? 0 : 1)) {
310  enforced = false;
311  break;
312  }
313  }
314  if (!enforced) continue;
315 
316  switch (ct.constraint_case()) {
317  case ConstraintProto::kBoolOr:
318  PostsolveClause(ct, &domains);
319  break;
320  case ConstraintProto::kExactlyOne:
321  PostsolveExactlyOne(ct, &domains);
322  break;
323  case ConstraintProto::kLinear:
324  PostsolveLinear(ct, prefer_lower_value, &domains);
325  break;
326  case ConstraintProto::kIntMax:
327  PostsolveIntMax(ct, &domains);
328  break;
329  case ConstraintProto::kElement:
330  PostsolveElement(ct, &domains);
331  break;
332  default:
333  // This should never happen as we control what kind of constraint we
334  // add to the mapping_proto;
335  LOG(FATAL) << "Unsupported constraint: " << ct.ShortDebugString();
336  }
337  }
338 
339  // Fill the response. Maybe fix some still unfixed variable.
340  response->mutable_solution()->Clear();
341  CHECK_LE(num_variables_in_original_model, domains.size());
342  for (int i = 0; i < num_variables_in_original_model; ++i) {
343  if (prefer_lower_value[i]) {
344  response->add_solution(domains[i].Min());
345  } else {
346  response->add_solution(domains[i].Max());
347  }
348  }
349 }
350 
351 } // namespace sat
352 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define CHECK_LT(val1, val2)
Definition: base/logging.h:708
#define CHECK_NE(val1, val2)
Definition: base/logging.h:706
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
#define CHECK_LE(val1, val2)
Definition: base/logging.h:707
We call domain any subset of Int64 = [kint64min, kint64max].
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t Max() const
Returns the max value of the domain.
SharedResponseManager * response
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
const int FATAL
Definition: log_severity.h:32
void PostsolveElement(const ConstraintProto &ct, std::vector< Domain > *domains)
bool RefIsPositive(int ref)
void PostsolveResponse(const int64_t num_variables_in_original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, CpSolverResponse *response)
void PostsolveIntMax(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveExactlyOne(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveLinear(const ConstraintProto &ct, const std::vector< bool > &prefer_lower_value, std::vector< Domain > *domains)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void PostsolveClause(const ConstraintProto &ct, std::vector< Domain > *domains)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
Definition: integer.h:1484
Collection of objects used to extend the Constraint Solver library.