OR-Tools  8.1
cp_model_postsolve.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 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 
17 
18 namespace operations_research {
19 namespace sat {
20 
21 // This postsolve is "special". If the clause is not satisfied, we fix the
22 // first literal in the clause to true (even if it was fixed to false). This
23 // allows to handle more complex presolve operations used by the SAT presolver.
24 //
25 // Also, any "free" Boolean should be fixed to some value for the subsequent
26 // postsolve steps.
27 void PostsolveClause(const ConstraintProto& ct, std::vector<Domain>* domains) {
28  const int size = ct.bool_or().literals_size();
29  CHECK_NE(size, 0);
30  bool satisfied = false;
31  for (int i = 0; i < size; ++i) {
32  const int ref = ct.bool_or().literals(i);
33  const int var = PositiveRef(ref);
34  if ((*domains)[var].IsFixed()) {
35  if ((*domains)[var].FixedValue() == (RefIsPositive(ref) ? 1 : 0)) {
36  satisfied = true;
37  }
38  } else {
39  // We still need to assign free variable. Any value should work.
40  (*domains)[PositiveRef(ref)] = Domain(0);
41  }
42  }
43  if (satisfied) return;
44 
45  // Change the value of the first variable (which was chosen at presolve).
46  const int first_ref = ct.bool_or().literals(0);
47  (*domains)[PositiveRef(first_ref)] = Domain(RefIsPositive(first_ref) ? 1 : 0);
48 }
49 
50 // Here we simply assign all non-fixed variable to a feasible value. Which
51 // should always exists by construction.
52 void PostsolveLinear(const ConstraintProto& ct,
53  const std::vector<bool>& prefer_lower_value,
54  std::vector<Domain>* domains) {
55  int64 fixed_activity = 0;
56  const int size = ct.linear().vars().size();
57  std::vector<int> free_vars;
58  std::vector<int64> free_coeffs;
59  for (int i = 0; i < size; ++i) {
60  const int var = ct.linear().vars(i);
61  const int64 coeff = ct.linear().coeffs(i);
62  CHECK_LT(var, domains->size());
63  if (coeff == 0) continue;
64  if ((*domains)[var].IsFixed()) {
65  fixed_activity += (*domains)[var].FixedValue() * coeff;
66  } else {
67  free_vars.push_back(var);
68  free_coeffs.push_back(coeff);
69  }
70  }
71  if (free_vars.empty()) return;
72 
73  Domain rhs =
74  ReadDomainFromProto(ct.linear()).AdditionWith(Domain(-fixed_activity));
75 
76  // Fast track for the most common case.
77  if (free_vars.size() == 1) {
78  const int var = free_vars[0];
79  const Domain domain = rhs.InverseMultiplicationBy(free_coeffs[0])
80  .IntersectionWith((*domains)[var]);
81  const int64 value = prefer_lower_value[var] ? domain.Min() : domain.Max();
82  (*domains)[var] = Domain(value);
83  return;
84  }
85 
86  // The postsolve code is a bit involved if there is more than one free
87  // variable, we have to postsolve them one by one.
88  std::vector<Domain> to_add;
89  to_add.push_back(Domain(0));
90  for (int i = 0; i + 1 < free_vars.size(); ++i) {
91  bool exact = false;
92  Domain term =
93  (*domains)[free_vars[i]].MultiplicationBy(-free_coeffs[i], &exact);
94  CHECK(exact);
95  to_add.push_back(term.AdditionWith(to_add.back()));
96  }
97  for (int i = free_vars.size() - 1; i >= 0; --i) {
98  // Choose a value for free_vars[i] that fall into rhs + to_add[i].
99  // This will crash if the intersection is empty, but it shouldn't be.
100  const int var = free_vars[i];
101  const int64 coeff = free_coeffs[i];
102  const Domain domain = rhs.AdditionWith(to_add[i])
104  .IntersectionWith((*domains)[var]);
105  CHECK(!domain.IsEmpty()) << ct.ShortDebugString();
106  const int64 value = prefer_lower_value[var] ? domain.Min() : domain.Max();
107  (*domains)[var] = Domain(value);
108  rhs = rhs.AdditionWith(Domain(-coeff * value));
109 
110  // Only needed in debug.
111  fixed_activity += coeff * value;
112  }
113  DCHECK(ReadDomainFromProto(ct.linear()).Contains(fixed_activity));
114 }
115 
116 // We assign any non fixed lhs variables to their minimum value. Then we assign
117 // the target to the max. This should always be feasible.
118 void PostsolveIntMax(const ConstraintProto& ct, std::vector<Domain>* domains) {
119  int64 m = kint64min;
120  for (const int ref : ct.int_max().vars()) {
121  const int var = PositiveRef(ref);
122  if (!(*domains)[var].IsFixed()) {
123  // Assign to minimum value.
124  const int64 value =
125  RefIsPositive(ref) ? (*domains)[var].Min() : (*domains)[var].Max();
126  (*domains)[var] = Domain(value);
127  }
128 
129  const int64 value = (*domains)[var].FixedValue();
130  m = std::max(m, RefIsPositive(ref) ? value : -value);
131  }
132  const int target_ref = ct.int_max().target();
133  const int target_var = PositiveRef(target_ref);
134  if (RefIsPositive(target_ref)) {
135  (*domains)[target_var] = (*domains)[target_var].IntersectionWith(Domain(m));
136  } else {
137  (*domains)[target_var] =
138  (*domains)[target_var].IntersectionWith(Domain(-m));
139  }
140  CHECK(!(*domains)[target_var].IsEmpty());
141 }
142 
143 // We only support 3 cases in the presolve currently.
144 void PostsolveElement(const ConstraintProto& ct, std::vector<Domain>* domains) {
145  const int index_ref = ct.element().index();
146  const int index_var = PositiveRef(index_ref);
147  const int target_ref = ct.element().target();
148  const int target_var = PositiveRef(target_ref);
149 
150  // Deal with non-fixed target and non-fixed index. This only happen if
151  // whatever the value of the index and selected variable, we can choose a
152  // valid target, so we just fix the index to its min value in this case.
153  if (!(*domains)[target_var].IsFixed() && !(*domains)[index_var].IsFixed()) {
154  const int64 index_value = (*domains)[index_var].Min();
155  (*domains)[index_var] = Domain(index_value);
156 
157  // If the selected variable is not fixed, we also need to fix it.
158  const int selected_ref = ct.element().vars(
159  RefIsPositive(index_ref) ? index_value : -index_value);
160  const int selected_var = PositiveRef(selected_ref);
161  if (!(*domains)[selected_var].IsFixed()) {
162  (*domains)[selected_var] = Domain((*domains)[selected_var].Min());
163  }
164  }
165 
166  // Deal with fixed index (and constant vars).
167  if ((*domains)[index_var].IsFixed()) {
168  const int64 index_value = (*domains)[index_var].FixedValue();
169  const int selected_ref = ct.element().vars(
170  RefIsPositive(index_ref) ? index_value : -index_value);
171  const int selected_var = PositiveRef(selected_ref);
172  const int64 selected_value = (*domains)[selected_var].FixedValue();
173  (*domains)[target_var] = (*domains)[target_var].IntersectionWith(
174  Domain(RefIsPositive(target_ref) == RefIsPositive(selected_ref)
175  ? selected_value
176  : -selected_value));
177  DCHECK(!(*domains)[target_var].IsEmpty());
178  return;
179  }
180 
181  // Deal with fixed target (and constant vars).
182  const int64 target_value = (*domains)[target_var].FixedValue();
183  int selected_index_value = -1;
184  for (int i = 0; i < ct.element().vars().size(); ++i) {
185  const int ref = ct.element().vars(i);
186  const int var = PositiveRef(ref);
187  const int64 value = (*domains)[var].FixedValue();
188  if (RefIsPositive(target_ref) == RefIsPositive(ref)) {
189  if (value == target_value) {
190  selected_index_value = i;
191  break;
192  }
193  } else {
194  if (value == -target_value) {
195  selected_index_value = i;
196  break;
197  }
198  }
199  }
200 
201  CHECK_NE(selected_index_value, -1);
202  (*domains)[index_var] = (*domains)[index_var].IntersectionWith(Domain(
203  RefIsPositive(index_var) ? selected_index_value : -selected_index_value));
204  DCHECK(!(*domains)[index_var].IsEmpty());
205 }
206 
207 void PostsolveResponse(const int64 num_variables_in_original_model,
208  const CpModelProto& mapping_proto,
209  const std::vector<int>& postsolve_mapping,
210  CpSolverResponse* response) {
211  // Abort if no solution or something is wrong.
212  if (response->status() != CpSolverStatus::FEASIBLE &&
213  response->status() != CpSolverStatus::OPTIMAL) {
214  return;
215  }
216  if (response->solution_size() != postsolve_mapping.size()) return;
217 
218  // Read the initial variable domains, either from the fixed solution of the
219  // presolved problems or from the mapping model.
220  std::vector<Domain> domains(mapping_proto.variables_size());
221  for (int i = 0; i < postsolve_mapping.size(); ++i) {
222  CHECK_LE(postsolve_mapping[i], domains.size());
223  domains[postsolve_mapping[i]] = Domain(response->solution(i));
224  }
225  for (int i = 0; i < domains.size(); ++i) {
226  if (domains[i].IsEmpty()) {
227  domains[i] = ReadDomainFromProto(mapping_proto.variables(i));
228  }
229  CHECK(!domains[i].IsEmpty());
230  }
231 
232  // Some free variable should be fixed towards their good objective direction.
233  //
234  // TODO(user): currently the objective is not part of the mapping_proto, so
235  // this shouldn't matter for our current presolve reduction.
236  CHECK(!mapping_proto.has_objective());
237  std::vector<bool> prefer_lower_value(domains.size(), true);
238  if (mapping_proto.has_objective()) {
239  const int size = mapping_proto.objective().vars().size();
240  for (int i = 0; i < size; ++i) {
241  int var = mapping_proto.objective().vars(i);
242  int64 coeff = mapping_proto.objective().coeffs(i);
243  if (!RefIsPositive(var)) {
244  var = PositiveRef(var);
245  coeff = -coeff;
246  }
247  prefer_lower_value[i] = (coeff >= 0);
248  }
249  }
250 
251  // Process the constraints in reverse order.
252  const int num_constraints = mapping_proto.constraints_size();
253  for (int i = num_constraints - 1; i >= 0; i--) {
254  const ConstraintProto& ct = mapping_proto.constraints(i);
255 
256  // We should only encounter assigned enforcement literal.
257  bool enforced = true;
258  for (const int ref : ct.enforcement_literal()) {
259  if (domains[PositiveRef(ref)].FixedValue() ==
260  (RefIsPositive(ref) ? 0 : 1)) {
261  enforced = false;
262  break;
263  }
264  }
265  if (!enforced) continue;
266 
267  switch (ct.constraint_case()) {
268  case ConstraintProto::kBoolOr:
269  PostsolveClause(ct, &domains);
270  break;
271  case ConstraintProto::kLinear:
272  PostsolveLinear(ct, prefer_lower_value, &domains);
273  break;
274  case ConstraintProto::kIntMax:
275  PostsolveIntMax(ct, &domains);
276  break;
277  case ConstraintProto::kElement:
278  PostsolveElement(ct, &domains);
279  break;
280  default:
281  // This should never happen as we control what kind of constraint we
282  // add to the mapping_proto;
283  LOG(FATAL) << "Unsupported constraint: " << ct.ShortDebugString();
284  }
285  }
286 
287  // Fill the response. Maybe fix some still unfixed variable.
288  response->mutable_solution()->Clear();
289  CHECK_LE(num_variables_in_original_model, domains.size());
290  for (int i = 0; i < num_variables_in_original_model; ++i) {
291  if (prefer_lower_value[i]) {
292  response->add_solution(domains[i].Min());
293  } else {
294  response->add_solution(domains[i].Max());
295  }
296  }
297 }
298 
299 } // namespace sat
300 } // namespace operations_research
var
IntVar * var
Definition: expr_array.cc:1858
cp_model_postsolve.h
response
SharedResponseManager * response
Definition: cp_model_solver.cc:2085
operations_research::sat::FEASIBLE
@ FEASIBLE
Definition: cp_model.pb.h:230
max
int64 max
Definition: alldiff_cst.cc:139
LOG
#define LOG(severity)
Definition: base/logging.h:420
operations_research::Domain::Contains
bool Contains(int64 value) const
Returns true iff value is in Domain.
Definition: sorted_interval_list.cc:221
FATAL
const int FATAL
Definition: log_severity.h:32
operations_research::sat::PostsolveResponse
void PostsolveResponse(const int64 num_variables_in_original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, CpSolverResponse *response)
Definition: cp_model_postsolve.cc:207
operations_research::sat::PostsolveIntMax
void PostsolveIntMax(const ConstraintProto &ct, std::vector< Domain > *domains)
Definition: cp_model_postsolve.cc:118
operations_research::Domain::IsEmpty
bool IsEmpty() const
Returns true if this is the empty set.
Definition: sorted_interval_list.cc:190
operations_research::sat::PostsolveClause
void PostsolveClause(const ConstraintProto &ct, std::vector< Domain > *domains)
Definition: cp_model_postsolve.cc:27
value
int64 value
Definition: demon_profiler.cc:43
CHECK_LT
#define CHECK_LT(val1, val2)
Definition: base/logging.h:700
operations_research
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Definition: dense_doubly_linked_list.h:21
operations_research::Domain::AdditionWith
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
Definition: sorted_interval_list.cc:332
kint64min
static const int64 kint64min
Definition: integral_types.h:60
operations_research::Domain
We call domain any subset of Int64 = [kint64min, kint64max].
Definition: sorted_interval_list.h:81
int64
int64_t int64
Definition: integral_types.h:34
operations_research::Domain::IntersectionWith
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
Definition: sorted_interval_list.cc:282
operations_research::Domain::InverseMultiplicationBy
Domain InverseMultiplicationBy(const int64 coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Definition: sorted_interval_list.cc:433
operations_research::sat::PositiveRef
int PositiveRef(int ref)
Definition: cp_model_utils.h:33
operations_research::sat::PostsolveLinear
void PostsolveLinear(const ConstraintProto &ct, const std::vector< bool > &prefer_lower_value, std::vector< Domain > *domains)
Definition: cp_model_postsolve.cc:52
operations_research::sat::PostsolveElement
void PostsolveElement(const ConstraintProto &ct, std::vector< Domain > *domains)
Definition: cp_model_postsolve.cc:144
ct
const Constraint * ct
Definition: demon_profiler.cc:42
operations_research::sat::OPTIMAL
@ OPTIMAL
Definition: cp_model.pb.h:232
DCHECK
#define DCHECK(condition)
Definition: base/logging.h:884
operations_research::sat::IsFixed
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
Definition: integer.h:1406
CHECK_LE
#define CHECK_LE(val1, val2)
Definition: base/logging.h:699
operations_research::Domain::Min
int64 Min() const
Returns the min value of the domain.
Definition: sorted_interval_list.cc:206
operations_research::sat::RefIsPositive
bool RefIsPositive(int ref)
Definition: cp_model_utils.h:34
CHECK_NE
#define CHECK_NE(val1, val2)
Definition: base/logging.h:698
operations_research::sat::ReadDomainFromProto
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Definition: cp_model_utils.h:102
operations_research::Domain::Max
int64 Max() const
Returns the max value of the domain.
Definition: sorted_interval_list.cc:211
operations_research::Domain::MultiplicationBy
Domain MultiplicationBy(int64 coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Definition: sorted_interval_list.cc:361
CHECK
#define CHECK(condition)
Definition: base/logging.h:495
cp_model_utils.h