OR-Tools  9.6
cp_model_solver.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <cstdlib>
20 #include <deque>
21 #include <functional>
22 #include <limits>
23 #include <memory>
24 #include <random>
25 #include <string>
26 #include <thread>
27 #include <tuple>
28 #include <utility>
29 #include <vector>
30 
31 #include "ortools/base/logging.h"
32 #include "ortools/base/timer.h"
33 #if !defined(__PORTABLE_PLATFORM__)
34 #include "ortools/base/file.h"
35 #include "ortools/base/helpers.h"
36 #include "ortools/base/options.h"
37 #endif // __PORTABLE_PLATFORM__
38 #include "absl/base/thread_annotations.h"
39 #include "absl/container/btree_map.h"
40 #include "absl/container/btree_set.h"
41 #include "absl/container/flat_hash_set.h"
42 #include "absl/flags/flag.h"
43 #include "absl/status/status.h"
44 #include "absl/strings/str_cat.h"
45 #include "absl/strings/str_format.h"
46 #include "absl/strings/str_join.h"
47 #include "absl/strings/str_split.h"
48 #include "absl/strings/string_view.h"
49 #include "absl/synchronization/mutex.h"
50 #include "absl/types/span.h"
51 #include "ortools/base/cleanup.h"
54 #include "ortools/sat/clause.h"
55 #include "ortools/sat/cp_model.pb.h"
65 #include "ortools/sat/cuts.h"
70 #include "ortools/sat/integer.h"
77 #include "ortools/sat/lp_utils.h"
78 #include "ortools/sat/max_hs.h"
79 #include "ortools/sat/model.h"
84 #include "ortools/sat/probing.h"
85 #include "ortools/sat/rins.h"
86 #include "ortools/sat/sat_base.h"
88 #include "ortools/sat/sat_parameters.pb.h"
89 #include "ortools/sat/sat_solver.h"
91 #include "ortools/sat/subsolver.h"
93 #include "ortools/sat/util.h"
94 #include "ortools/util/logging.h"
96 #if !defined(__PORTABLE_PLATFORM__)
97 #include "ortools/util/sigint.h"
98 #endif // __PORTABLE_PLATFORM__
99 #include "ortools/base/version.h"
102 #include "ortools/util/time_limit.h"
103 
104 #if defined(_MSC_VER)
105 ABSL_FLAG(std::string, cp_model_dump_prefix, ".\\",
106  "Prefix filename for all dumped files");
107 #else
108 ABSL_FLAG(std::string, cp_model_dump_prefix, "/tmp/",
109  "Prefix filename for all dumped files");
110 #endif
111 ABSL_FLAG(bool, cp_model_dump_models, false,
112  "DEBUG ONLY. When set to true, SolveCpModel() will dump its model "
113  "protos (original model, presolved model, mapping model) in text "
114  "format to 'FLAGS_cp_model_dump_prefix'{model|presolved_model|"
115  "mapping_model}.pb.txt.");
116 
117 ABSL_FLAG(bool, cp_model_dump_text_proto, true,
118  "DEBUG ONLY, dump models in text proto instead of binary proto.");
119 
120 ABSL_FLAG(bool, cp_model_dump_lns, false,
121  "DEBUG ONLY. When set to true, solve will dump all "
122  "lns models proto in text format to "
123  "'FLAGS_cp_model_dump_prefix'lns_xxx.pb.txt.");
124 
126  bool, cp_model_dump_problematic_lns, false,
127  "DEBUG ONLY. Similar to --cp_model_dump_lns, but only dump fragment for "
128  "which we got an issue while validating the postsolved solution. This "
129  "allows to debug presolve issues without dumping all the models.");
130 
131 ABSL_FLAG(bool, cp_model_dump_response, false,
132  "DEBUG ONLY. If true, the final response of each solve will be "
133  "dumped to 'FLAGS_cp_model_dump_prefix'response.pb.txt");
134 
135 ABSL_FLAG(std::string, cp_model_params, "",
136  "This is interpreted as a text SatParameters proto. The "
137  "specified fields will override the normal ones for all solves.");
138 
139 ABSL_FLAG(std::string, drat_output, "",
140  "If non-empty, a proof in DRAT format will be written to this file. "
141  "This will only be used for pure-SAT problems.");
142 
143 ABSL_FLAG(bool, drat_check, false,
144  "If true, a proof in DRAT format will be stored in memory and "
145  "checked if the problem is UNSAT. This will only be used for "
146  "pure-SAT problems.");
147 
148 ABSL_FLAG(double, max_drat_time_in_seconds,
149  std::numeric_limits<double>::infinity(),
150  "Maximum time in seconds to check the DRAT proof. This will only "
151  "be used is the drat_check flag is enabled.");
152 
153 ABSL_FLAG(bool, cp_model_check_intermediate_solutions, false,
154  "When true, all intermediate solutions found by the solver will be "
155  "checked. This can be expensive, therefore it is off by default.");
156 
158  std::string, cp_model_load_debug_solution, "",
159  "DEBUG ONLY. When this is set to a non-empty file name, "
160  "we will interpret this as an internal solution which can be used for "
161  "debugging. For instance we use it to identify wrong cuts/reasons.");
162 
163 ABSL_FLAG(bool, cp_model_ignore_objective, false,
164  "If true, ignore the objective.");
165 ABSL_FLAG(bool, cp_model_fingerprint_model, true, "Fingerprint the model.");
166 
167 namespace operations_research {
168 namespace sat {
169 
170 std::string CpSatSolverVersion() {
171  return absl::StrCat("CP-SAT solver v", OrToolsVersionString());
172 }
173 
174 namespace {
175 
176 // Makes the string fit in one line by cutting it in the middle if necessary.
177 std::string Summarize(const std::string& input) {
178  if (input.size() < 105) return input;
179  const int half = 50;
180  return absl::StrCat(input.substr(0, half), " ... ",
181  input.substr(input.size() - half, half));
182 }
183 
184 template <class M>
185 void DumpModelProto(const M& proto, const std::string& name) {
186  std::string filename;
187  if (absl::GetFlag(FLAGS_cp_model_dump_text_proto)) {
188  filename = absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix), name,
189  ".pb.txt");
190  LOG(INFO) << "Dumping " << name << " text proto to '" << filename << "'.";
191  } else {
192  const std::string filename =
193  absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix), name, ".bin");
194  LOG(INFO) << "Dumping " << name << " binary proto to '" << filename << "'.";
195  }
196  CHECK(WriteModelProtoToFile(proto, filename));
197 }
198 
199 } // namespace.
200 
201 // =============================================================================
202 // Public API.
203 // =============================================================================
204 
205 std::string CpModelStats(const CpModelProto& model_proto) {
206  absl::btree_map<std::string, int> num_constraints_by_name;
207  absl::btree_map<std::string, int> num_reif_constraints_by_name;
208  absl::btree_map<std::string, int> num_multi_reif_constraints_by_name;
209  absl::btree_map<std::string, int> name_to_num_literals;
210  absl::btree_map<std::string, int> name_to_num_terms;
211  absl::btree_map<std::string, int> name_to_num_complex_domain;
212  absl::btree_map<std::string, int> name_to_num_expressions;
213 
214  int no_overlap_2d_num_rectangles = 0;
215  int no_overlap_2d_num_optional_rectangles = 0;
216  int no_overlap_2d_num_linear_areas = 0;
217  int no_overlap_2d_num_quadratic_areas = 0;
218 
219  int cumulative_num_intervals = 0;
220  int cumulative_num_optional_intervals = 0;
221  int cumulative_num_variable_sizes = 0;
222  int cumulative_num_variable_demands = 0;
223 
224  int no_overlap_num_intervals = 0;
225  int no_overlap_num_optional_intervals = 0;
226  int no_overlap_num_variable_sizes = 0;
227 
228  for (const ConstraintProto& ct : model_proto.constraints()) {
229  std::string name = ConstraintCaseName(ct.constraint_case());
230 
231  // We split the linear constraints into 3 buckets has it gives more insight
232  // on the type of problem we are facing.
233  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
234  if (ct.linear().vars_size() == 0) name += "0";
235  if (ct.linear().vars_size() == 1) name += "1";
236  if (ct.linear().vars_size() == 2) name += "2";
237  if (ct.linear().vars_size() == 3) name += "3";
238  if (ct.linear().vars_size() > 3) name += "N";
239  }
240 
241  num_constraints_by_name[name]++;
242  if (!ct.enforcement_literal().empty()) {
243  num_reif_constraints_by_name[name]++;
244  if (ct.enforcement_literal().size() > 1) {
245  num_multi_reif_constraints_by_name[name]++;
246  }
247  }
248 
249  auto variable_is_fixed = [&model_proto](int ref) {
250  const IntegerVariableProto& proto =
251  model_proto.variables(PositiveRef(ref));
252  return proto.domain_size() == 2 && proto.domain(0) == proto.domain(1);
253  };
254 
255  auto expression_is_fixed =
256  [&variable_is_fixed](const LinearExpressionProto& expr) {
257  for (const int ref : expr.vars()) {
258  if (!variable_is_fixed(ref)) {
259  return false;
260  }
261  }
262  return true;
263  };
264 
265  auto interval_has_fixed_size = [&model_proto, &expression_is_fixed](int c) {
266  return expression_is_fixed(model_proto.constraints(c).interval().size());
267  };
268 
269  auto constraint_is_optional = [&model_proto](int i) {
270  return !model_proto.constraints(i).enforcement_literal().empty();
271  };
272 
273  // For pure Boolean constraints, we also display the total number of literal
274  // involved as this gives a good idea of the problem size.
275  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
276  name_to_num_literals[name] += ct.bool_or().literals().size();
277  } else if (ct.constraint_case() ==
278  ConstraintProto::ConstraintCase::kBoolAnd) {
279  name_to_num_literals[name] +=
280  ct.enforcement_literal().size() + ct.bool_and().literals().size();
281  } else if (ct.constraint_case() ==
282  ConstraintProto::ConstraintCase::kAtMostOne) {
283  name_to_num_literals[name] += ct.at_most_one().literals().size();
284  } else if (ct.constraint_case() ==
285  ConstraintProto::ConstraintCase::kExactlyOne) {
286  name_to_num_literals[name] += ct.exactly_one().literals().size();
287  } else if (ct.constraint_case() ==
288  ConstraintProto::ConstraintCase::kLinMax) {
289  name_to_num_expressions[name] += ct.lin_max().exprs().size();
290  } else if (ct.constraint_case() ==
291  ConstraintProto::ConstraintCase::kNoOverlap2D) {
292  const int num_boxes = ct.no_overlap_2d().x_intervals_size();
293  no_overlap_2d_num_rectangles += num_boxes;
294  for (int i = 0; i < num_boxes; ++i) {
295  const int x_interval = ct.no_overlap_2d().x_intervals(i);
296  const int y_interval = ct.no_overlap_2d().y_intervals(i);
297  if (constraint_is_optional(x_interval) ||
298  constraint_is_optional(y_interval)) {
299  no_overlap_2d_num_optional_rectangles++;
300  }
301  const int num_fixed = interval_has_fixed_size(x_interval) +
302  interval_has_fixed_size(y_interval);
303  if (num_fixed == 0) {
304  no_overlap_2d_num_quadratic_areas++;
305  } else if (num_fixed == 1) {
306  no_overlap_2d_num_linear_areas++;
307  }
308  }
309  } else if (ct.constraint_case() ==
310  ConstraintProto::ConstraintCase::kNoOverlap) {
311  const int num_intervals = ct.no_overlap().intervals_size();
312  no_overlap_num_intervals += num_intervals;
313  for (int i = 0; i < num_intervals; ++i) {
314  const int interval = ct.no_overlap().intervals(i);
315  if (constraint_is_optional(interval)) {
316  no_overlap_num_optional_intervals++;
317  }
318  if (!interval_has_fixed_size(interval)) {
319  no_overlap_num_variable_sizes++;
320  }
321  }
322  } else if (ct.constraint_case() ==
323  ConstraintProto::ConstraintCase::kCumulative) {
324  const int num_intervals = ct.cumulative().intervals_size();
325  cumulative_num_intervals += num_intervals;
326  for (int i = 0; i < num_intervals; ++i) {
327  const int interval = ct.cumulative().intervals(i);
328  if (constraint_is_optional(interval)) {
329  cumulative_num_optional_intervals++;
330  }
331  if (!interval_has_fixed_size(interval)) {
332  cumulative_num_variable_sizes++;
333  }
334  if (!expression_is_fixed(ct.cumulative().demands(i))) {
335  cumulative_num_variable_demands++;
336  }
337  }
338  }
339 
340  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
341  ct.linear().vars_size() > 3) {
342  name_to_num_terms[name] += ct.linear().vars_size();
343  }
344  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
345  ct.linear().vars_size() > 1 && ct.linear().domain().size() > 2) {
346  name_to_num_complex_domain[name]++;
347  }
348  }
349 
350  int num_constants = 0;
351  absl::btree_set<int64_t> constant_values;
352  absl::btree_map<Domain, int> num_vars_per_domains;
353  for (const IntegerVariableProto& var : model_proto.variables()) {
354  if (var.domain_size() == 2 && var.domain(0) == var.domain(1)) {
355  ++num_constants;
356  constant_values.insert(var.domain(0));
357  } else {
358  num_vars_per_domains[ReadDomainFromProto(var)]++;
359  }
360  }
361 
362  std::string result;
363  const std::string model_fingerprint_str =
364  (absl::GetFlag(FLAGS_cp_model_fingerprint_model))
365  ? absl::StrFormat(" (model_fingerprint: %#x)",
367  : "";
368 
369  if (model_proto.has_objective() ||
370  model_proto.has_floating_point_objective()) {
371  absl::StrAppend(&result, "optimization model '", model_proto.name(),
372  "':", model_fingerprint_str, "\n");
373  } else {
374  absl::StrAppend(&result, "satisfaction model '", model_proto.name(),
375  "':", model_fingerprint_str, "\n");
376  }
377 
378  for (const DecisionStrategyProto& strategy : model_proto.search_strategy()) {
379  absl::StrAppend(
380  &result, "Search strategy: on ", strategy.variables_size(),
381  " variables, ",
382  ProtoEnumToString<DecisionStrategyProto::VariableSelectionStrategy>(
383  strategy.variable_selection_strategy()),
384  ", ",
385  ProtoEnumToString<DecisionStrategyProto::DomainReductionStrategy>(
386  strategy.domain_reduction_strategy()),
387  "\n");
388  }
389 
390  auto count_variables_by_type =
391  [&model_proto](const google::protobuf::RepeatedField<int>& vars,
392  int* num_booleans, int* num_integers) {
393  for (const int ref : vars) {
394  const auto& var_proto = model_proto.variables(PositiveRef(ref));
395  if (var_proto.domain_size() == 2 && var_proto.domain(0) == 0 &&
396  var_proto.domain(1) == 1) {
397  (*num_booleans)++;
398  }
399  }
400  *num_integers = vars.size() - *num_booleans;
401  };
402 
403  {
404  int num_boolean_variables_in_objective = 0;
405  int num_integer_variables_in_objective = 0;
406  if (model_proto.has_objective()) {
407  count_variables_by_type(model_proto.objective().vars(),
408  &num_boolean_variables_in_objective,
409  &num_integer_variables_in_objective);
410  }
411  if (model_proto.has_floating_point_objective()) {
412  count_variables_by_type(model_proto.floating_point_objective().vars(),
413  &num_boolean_variables_in_objective,
414  &num_integer_variables_in_objective);
415  }
416 
417  std::vector<std::string> obj_vars_strings;
418  if (num_boolean_variables_in_objective > 0) {
419  obj_vars_strings.push_back(
420  absl::StrCat("#bools:", num_boolean_variables_in_objective));
421  }
422  if (num_integer_variables_in_objective > 0) {
423  obj_vars_strings.push_back(
424  absl::StrCat("#ints:", num_integer_variables_in_objective));
425  }
426 
427  const std::string objective_string =
428  model_proto.has_objective()
429  ? absl::StrCat(" (", absl::StrJoin(obj_vars_strings, " "),
430  " in objective)")
431  : (model_proto.has_floating_point_objective()
432  ? absl::StrCat(" (", absl::StrJoin(obj_vars_strings, " "),
433  " in floating point objective)")
434  : "");
435  absl::StrAppend(&result, "#Variables: ", model_proto.variables_size(),
436  objective_string, "\n");
437  }
438  if (num_vars_per_domains.contains(Domain(0, 1))) {
439  // We always list Boolean first.
440  const int num_bools = num_vars_per_domains[Domain(0, 1)];
441  const std::string temp = absl::StrCat(" - ", num_bools, " Booleans in ",
442  Domain(0, 1).ToString(), "\n");
443  absl::StrAppend(&result, Summarize(temp));
444  num_vars_per_domains.erase(Domain(0, 1));
445  }
446  if (num_vars_per_domains.size() < 100) {
447  for (const auto& entry : num_vars_per_domains) {
448  const std::string temp = absl::StrCat(" - ", entry.second, " in ",
449  entry.first.ToString(), "\n");
450  absl::StrAppend(&result, Summarize(temp));
451  }
452  } else {
453  int64_t max_complexity = 0;
456  for (const auto& entry : num_vars_per_domains) {
457  min = std::min(min, entry.first.Min());
458  max = std::max(max, entry.first.Max());
459  max_complexity = std::max(
460  max_complexity, static_cast<int64_t>(entry.first.NumIntervals()));
461  }
462  absl::StrAppend(&result, " - ", num_vars_per_domains.size(),
463  " different domains in [", min, ",", max,
464  "] with a largest complexity of ", max_complexity, ".\n");
465  }
466 
467  if (num_constants > 0) {
468  const std::string temp =
469  absl::StrCat(" - ", num_constants, " constants in {",
470  absl::StrJoin(constant_values, ","), "} \n");
471  absl::StrAppend(&result, Summarize(temp));
472  }
473 
474  std::vector<std::string> constraints;
475  constraints.reserve(num_constraints_by_name.size());
476  for (const auto& entry : num_constraints_by_name) {
477  const std::string& name = entry.first;
478  constraints.push_back(absl::StrCat("#", name, ": ", entry.second));
479  if (num_reif_constraints_by_name.contains(name)) {
480  if (num_multi_reif_constraints_by_name.contains(name)) {
481  absl::StrAppend(&constraints.back(),
482  " (#enforced: ", num_reif_constraints_by_name[name],
483  " #multi: ", num_multi_reif_constraints_by_name[name],
484  ")");
485  } else {
486  absl::StrAppend(&constraints.back(),
487  " (#enforced: ", num_reif_constraints_by_name[name],
488  ")");
489  }
490  }
491  if (name_to_num_literals.contains(name)) {
492  absl::StrAppend(&constraints.back(),
493  " (#literals: ", name_to_num_literals[name], ")");
494  }
495  if (name_to_num_terms.contains(name)) {
496  absl::StrAppend(&constraints.back(),
497  " (#terms: ", name_to_num_terms[name], ")");
498  }
499  if (name_to_num_expressions.contains(name)) {
500  absl::StrAppend(&constraints.back(),
501  " (#expressions: ", name_to_num_expressions[name], ")");
502  }
503  if (name_to_num_complex_domain.contains(name)) {
504  absl::StrAppend(&constraints.back(),
505  " (#complex_domain: ", name_to_num_complex_domain[name],
506  ")");
507  }
508  if (name == "kNoOverlap2D") {
509  absl::StrAppend(&constraints.back(),
510  " (#rectangles: ", no_overlap_2d_num_rectangles);
511  if (no_overlap_2d_num_optional_rectangles > 0) {
512  absl::StrAppend(&constraints.back(),
513  ", #optional: ", no_overlap_2d_num_optional_rectangles);
514  }
515  if (no_overlap_2d_num_linear_areas > 0) {
516  absl::StrAppend(&constraints.back(),
517  ", #linear_areas: ", no_overlap_2d_num_linear_areas);
518  }
519  if (no_overlap_2d_num_quadratic_areas > 0) {
520  absl::StrAppend(&constraints.back(), ", #quadratic_areas: ",
521  no_overlap_2d_num_quadratic_areas);
522  }
523  absl::StrAppend(&constraints.back(), ")");
524  } else if (name == "kCumulative") {
525  absl::StrAppend(&constraints.back(),
526  " (#intervals: ", cumulative_num_intervals);
527  if (cumulative_num_optional_intervals > 0) {
528  absl::StrAppend(&constraints.back(),
529  ", #optional: ", cumulative_num_optional_intervals);
530  }
531  if (cumulative_num_variable_sizes > 0) {
532  absl::StrAppend(&constraints.back(),
533  ", #variable_sizes: ", cumulative_num_variable_sizes);
534  }
535  if (cumulative_num_variable_demands > 0) {
536  absl::StrAppend(&constraints.back(), ", #variable_demands: ",
537  cumulative_num_variable_demands);
538  }
539  absl::StrAppend(&constraints.back(), ")");
540  } else if (name == "kNoOverlap") {
541  absl::StrAppend(&constraints.back(),
542  " (#intervals: ", no_overlap_num_intervals);
543  if (no_overlap_num_optional_intervals > 0) {
544  absl::StrAppend(&constraints.back(),
545  ", #optional: ", no_overlap_num_optional_intervals);
546  }
547  if (no_overlap_num_variable_sizes > 0) {
548  absl::StrAppend(&constraints.back(),
549  ", #variable_sizes: ", no_overlap_num_variable_sizes);
550  }
551  absl::StrAppend(&constraints.back(), ")");
552  }
553  }
554  std::sort(constraints.begin(), constraints.end());
555  absl::StrAppend(&result, absl::StrJoin(constraints, "\n"));
556 
557  return result;
558 }
559 
560 std::string CpSolverResponseStats(const CpSolverResponse& response,
561  bool has_objective) {
562  std::string result;
563  absl::StrAppend(&result, "CpSolverResponse summary:");
564  absl::StrAppend(&result, "\nstatus: ",
565  ProtoEnumToString<CpSolverStatus>(response.status()));
566 
567  if (has_objective && response.status() != CpSolverStatus::INFEASIBLE) {
568  absl::StrAppendFormat(&result, "\nobjective: %.16g",
569  response.objective_value());
570  absl::StrAppendFormat(&result, "\nbest_bound: %.16g",
571  response.best_objective_bound());
572  } else {
573  absl::StrAppend(&result, "\nobjective: NA");
574  absl::StrAppend(&result, "\nbest_bound: NA");
575  }
576 
577  absl::StrAppend(&result, "\nintegers: ", response.num_integers());
578  absl::StrAppend(&result, "\nbooleans: ", response.num_booleans());
579  absl::StrAppend(&result, "\nconflicts: ", response.num_conflicts());
580  absl::StrAppend(&result, "\nbranches: ", response.num_branches());
581 
582  // TODO(user): This is probably better named "binary_propagation", but we just
583  // output "propagations" to be consistent with sat/analyze.sh.
584  absl::StrAppend(&result,
585  "\npropagations: ", response.num_binary_propagations());
586  absl::StrAppend(
587  &result, "\ninteger_propagations: ", response.num_integer_propagations());
588 
589  absl::StrAppend(&result, "\nrestarts: ", response.num_restarts());
590  absl::StrAppend(&result, "\nlp_iterations: ", response.num_lp_iterations());
591  absl::StrAppend(&result, "\nwalltime: ", response.wall_time());
592  absl::StrAppend(&result, "\nusertime: ", response.user_time());
593  absl::StrAppend(&result,
594  "\ndeterministic_time: ", response.deterministic_time());
595  absl::StrAppend(&result, "\ngap_integral: ", response.gap_integral());
596  if (!response.solution().empty()) {
597  absl::StrAppendFormat(
598  &result, "\nsolution_fingerprint: %#x",
600  }
601  absl::StrAppend(&result, "\n");
602  return result;
603 }
604 
605 namespace {
606 
607 #if !defined(__PORTABLE_PLATFORM__)
608 #endif // __PORTABLE_PLATFORM__
609 
610 // This should be called on the presolved model. It will read the file
611 // specified by --cp_model_load_debug_solution and properly fill the
612 // model->Get<DebugSolution>() proto vector.
613 void LoadDebugSolution(const CpModelProto& model_proto, Model* model) {
614 #if !defined(__PORTABLE_PLATFORM__)
615  if (absl::GetFlag(FLAGS_cp_model_load_debug_solution).empty()) return;
616 
617  CpSolverResponse response;
618  SOLVER_LOG(model->GetOrCreate<SolverLogger>(),
619  "Reading debug solution from '",
620  absl::GetFlag(FLAGS_cp_model_load_debug_solution), "'.");
621  CHECK_OK(file::GetTextProto(absl::GetFlag(FLAGS_cp_model_load_debug_solution),
622  &response, file::Defaults()));
623 
624  // Make sure we load a solution with the same number of variable has in the
625  // presolved model.
626  CHECK_EQ(response.solution().size(), model_proto.variables().size());
627  model->GetOrCreate<SharedResponseManager>()->LoadDebugSolution(
628  response.solution());
629 #endif // __PORTABLE_PLATFORM__
630 }
631 
632 // This both copy the "main" DebugSolution to a local_model and also cache
633 // the value of the integer variables in that solution.
634 void InitializeDebugSolution(const CpModelProto& model_proto, Model* model) {
635  auto* shared_response = model->Get<SharedResponseManager>();
636  if (shared_response == nullptr) return;
637  if (shared_response->DebugSolution().empty()) return;
638 
639  // Copy the proto values.
640  DebugSolution& debug_sol = *model->GetOrCreate<DebugSolution>();
641  debug_sol.proto_values = shared_response->DebugSolution();
642 
643  // Fill the values by integer variable.
644  const int num_integers =
645  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value();
646  debug_sol.ivar_has_value.assign(num_integers, false);
647  debug_sol.ivar_values.assign(num_integers, 0);
648 
649  const auto& mapping = *model->GetOrCreate<CpModelMapping>();
650  for (int i = 0; i < debug_sol.proto_values.size(); ++i) {
651  if (!mapping.IsInteger(i)) continue;
652  const IntegerVariable var = mapping.Integer(i);
653  debug_sol.ivar_has_value[var] = true;
654  debug_sol.ivar_has_value[NegationOf(var)] = true;
655  debug_sol.ivar_values[var] = debug_sol.proto_values[i];
656  debug_sol.ivar_values[NegationOf(var)] = -debug_sol.proto_values[i];
657  }
658 
659  // The objective variable is usually not part of the proto, but it is still
660  // nice to have it, so we recompute it here.
661  auto* objective_def = model->Get<ObjectiveDefinition>();
662  if (objective_def != nullptr) {
663  const IntegerVariable objective_var = objective_def->objective_var;
664  const int64_t objective_value =
665  ComputeInnerObjective(model_proto.objective(), debug_sol.proto_values);
666  debug_sol.ivar_has_value[objective_var] = true;
667  debug_sol.ivar_has_value[NegationOf(objective_var)] = true;
668  debug_sol.ivar_values[objective_var] = objective_value;
669  debug_sol.ivar_values[NegationOf(objective_var)] = -objective_value;
670  }
671 
672  // We also register a DEBUG callback to check our reasons.
673  auto* encoder = model->GetOrCreate<IntegerEncoder>();
674  const auto checker = [mapping, encoder, debug_sol, model](
675  absl::Span<const Literal> clause,
676  absl::Span<const IntegerLiteral> integers) {
677  bool is_satisfied = false;
678  int num_bools = 0;
679  int num_ints = 0;
680  std::vector<std::tuple<Literal, IntegerLiteral, int>> to_print;
681  for (const Literal l : clause) {
682  // First case, this Boolean is mapped.
683  {
684  const int proto_var =
685  mapping.GetProtoVariableFromBooleanVariable(l.Variable());
686  if (proto_var != -1) {
687  to_print.push_back({l, IntegerLiteral(), proto_var});
688  if (debug_sol.proto_values[proto_var] == (l.IsPositive() ? 1 : 0)) {
689  is_satisfied = true;
690  break;
691  }
692  ++num_bools;
693  continue;
694  }
695  }
696 
697  // Second case, it is associated to IntVar >= value.
698  // We can use any of them, so if one is false, we use this one.
699  bool all_true = true;
700  for (const IntegerLiteral associated : encoder->GetIntegerLiterals(l)) {
701  const int proto_var = mapping.GetProtoVariableFromIntegerVariable(
702  PositiveVariable(associated.var));
703  if (proto_var == -1) break;
704  int64_t value = debug_sol.proto_values[proto_var];
705  to_print.push_back({l, associated, proto_var});
706 
707  if (!VariableIsPositive(associated.var)) value = -value;
708  if (value < associated.bound) {
709  ++num_ints;
710  all_true = false;
711  break;
712  }
713  }
714  if (all_true) {
715  is_satisfied = true;
716  break;
717  }
718  }
719  for (const IntegerLiteral i_lit : integers) {
720  const int proto_var = mapping.GetProtoVariableFromIntegerVariable(
721  PositiveVariable(i_lit.var));
722  if (proto_var == -1) {
723  is_satisfied = true;
724  break;
725  }
726 
727  int64_t value = debug_sol.proto_values[proto_var];
728  to_print.push_back({Literal(kNoLiteralIndex), i_lit, proto_var});
729 
730  if (!VariableIsPositive(i_lit.var)) value = -value;
731  // Note the sign is inversed, we cannot have all literal false and all
732  // integer literal true.
733  if (value >= i_lit.bound) {
734  is_satisfied = true;
735  break;
736  }
737  }
738  if (!is_satisfied) {
739  LOG(INFO) << "Reason clause is not satisfied by loaded solution:";
740  LOG(INFO) << "Worker '" << model->Name() << "', level="
741  << model->GetOrCreate<SatSolver>()->CurrentDecisionLevel();
742  LOG(INFO) << "literals (neg): " << clause;
743  LOG(INFO) << "integer literals: " << integers;
744  for (const auto [l, i_lit, proto_var] : to_print) {
745  LOG(INFO) << l << " " << i_lit << " var=" << proto_var
746  << " value_in_sol=" << debug_sol.proto_values[proto_var];
747  }
748  }
749  return is_satisfied;
750  };
751  const auto lit_checker = [checker](absl::Span<const Literal> clause) {
752  return checker(clause, {});
753  };
754 
755  model->GetOrCreate<Trail>()->RegisterDebugChecker(lit_checker);
756  model->GetOrCreate<IntegerTrail>()->RegisterDebugChecker(checker);
757 }
758 
759 std::vector<int64_t> GetSolutionValues(const CpModelProto& model_proto,
760  const Model& model) {
761  auto* mapping = model.Get<CpModelMapping>();
762  auto* trail = model.Get<Trail>();
763 
764  std::vector<int64_t> solution;
765  for (int i = 0; i < model_proto.variables_size(); ++i) {
766  if (mapping->IsInteger(i)) {
767  const IntegerVariable var = mapping->Integer(i);
768 
769  // For ignored or not fully instantiated variable, we just use the
770  // lower bound.
771  solution.push_back(model.Get(LowerBound(var)));
772  } else {
773  DCHECK(mapping->IsBoolean(i));
774  const Literal literal = mapping->Literal(i);
775  if (trail->Assignment().LiteralIsAssigned(literal)) {
776  solution.push_back(model.Get(Value(literal)));
777  } else {
778  // Just use the lower bound if the variable is not fully instantiated.
779  solution.push_back(0);
780  }
781  }
782  }
783 
784  if (DEBUG_MODE ||
785  absl::GetFlag(FLAGS_cp_model_check_intermediate_solutions)) {
786  // TODO(user): Checks against initial model.
787  CHECK(SolutionIsFeasible(model_proto, solution));
788  }
789  return solution;
790 }
791 
792 namespace {
793 
794 IntegerVariable GetOrCreateVariableWithTightBound(
795  const std::vector<std::pair<IntegerVariable, int64_t>>& terms,
796  Model* model) {
797  if (terms.empty()) return model->Add(ConstantIntegerVariable(0));
798  if (terms.size() == 1 && terms.front().second == 1) {
799  return terms.front().first;
800  }
801  if (terms.size() == 1 && terms.front().second == -1) {
802  return NegationOf(terms.front().first);
803  }
804 
805  int64_t sum_min = 0;
806  int64_t sum_max = 0;
807  for (const std::pair<IntegerVariable, int64_t>& var_coeff : terms) {
808  const int64_t min_domain = model->Get(LowerBound(var_coeff.first));
809  const int64_t max_domain = model->Get(UpperBound(var_coeff.first));
810  const int64_t coeff = var_coeff.second;
811  const int64_t prod1 = min_domain * coeff;
812  const int64_t prod2 = max_domain * coeff;
813  sum_min += std::min(prod1, prod2);
814  sum_max += std::max(prod1, prod2);
815  }
816  return model->Add(NewIntegerVariable(sum_min, sum_max));
817 }
818 
819 IntegerVariable GetOrCreateVariableLinkedToSumOf(
820  const std::vector<std::pair<IntegerVariable, int64_t>>& terms,
821  bool use_equality, Model* model) {
822  if (terms.empty()) return model->Add(ConstantIntegerVariable(0));
823  if (terms.size() == 1 && terms.front().second == 1) {
824  return terms.front().first;
825  }
826  if (terms.size() == 1 && terms.front().second == -1) {
827  return NegationOf(terms.front().first);
828  }
829 
830  // Add var == terms or var >= terms if use_equality = false.
831  const IntegerVariable new_var =
832  GetOrCreateVariableWithTightBound(terms, model);
833 
834  // TODO(user): use the same format, i.e. LinearExpression in both code!
835  std::vector<IntegerVariable> vars;
836  std::vector<int64_t> coeffs;
837  for (const auto [var, coeff] : terms) {
838  vars.push_back(var);
839  coeffs.push_back(coeff);
840  }
841  vars.push_back(new_var);
842  coeffs.push_back(-1);
843 
844  // We want == 0 or <= 0 if use_equality = false.
845  const bool lb_required = use_equality;
846  const bool ub_required = true;
847  SplitAndLoadIntermediateConstraints(lb_required, ub_required, &vars, &coeffs,
848  model);
849 
850  // Load the top-level constraint.
851  if (lb_required) {
852  model->Add(WeightedSumGreaterOrEqual(vars, coeffs, 0));
853  }
854  if (ub_required) {
855  model->Add(WeightedSumLowerOrEqual(vars, coeffs, 0));
856  }
857 
858  return new_var;
859 }
860 
861 } // namespace
862 
863 // Adds one LinearProgrammingConstraint per connected component of the model.
864 IntegerVariable AddLPConstraints(bool objective_need_to_be_tight,
865  const CpModelProto& model_proto, Model* m) {
866  const LinearRelaxation relaxation = ComputeLinearRelaxation(model_proto, m);
867 
868  // The bipartite graph of LP constraints might be disconnected:
869  // make a partition of the variables into connected components.
870  // Constraint nodes are indexed by [0..num_lp_constraints),
871  // variable nodes by [num_lp_constraints..num_lp_constraints+num_variables).
872  //
873  // TODO(user): look into biconnected components.
874  const int num_lp_constraints = relaxation.linear_constraints.size();
875  const int num_lp_cut_generators = relaxation.cut_generators.size();
876  const int num_integer_variables =
877  m->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value();
878 
880  components.SetNumberOfNodes(num_lp_constraints + num_lp_cut_generators +
881  num_integer_variables);
882  auto get_constraint_index = [](int ct_index) { return ct_index; };
883  auto get_cut_generator_index = [num_lp_constraints](int cut_index) {
884  return num_lp_constraints + cut_index;
885  };
886  auto get_var_index = [num_lp_constraints,
887  num_lp_cut_generators](IntegerVariable var) {
888  return num_lp_constraints + num_lp_cut_generators +
889  PositiveVariable(var).value();
890  };
891  for (int i = 0; i < num_lp_constraints; i++) {
892  for (const IntegerVariable var : relaxation.linear_constraints[i].vars) {
893  components.AddEdge(get_constraint_index(i), get_var_index(var));
894  }
895  }
896  for (int i = 0; i < num_lp_cut_generators; ++i) {
897  for (const IntegerVariable var : relaxation.cut_generators[i].vars) {
898  components.AddEdge(get_cut_generator_index(i), get_var_index(var));
899  }
900  }
901 
902  const int num_components = components.GetNumberOfComponents();
903  std::vector<int> component_sizes(num_components, 0);
904  const std::vector<int> index_to_component = components.GetComponentIds();
905  for (int i = 0; i < num_lp_constraints; i++) {
906  ++component_sizes[index_to_component[get_constraint_index(i)]];
907  }
908  for (int i = 0; i < num_lp_cut_generators; i++) {
909  ++component_sizes[index_to_component[get_cut_generator_index(i)]];
910  }
911 
912  // TODO(user): Optimize memory layout.
913  std::vector<std::vector<IntegerVariable>> component_to_var(num_components);
914  for (IntegerVariable var(0); var < num_integer_variables; var += 2) {
915  DCHECK(VariableIsPositive(var));
916  component_to_var[index_to_component[get_var_index(var)]].push_back(var);
917  }
918 
919  // Make sure any constraint that touch the objective is not discarded even
920  // if it is the only one in its component. This is important to propagate
921  // as much as possible the objective bound by using any bounds the LP give
922  // us on one of its components. This is critical on the zephyrus problems for
923  // instance.
924  auto* mapping = m->GetOrCreate<CpModelMapping>();
925  for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
926  const IntegerVariable var =
927  mapping->Integer(model_proto.objective().vars(i));
928  ++component_sizes[index_to_component[get_var_index(var)]];
929  }
930 
931  // Dispatch every constraint to its LinearProgrammingConstraint.
932  std::vector<LinearProgrammingConstraint*> lp_constraints(num_components,
933  nullptr);
934  std::vector<std::vector<LinearConstraint>> component_to_constraints(
935  num_components);
936  for (int i = 0; i < num_lp_constraints; i++) {
937  const int c = index_to_component[get_constraint_index(i)];
938  if (component_sizes[c] <= 1) continue;
939  component_to_constraints[c].push_back(relaxation.linear_constraints[i]);
940  if (lp_constraints[c] == nullptr) {
941  lp_constraints[c] =
942  new LinearProgrammingConstraint(m, component_to_var[c]);
943  m->TakeOwnership(lp_constraints[c]);
944  }
945  // Load the constraint.
946  lp_constraints[c]->AddLinearConstraint(relaxation.linear_constraints[i]);
947  }
948 
949  // Dispatch every cut generator to its LinearProgrammingConstraint.
950  for (int i = 0; i < num_lp_cut_generators; i++) {
951  const int c = index_to_component[get_cut_generator_index(i)];
952  if (lp_constraints[c] == nullptr) {
953  lp_constraints[c] =
954  new LinearProgrammingConstraint(m, component_to_var[c]);
955  m->TakeOwnership(lp_constraints[c]);
956  }
957  lp_constraints[c]->AddCutGenerator(std::move(relaxation.cut_generators[i]));
958  }
959 
960  // Add the objective.
961  std::vector<std::vector<std::pair<IntegerVariable, int64_t>>>
962  component_to_cp_terms(num_components);
963  std::vector<std::pair<IntegerVariable, int64_t>> top_level_cp_terms;
964  int num_components_containing_objective = 0;
965  if (model_proto.has_objective()) {
966  // First pass: set objective coefficients on the lp constraints, and store
967  // the cp terms in one vector per component.
968  for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
969  const IntegerVariable var =
970  mapping->Integer(model_proto.objective().vars(i));
971  const int64_t coeff = model_proto.objective().coeffs(i);
972  const int c = index_to_component[get_var_index(var)];
973  if (lp_constraints[c] != nullptr) {
974  lp_constraints[c]->SetObjectiveCoefficient(var, IntegerValue(coeff));
975  component_to_cp_terms[c].push_back(std::make_pair(var, coeff));
976  } else {
977  // Component is too small. We still need to store the objective term.
978  top_level_cp_terms.push_back(std::make_pair(var, coeff));
979  }
980  }
981  // Second pass: Build the cp sub-objectives per component.
982  for (int c = 0; c < num_components; ++c) {
983  if (component_to_cp_terms[c].empty()) continue;
984  const IntegerVariable sub_obj_var = GetOrCreateVariableLinkedToSumOf(
985  component_to_cp_terms[c], objective_need_to_be_tight, m);
986  top_level_cp_terms.push_back(std::make_pair(sub_obj_var, 1));
987  lp_constraints[c]->SetMainObjectiveVariable(sub_obj_var);
988  num_components_containing_objective++;
989  }
990  }
991 
992  const IntegerVariable main_objective_var =
993  model_proto.has_objective()
994  ? GetOrCreateVariableLinkedToSumOf(top_level_cp_terms,
995  objective_need_to_be_tight, m)
997 
998  // Register LP constraints. Note that this needs to be done after all the
999  // constraints have been added.
1000  for (LinearProgrammingConstraint* lp_constraint : lp_constraints) {
1001  if (lp_constraint == nullptr) continue;
1002  lp_constraint->RegisterWith(m);
1003  VLOG(3) << "LP constraint: " << lp_constraint->DimensionString() << ".";
1004  }
1005 
1006  VLOG(3) << top_level_cp_terms.size()
1007  << " terms in the main objective linear equation ("
1008  << num_components_containing_objective << " from LP constraints).";
1009  return main_objective_var;
1010 }
1011 
1012 } // namespace
1013 
1014 // Used by NewFeasibleSolutionObserver to register observers.
1016  std::vector<std::function<void(const CpSolverResponse& response)>> observers;
1017 };
1018 
1019 std::function<void(Model*)> NewFeasibleSolutionObserver(
1020  const std::function<void(const CpSolverResponse& response)>& observer) {
1021  return [=](Model* model) {
1022  model->GetOrCreate<SolutionObservers>()->observers.push_back(observer);
1023  };
1024 }
1025 
1026 #if !defined(__PORTABLE_PLATFORM__)
1027 // TODO(user): Support it on android.
1028 std::function<SatParameters(Model*)> NewSatParameters(
1029  const std::string& params) {
1030  sat::SatParameters parameters;
1031  if (!params.empty()) {
1032  CHECK(google::protobuf::TextFormat::ParseFromString(params, &parameters))
1033  << params;
1034  }
1035  return NewSatParameters(parameters);
1036 }
1037 #endif // __PORTABLE_PLATFORM__
1038 
1039 std::function<SatParameters(Model*)> NewSatParameters(
1040  const sat::SatParameters& parameters) {
1041  return [=](Model* model) {
1042  // Tricky: It is important to initialize the model parameters before any
1043  // of the solver object are created, so that by default they use the given
1044  // parameters.
1045  //
1046  // TODO(user): A notable exception to this is the TimeLimit which is
1047  // currently not initializing itself from the SatParameters in the model. It
1048  // will also starts counting from the time of its creation. It will be good
1049  // to find a solution that is less error prone.
1050  *model->GetOrCreate<SatParameters>() = parameters;
1051  return parameters;
1052  };
1053 }
1054 
1055 namespace {
1056 
1057 // Registers a callback that will export variables bounds fixed at level 0 of
1058 // the search. This should not be registered to a LNS search.
1059 void RegisterVariableBoundsLevelZeroExport(
1060  const CpModelProto& /*model_proto*/,
1061  SharedBoundsManager* shared_bounds_manager, Model* model) {
1062  CHECK(shared_bounds_manager != nullptr);
1063 
1064  auto* mapping = model->GetOrCreate<CpModelMapping>();
1065  auto* trail = model->Get<Trail>();
1066  auto* integer_trail = model->Get<IntegerTrail>();
1067 
1068  int saved_trail_index = 0;
1069  std::vector<int> model_variables;
1070  std::vector<int64_t> new_lower_bounds;
1071  std::vector<int64_t> new_upper_bounds;
1072  absl::flat_hash_set<int> visited_variables;
1073 
1074  auto broadcast_level_zero_bounds =
1075  [=](const std::vector<IntegerVariable>& modified_vars) mutable {
1076  // Inspect the modified IntegerVariables.
1077  for (const IntegerVariable& var : modified_vars) {
1078  const IntegerVariable positive_var = PositiveVariable(var);
1079  const int model_var =
1080  mapping->GetProtoVariableFromIntegerVariable(positive_var);
1081 
1082  if (model_var == -1) continue;
1083  const auto [_, inserted] = visited_variables.insert(model_var);
1084  if (!inserted) continue;
1085 
1086  const int64_t new_lb =
1087  integer_trail->LevelZeroLowerBound(positive_var).value();
1088  const int64_t new_ub =
1089  integer_trail->LevelZeroUpperBound(positive_var).value();
1090 
1091  // TODO(user): We could imagine an API based on atomic<int64_t>
1092  // that could preemptively check if this new bounds are improving.
1093  model_variables.push_back(model_var);
1094  new_lower_bounds.push_back(new_lb);
1095  new_upper_bounds.push_back(new_ub);
1096  }
1097 
1098  // Inspect the newly modified Booleans.
1099  for (; saved_trail_index < trail->Index(); ++saved_trail_index) {
1100  const Literal fixed_literal = (*trail)[saved_trail_index];
1101  const int model_var = mapping->GetProtoVariableFromBooleanVariable(
1102  fixed_literal.Variable());
1103 
1104  if (model_var == -1) continue;
1105  const auto [_, inserted] = visited_variables.insert(model_var);
1106  if (!inserted) continue;
1107 
1108  model_variables.push_back(model_var);
1109  if (fixed_literal.IsPositive()) {
1110  new_lower_bounds.push_back(1);
1111  new_upper_bounds.push_back(1);
1112  } else {
1113  new_lower_bounds.push_back(0);
1114  new_upper_bounds.push_back(0);
1115  }
1116  }
1117 
1118  if (!model_variables.empty()) {
1119  shared_bounds_manager->ReportPotentialNewBounds(
1120  model->Name(), model_variables, new_lower_bounds,
1121  new_upper_bounds);
1122 
1123  // Clear for next call.
1124  model_variables.clear();
1125  new_lower_bounds.clear();
1126  new_upper_bounds.clear();
1127  visited_variables.clear();
1128 
1129  // If we are not in interleave_search we synchronize right away.
1130  if (!model->Get<SatParameters>()->interleave_search()) {
1131  shared_bounds_manager->Synchronize();
1132  }
1133  }
1134  };
1135 
1136  // The callback will just be called on NEWLY modified var. So initially,
1137  // we do want to read all variables.
1138  //
1139  // TODO(user): Find a better way? It seems nicer to register this before
1140  // any variable is modified. But then we don't want to call it each time
1141  // we reach level zero during probing. It should be better to only call
1142  // it when a new variable has been fixed.
1143  const IntegerVariable num_vars =
1144  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables();
1145  std::vector<IntegerVariable> all_variables;
1146  all_variables.reserve(num_vars.value());
1147  for (IntegerVariable var(0); var < num_vars; ++var) {
1148  all_variables.push_back(var);
1149  }
1150  broadcast_level_zero_bounds(all_variables);
1151 
1152  model->GetOrCreate<GenericLiteralWatcher>()
1153  ->RegisterLevelZeroModifiedVariablesCallback(broadcast_level_zero_bounds);
1154 }
1155 
1156 // Registers a callback to import new variables bounds stored in the
1157 // shared_bounds_manager. These bounds are imported at level 0 of the search
1158 // in the linear scan minimize function.
1159 void RegisterVariableBoundsLevelZeroImport(
1160  const CpModelProto& model_proto, SharedBoundsManager* shared_bounds_manager,
1161  Model* model) {
1162  CHECK(shared_bounds_manager != nullptr);
1163  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1164  CpModelMapping* const mapping = model->GetOrCreate<CpModelMapping>();
1165  const int id = shared_bounds_manager->RegisterNewId();
1166 
1167  const auto& import_level_zero_bounds = [&model_proto, shared_bounds_manager,
1168  model, integer_trail, id, mapping]() {
1169  std::vector<int> model_variables;
1170  std::vector<int64_t> new_lower_bounds;
1171  std::vector<int64_t> new_upper_bounds;
1172  shared_bounds_manager->GetChangedBounds(
1173  id, &model_variables, &new_lower_bounds, &new_upper_bounds);
1174  bool new_bounds_have_been_imported = false;
1175  for (int i = 0; i < model_variables.size(); ++i) {
1176  const int model_var = model_variables[i];
1177  // This can happen if a boolean variables is forced to have an
1178  // integer view in one thread, and not in another thread.
1179  if (!mapping->IsInteger(model_var)) continue;
1180  const IntegerVariable var = mapping->Integer(model_var);
1181  const IntegerValue new_lb(new_lower_bounds[i]);
1182  const IntegerValue new_ub(new_upper_bounds[i]);
1183  const IntegerValue old_lb = integer_trail->LowerBound(var);
1184  const IntegerValue old_ub = integer_trail->UpperBound(var);
1185  const bool changed_lb = new_lb > old_lb;
1186  const bool changed_ub = new_ub < old_ub;
1187  if (!changed_lb && !changed_ub) continue;
1188 
1189  new_bounds_have_been_imported = true;
1190  if (VLOG_IS_ON(3)) {
1191  const IntegerVariableProto& var_proto =
1192  model_proto.variables(model_var);
1193  const std::string& var_name =
1194  var_proto.name().empty()
1195  ? absl::StrCat("anonymous_var(", model_var, ")")
1196  : var_proto.name();
1197  LOG(INFO) << " '" << model->Name() << "' imports new bounds for "
1198  << var_name << ": from [" << old_lb << ", " << old_ub
1199  << "] to [" << new_lb << ", " << new_ub << "]";
1200  }
1201 
1202  if (changed_lb &&
1203  !integer_trail->Enqueue(IntegerLiteral::GreaterOrEqual(var, new_lb),
1204  {}, {})) {
1205  return false;
1206  }
1207  if (changed_ub &&
1208  !integer_trail->Enqueue(IntegerLiteral::LowerOrEqual(var, new_ub), {},
1209  {})) {
1210  return false;
1211  }
1212  }
1213  if (new_bounds_have_been_imported &&
1214  !model->GetOrCreate<SatSolver>()->FinishPropagation()) {
1215  return false;
1216  }
1217  return true;
1218  };
1219  model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
1220  import_level_zero_bounds);
1221 }
1222 
1223 // Registers a callback that will report improving objective best bound.
1224 // It will be called each time new objective bound are propagated at level zero.
1225 void RegisterObjectiveBestBoundExport(
1226  IntegerVariable objective_var,
1227  SharedResponseManager* shared_response_manager, Model* model) {
1228  auto* integer_trail = model->Get<IntegerTrail>();
1229  const auto broadcast_objective_lower_bound =
1230  [objective_var, integer_trail, shared_response_manager, model,
1231  best_obj_lb =
1232  kMinIntegerValue](const std::vector<IntegerVariable>&) mutable {
1233  const IntegerValue objective_lb =
1234  integer_trail->LevelZeroLowerBound(objective_var);
1235  if (objective_lb > best_obj_lb) {
1236  best_obj_lb = objective_lb;
1237  shared_response_manager->UpdateInnerObjectiveBounds(
1238  model->Name(), objective_lb,
1239  integer_trail->LevelZeroUpperBound(objective_var));
1240  // If we are not in interleave_search we synchronize right away.
1241  if (!model->Get<SatParameters>()->interleave_search()) {
1242  shared_response_manager->Synchronize();
1243  }
1244  }
1245  };
1246  model->GetOrCreate<GenericLiteralWatcher>()
1247  ->RegisterLevelZeroModifiedVariablesCallback(
1248  broadcast_objective_lower_bound);
1249 }
1250 
1251 // Registers a callback to import new objective bounds. It will be called each
1252 // time the search main loop is back to level zero. Note that it the presence of
1253 // assumptions, this will not happen until the set of assumptions is changed.
1254 void RegisterObjectiveBoundsImport(
1255  SharedResponseManager* shared_response_manager, Model* model) {
1256  auto* solver = model->GetOrCreate<SatSolver>();
1257  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1258  auto* objective = model->GetOrCreate<ObjectiveDefinition>();
1259  const std::string name = model->Name();
1260  const auto import_objective_bounds = [name, solver, integer_trail, objective,
1261  shared_response_manager]() {
1262  if (solver->AssumptionLevel() != 0) return true;
1263  bool propagate = false;
1264 
1265  const IntegerValue external_lb =
1266  shared_response_manager->SynchronizedInnerObjectiveLowerBound();
1267  const IntegerValue current_lb =
1268  integer_trail->LowerBound(objective->objective_var);
1269  if (external_lb > current_lb) {
1270  if (!integer_trail->Enqueue(IntegerLiteral::GreaterOrEqual(
1271  objective->objective_var, external_lb),
1272  {}, {})) {
1273  return false;
1274  }
1275  propagate = true;
1276  }
1277 
1278  const IntegerValue external_ub =
1279  shared_response_manager->SynchronizedInnerObjectiveUpperBound();
1280  const IntegerValue current_ub =
1281  integer_trail->UpperBound(objective->objective_var);
1282  if (external_ub < current_ub) {
1283  if (!integer_trail->Enqueue(IntegerLiteral::LowerOrEqual(
1284  objective->objective_var, external_ub),
1285  {}, {})) {
1286  return false;
1287  }
1288  propagate = true;
1289  }
1290 
1291  if (!propagate) return true;
1292 
1293  VLOG(3) << "'" << name << "' imports objective bounds: external ["
1294  << objective->ScaleIntegerObjective(external_lb) << ", "
1295  << objective->ScaleIntegerObjective(external_ub) << "], current ["
1296  << objective->ScaleIntegerObjective(current_lb) << ", "
1297  << objective->ScaleIntegerObjective(current_ub) << "]";
1298 
1299  return solver->FinishPropagation();
1300  };
1301 
1302  model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
1303  import_objective_bounds);
1304 }
1305 
1306 // Registers a callback that will export non-problem clauses added during
1307 // search.
1308 void RegisterClausesExport(int id, SharedClausesManager* shared_clauses_manager,
1309  Model* model) {
1310  auto* mapping = model->GetOrCreate<CpModelMapping>();
1311  auto* sat_solver = model->GetOrCreate<SatSolver>();
1312  const auto& share_binary_clause = [mapping, id, shared_clauses_manager](
1313  Literal l1, Literal l2) {
1314  const int var1 =
1315  mapping->GetProtoVariableFromBooleanVariable(l1.Variable());
1316  if (var1 == -1) return;
1317  const int var2 =
1318  mapping->GetProtoVariableFromBooleanVariable(l2.Variable());
1319  if (var2 == -1) return;
1320  const int lit1 = l1.IsPositive() ? var1 : NegatedRef(var1);
1321  const int lit2 = l2.IsPositive() ? var2 : NegatedRef(var2);
1322  shared_clauses_manager->AddBinaryClause(id, lit1, lit2);
1323  };
1324  sat_solver->SetShareBinaryClauseCallback(share_binary_clause);
1325 }
1326 
1327 // Registers a callback to import new clauses stored in the
1328 // shared_clausess_manager. These clauses are imported at level 0 of the search
1329 // in the linear scan minimize function.
1330 // it returns the id of the worker in the shared clause manager.
1331 //
1332 // TODO(user): Can we import them in the core worker ?
1333 int RegisterClausesLevelZeroImport(int id,
1334  SharedClausesManager* shared_clauses_manager,
1335  Model* model) {
1336  CHECK(shared_clauses_manager != nullptr);
1337  CpModelMapping* const mapping = model->GetOrCreate<CpModelMapping>();
1338  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
1339  const auto& import_level_zero_clauses = [shared_clauses_manager, id, mapping,
1340  sat_solver]() {
1341  std::vector<std::pair<int, int>> new_binary_clauses;
1342  shared_clauses_manager->GetUnseenBinaryClauses(id, &new_binary_clauses);
1343  for (const auto& [ref1, ref2] : new_binary_clauses) {
1344  const Literal l1 = mapping->Literal(ref1);
1345  const Literal l2 = mapping->Literal(ref2);
1346  if (!sat_solver->AddBinaryClause(l1, l2)) {
1347  return false;
1348  }
1349  }
1350  return true;
1351  };
1352  model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
1353  import_level_zero_clauses);
1354  return id;
1355 }
1356 
1357 void LoadBaseModel(const CpModelProto& model_proto, Model* model) {
1358  auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1359  CHECK(shared_response_manager != nullptr);
1360  auto* sat_solver = model->GetOrCreate<SatSolver>();
1361 
1362  // Simple function for the few places where we do "return unsat()".
1363  const auto unsat = [shared_response_manager, sat_solver, model] {
1364  sat_solver->NotifyThatModelIsUnsat();
1365  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1366  absl::StrCat(model->Name(), " [loading]"));
1367  };
1368 
1369  // We will add them all at once after model_proto is loaded.
1370  model->GetOrCreate<IntegerEncoder>()->DisableImplicationBetweenLiteral();
1371 
1372  auto* mapping = model->GetOrCreate<CpModelMapping>();
1373  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1374  const bool view_all_booleans_as_integers =
1375  (parameters.linearization_level() >= 2) ||
1376  (parameters.search_branching() == SatParameters::FIXED_SEARCH &&
1377  model_proto.search_strategy().empty()) ||
1378  parameters.optimize_with_max_hs();
1379  LoadVariables(model_proto, view_all_booleans_as_integers, model);
1381 
1382  // TODO(user): The core algo and symmetries seems to be problematic in some
1383  // cases. See for instance: neos-691058.mps.gz. This is probably because as
1384  // we modify the model, our symmetry might be wrong? investigate.
1385  //
1386  // TODO(user): More generally, we cannot load the symmetry if we create
1387  // new Booleans and constraints that link them to some Booleans of the model.
1388  // Creating Booleans related to integer variable is fine since we only deal
1389  // with Boolean only symmetry here. It is why we disable this when we have
1390  // linear relaxation as some of them create new constraints.
1391  if (!parameters.optimize_with_core() && parameters.symmetry_level() > 1 &&
1392  !parameters.enumerate_all_solutions() &&
1393  parameters.linearization_level() == 0) {
1395  }
1396 
1400 
1401  // Check the model is still feasible before continuing.
1402  if (sat_solver->ModelIsUnsat()) return unsat();
1403 
1404  // Fully encode variables as needed by the search strategy.
1406 
1407  // Load the constraints.
1408  absl::btree_set<std::string> unsupported_types;
1409  int num_ignored_constraints = 0;
1410  for (const ConstraintProto& ct : model_proto.constraints()) {
1411  if (mapping->ConstraintIsAlreadyLoaded(&ct)) {
1412  ++num_ignored_constraints;
1413  continue;
1414  }
1415 
1416  if (!LoadConstraint(ct, model)) {
1417  unsupported_types.insert(ConstraintCaseName(ct.constraint_case()));
1418  continue;
1419  }
1420 
1421  // We propagate after each new Boolean constraint but not the integer
1422  // ones. So we call FinishPropagation() manually here.
1423  //
1424  // Note that we only do that in debug mode as this can be really slow on
1425  // certain types of problems with millions of constraints.
1426  if (DEBUG_MODE) {
1427  if (sat_solver->FinishPropagation()) {
1428  Trail* trail = model->GetOrCreate<Trail>();
1429  const int old_num_fixed = trail->Index();
1430  if (trail->Index() > old_num_fixed) {
1431  VLOG(3) << "Constraint fixed " << trail->Index() - old_num_fixed
1432  << " Boolean variable(s): " << ProtobufDebugString(ct);
1433  }
1434  }
1435  }
1436  if (sat_solver->ModelIsUnsat()) {
1437  VLOG(2) << "UNSAT during extraction (after adding '"
1438  << ConstraintCaseName(ct.constraint_case()) << "'). "
1439  << ProtobufDebugString(ct);
1440  break;
1441  }
1442  }
1443  if (num_ignored_constraints > 0) {
1444  VLOG(3) << num_ignored_constraints << " constraints were skipped.";
1445  }
1446  if (!unsupported_types.empty()) {
1447  VLOG(1) << "There is unsupported constraints types in this model: ";
1448  for (const std::string& type : unsupported_types) {
1449  VLOG(1) << " - " << type;
1450  }
1451  return unsat();
1452  }
1453 
1454  model->GetOrCreate<IntegerEncoder>()
1455  ->AddAllImplicationsBetweenAssociatedLiterals();
1456  if (!sat_solver->FinishPropagation()) return unsat();
1457 
1458  model->GetOrCreate<ProductDetector>()->ProcessImplicationGraph(
1459  model->GetOrCreate<BinaryImplicationGraph>());
1460 }
1461 
1462 void LoadFeasibilityPump(const CpModelProto& model_proto, Model* model) {
1463  LoadBaseModel(model_proto, model);
1464 
1465  auto* mapping = model->GetOrCreate<CpModelMapping>();
1466  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1467  if (parameters.linearization_level() == 0) return;
1468 
1469  // Add linear constraints to Feasibility Pump.
1470  const LinearRelaxation relaxation =
1472  const int num_lp_constraints = relaxation.linear_constraints.size();
1473  if (num_lp_constraints == 0) return;
1474  auto* feasibility_pump = model->GetOrCreate<FeasibilityPump>();
1475  for (int i = 0; i < num_lp_constraints; i++) {
1476  feasibility_pump->AddLinearConstraint(relaxation.linear_constraints[i]);
1477  }
1478 
1479  if (model_proto.has_objective()) {
1480  for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
1481  const IntegerVariable var =
1482  mapping->Integer(model_proto.objective().vars(i));
1483  const int64_t coeff = model_proto.objective().coeffs(i);
1484  feasibility_pump->SetObjectiveCoefficient(var, IntegerValue(coeff));
1485  }
1486  }
1487 }
1488 
1489 // Loads a CpModelProto inside the given model.
1490 // This should only be called once on a given 'Model' class.
1491 //
1492 // TODO(user): move to cp_model_loader.h/.cc
1493 void LoadCpModel(const CpModelProto& model_proto, Model* model) {
1494  LoadBaseModel(model_proto, model);
1495 
1496  // We want to load the debug solution before the initial propag.
1497  // But at this point the objective is not loaded yet, so we will not have
1498  // a value for the objective integer variable, so we do it again later.
1499  InitializeDebugSolution(model_proto, model);
1500 
1501  // Simple function for the few places where we do "return unsat()".
1502  auto* sat_solver = model->GetOrCreate<SatSolver>();
1503  auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1504  const auto unsat = [shared_response_manager, sat_solver, model] {
1505  sat_solver->NotifyThatModelIsUnsat();
1506  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1507  absl::StrCat(model->Name(), " [loading]"));
1508  };
1509 
1510  auto* mapping = model->GetOrCreate<CpModelMapping>();
1511  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1512 
1513  // Auto detect "at least one of" constraints in the PrecedencesPropagator.
1514  // Note that we do that before we finish loading the problem (objective and
1515  // LP relaxation), because propagation will be faster at this point and it
1516  // should be enough for the purpose of this auto-detection.
1517  if (model->Mutable<PrecedencesPropagator>() != nullptr &&
1518  parameters.auto_detect_greater_than_at_least_one_of()) {
1519  model->Mutable<PrecedencesPropagator>()
1520  ->AddGreaterThanAtLeastOneOfConstraints(model);
1521  if (!sat_solver->FinishPropagation()) return unsat();
1522  }
1523 
1524  // TODO(user): This should be done in the presolve instead.
1525  // TODO(user): We don't have a good deterministic time on all constraints,
1526  // so this might take more time than wanted.
1527  if (parameters.cp_model_probing_level() > 1) {
1528  Prober* prober = model->GetOrCreate<Prober>();
1529  prober->ProbeBooleanVariables(/*deterministic_time_limit=*/1.0);
1530  if (!model->GetOrCreate<BinaryImplicationGraph>()
1531  ->ComputeTransitiveReduction()) {
1532  return unsat();
1533  }
1534  }
1535  if (sat_solver->ModelIsUnsat()) return unsat();
1536 
1537  // We need to know beforehand if the objective var can just be >= terms or
1538  // needs to be == terms.
1539  bool objective_need_to_be_tight = false;
1540  if (model_proto.has_objective() &&
1541  !model_proto.objective().domain().empty()) {
1542  int64_t min_value = 0;
1543  int64_t max_value = 0;
1544  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1545  const CpObjectiveProto& obj = model_proto.objective();
1546  for (int i = 0; i < obj.vars_size(); ++i) {
1547  const int64_t coeff = obj.coeffs(i);
1548  const IntegerVariable var = mapping->Integer(obj.vars(i));
1549  if (coeff > 0) {
1550  min_value += coeff * integer_trail->LowerBound(var).value();
1551  max_value += coeff * integer_trail->UpperBound(var).value();
1552  } else {
1553  min_value += coeff * integer_trail->UpperBound(var).value();
1554  max_value += coeff * integer_trail->LowerBound(var).value();
1555  }
1556  }
1557  const Domain user_domain = ReadDomainFromProto(model_proto.objective());
1558  const Domain automatic_domain = Domain(min_value, max_value);
1559  objective_need_to_be_tight = !automatic_domain.IsIncludedIn(user_domain);
1560  }
1561 
1562  // Create an objective variable and its associated linear constraint if
1563  // needed.
1564  IntegerVariable objective_var = kNoIntegerVariable;
1565  if (parameters.linearization_level() > 0) {
1566  // Linearize some part of the problem and register LP constraint(s).
1567  objective_var =
1568  AddLPConstraints(objective_need_to_be_tight, model_proto, model);
1569  } else if (model_proto.has_objective()) {
1570  const CpObjectiveProto& obj = model_proto.objective();
1571  std::vector<std::pair<IntegerVariable, int64_t>> terms;
1572  terms.reserve(obj.vars_size());
1573  for (int i = 0; i < obj.vars_size(); ++i) {
1574  terms.push_back(
1575  std::make_pair(mapping->Integer(obj.vars(i)), obj.coeffs(i)));
1576  }
1577  if (parameters.optimize_with_core() && !objective_need_to_be_tight) {
1578  objective_var = GetOrCreateVariableWithTightBound(terms, model);
1579  } else {
1580  objective_var = GetOrCreateVariableLinkedToSumOf(
1581  terms, objective_need_to_be_tight, model);
1582  }
1583  }
1584 
1585  // Create the objective definition inside the Model so that it can be accessed
1586  // by the heuristics than needs it.
1587  if (objective_var != kNoIntegerVariable) {
1588  const CpObjectiveProto& objective_proto = model_proto.objective();
1589  auto* objective_definition = model->GetOrCreate<ObjectiveDefinition>();
1590 
1591  objective_definition->scaling_factor = objective_proto.scaling_factor();
1592  if (objective_definition->scaling_factor == 0.0) {
1593  objective_definition->scaling_factor = 1.0;
1594  }
1595  objective_definition->offset = objective_proto.offset();
1596  objective_definition->objective_var = objective_var;
1597 
1598  const int size = objective_proto.vars_size();
1599  objective_definition->vars.resize(size);
1600  objective_definition->coeffs.resize(size);
1601  for (int i = 0; i < objective_proto.vars_size(); ++i) {
1602  // Note that if there is no mapping, then the variable will be
1603  // kNoIntegerVariable.
1604  objective_definition->vars[i] = mapping->Integer(objective_proto.vars(i));
1605  objective_definition->coeffs[i] = IntegerValue(objective_proto.coeffs(i));
1606 
1607  // Fill the objective heuristics data.
1608  const int ref = objective_proto.vars(i);
1609  if (mapping->IsInteger(ref)) {
1610  const IntegerVariable var = mapping->Integer(objective_proto.vars(i));
1611  objective_definition->objective_impacting_variables.insert(
1612  objective_proto.coeffs(i) > 0 ? var : NegationOf(var));
1613  }
1614  }
1615 
1616  // Register an objective special propagator.
1617  model->TakeOwnership(
1618  new LevelZeroEquality(objective_var, objective_definition->vars,
1619  objective_definition->coeffs, model));
1620  }
1621 
1622  // Intersect the objective domain with the given one if any.
1623  if (!model_proto.objective().domain().empty()) {
1624  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1625  const Domain user_domain = ReadDomainFromProto(model_proto.objective());
1626  const Domain automatic_domain =
1627  integer_trail->InitialVariableDomain(objective_var);
1628  VLOG(3) << "Objective offset:" << model_proto.objective().offset()
1629  << " scaling_factor:" << model_proto.objective().scaling_factor();
1630  VLOG(3) << "Automatic internal objective domain: " << automatic_domain;
1631  VLOG(3) << "User specified internal objective domain: " << user_domain;
1632  CHECK_NE(objective_var, kNoIntegerVariable);
1633  if (!integer_trail->UpdateInitialDomain(objective_var, user_domain)) {
1634  VLOG(2) << "UNSAT due to the objective domain.";
1635  return unsat();
1636  }
1637  }
1638 
1639  // Note that we do one last propagation at level zero once all the
1640  // constraints were added.
1641  SOLVER_LOG(model->GetOrCreate<SolverLogger>(),
1642  "Initial num_bool: ", sat_solver->NumVariables());
1643  if (!sat_solver->FinishPropagation()) return unsat();
1644 
1645  if (model_proto.has_objective()) {
1646  // Report the initial objective variable bounds.
1647  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1648  shared_response_manager->UpdateInnerObjectiveBounds(
1649  absl::StrCat(model->Name(), " initial_propagation"),
1650  integer_trail->LowerBound(objective_var),
1651  integer_trail->UpperBound(objective_var));
1652 
1653  // Watch improved objective best bounds.
1654  RegisterObjectiveBestBoundExport(objective_var, shared_response_manager,
1655  model);
1656 
1657  // Import objective bounds.
1658  // TODO(user): Support objective bounds import in LNS and Core based
1659  // search.
1660  if (model->GetOrCreate<SatParameters>()->share_objective_bounds()) {
1661  RegisterObjectiveBoundsImport(shared_response_manager, model);
1662  }
1663  }
1664 
1665  // Cache the links between model vars, IntegerVariables and lp constraints.
1666  // TODO(user): Cache this only if it is actually used.
1667  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1668  auto* lp_dispatcher = model->GetOrCreate<LinearProgrammingDispatcher>();
1669  auto* lp_vars = model->GetOrCreate<LPVariables>();
1670  IntegerVariable size = integer_trail->NumIntegerVariables();
1671  for (IntegerVariable positive_var(0); positive_var < size;
1672  positive_var += 2) {
1673  LPVariable lp_var;
1674  lp_var.positive_var = positive_var;
1675  lp_var.model_var =
1676  mapping->GetProtoVariableFromIntegerVariable(positive_var);
1677  const auto& it = lp_dispatcher->find(positive_var);
1678  lp_var.lp = it != lp_dispatcher->end() ? it->second : nullptr;
1679 
1680  if (lp_var.model_var >= 0) {
1681  lp_vars->vars.push_back(lp_var);
1682  lp_vars->model_vars_size =
1683  std::max(lp_vars->model_vars_size, lp_var.model_var + 1);
1684  }
1685  }
1686 
1687  // Initialize the fixed_search strategy.
1688  auto* search_heuristics = model->GetOrCreate<SearchHeuristics>();
1689  if (parameters.search_branching() == SatParameters::PARTIAL_FIXED_SEARCH) {
1690  search_heuristics->user_search =
1692  }
1693  search_heuristics->fixed_search = ConstructFixedSearchStrategy(
1694  model_proto, mapping->GetVariableMapping(), objective_var, model);
1695  if (VLOG_IS_ON(3)) {
1696  search_heuristics->fixed_search =
1697  InstrumentSearchStrategy(model_proto, mapping->GetVariableMapping(),
1698  search_heuristics->fixed_search, model);
1699  }
1700 
1701  // Initialize the "follow hint" strategy.
1702  std::vector<BooleanOrIntegerVariable> vars;
1703  std::vector<IntegerValue> values;
1704  for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
1705  const int ref = model_proto.solution_hint().vars(i);
1706  CHECK(RefIsPositive(ref));
1707  BooleanOrIntegerVariable var;
1708  if (mapping->IsBoolean(ref)) {
1709  var.bool_var = mapping->Literal(ref).Variable();
1710  } else {
1711  var.int_var = mapping->Integer(ref);
1712  }
1713  vars.push_back(var);
1714  values.push_back(IntegerValue(model_proto.solution_hint().values(i)));
1715  }
1716  search_heuristics->hint_search = FollowHint(vars, values, model);
1717 
1718  // Create the CoreBasedOptimizer class if needed.
1719  if (parameters.optimize_with_core()) {
1720  // TODO(user): Remove code duplication with the solution_observer in
1721  // SolveLoadedCpModel().
1722  const auto solution_observer = [&model_proto, model,
1723  shared_response_manager,
1724  best_obj_ub = kMaxIntegerValue]() mutable {
1725  const std::vector<int64_t> solution =
1726  GetSolutionValues(model_proto, *model);
1727  const IntegerValue obj_ub =
1728  ComputeInnerObjective(model_proto.objective(), solution);
1729  if (obj_ub < best_obj_ub) {
1730  best_obj_ub = obj_ub;
1731  shared_response_manager->NewSolution(solution, model->Name(), model);
1732  }
1733  };
1734 
1735  const auto& objective = *model->GetOrCreate<ObjectiveDefinition>();
1736  if (parameters.optimize_with_max_hs()) {
1737  HittingSetOptimizer* max_hs = new HittingSetOptimizer(
1738  model_proto, objective, solution_observer, model);
1739  model->Register<HittingSetOptimizer>(max_hs);
1740  model->TakeOwnership(max_hs);
1741  } else {
1742  CoreBasedOptimizer* core =
1743  new CoreBasedOptimizer(objective_var, objective.vars,
1744  objective.coeffs, solution_observer, model);
1745  model->Register<CoreBasedOptimizer>(core);
1746  model->TakeOwnership(core);
1747  }
1748  }
1749 
1750  InitializeDebugSolution(model_proto, model);
1751 }
1752 
1753 // Solves an already loaded cp_model_proto.
1754 // The final CpSolverResponse must be read from the shared_response_manager.
1755 //
1756 // TODO(user): This should be transformed so that it can be called many times
1757 // and resume from the last search state as if it wasn't interrupted. That would
1758 // allow use to easily interleave different heuristics in the same thread.
1759 void SolveLoadedCpModel(const CpModelProto& model_proto, Model* model) {
1760  auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1761  if (shared_response_manager->ProblemIsSolved()) return;
1762 
1763  auto solution_observer = [&model_proto, model, shared_response_manager,
1764  best_obj_ub = kMaxIntegerValue]() mutable {
1765  const std::vector<int64_t> solution =
1766  GetSolutionValues(model_proto, *model);
1767  if (model_proto.has_objective()) {
1768  const IntegerValue obj_ub =
1769  ComputeInnerObjective(model_proto.objective(), solution);
1770  if (obj_ub < best_obj_ub) {
1771  best_obj_ub = obj_ub;
1772  shared_response_manager->NewSolution(solution, model->Name(), model);
1773  }
1774  } else {
1775  shared_response_manager->NewSolution(solution, model->Name(), model);
1776  }
1777  };
1778 
1779  // Reconfigure search heuristic if it was changed.
1781 
1782  const auto& mapping = *model->GetOrCreate<CpModelMapping>();
1784  const SatParameters& parameters = *model->GetOrCreate<SatParameters>();
1785 
1786  if (parameters.use_probing_search()) {
1787  ContinuousProber prober(model_proto, model);
1788  while (true) {
1789  status = prober.Probe();
1790  if (status == SatSolver::INFEASIBLE) {
1791  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1792  model->Name());
1793  break;
1794  }
1795  if (status == SatSolver::FEASIBLE) {
1796  solution_observer();
1797  } else {
1798  break;
1799  }
1800  }
1801  } else if (!model_proto.has_objective()) {
1802  while (true) {
1804  mapping.Literals(model_proto.assumptions()), model);
1805  if (status != SatSolver::Status::FEASIBLE) break;
1806  solution_observer();
1807  if (!parameters.enumerate_all_solutions()) break;
1809  }
1810  if (status == SatSolver::INFEASIBLE) {
1811  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1812  model->Name());
1813  }
1815  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1816  model->Name());
1817 
1818  // Extract a good subset of assumptions and add it to the response.
1819  auto* time_limit = model->GetOrCreate<TimeLimit>();
1820  auto* sat_solver = model->GetOrCreate<SatSolver>();
1821  std::vector<Literal> core = sat_solver->GetLastIncompatibleDecisions();
1822  MinimizeCoreWithPropagation(time_limit, sat_solver, &core);
1823  std::vector<int> core_in_proto_format;
1824  for (const Literal l : core) {
1825  core_in_proto_format.push_back(
1826  mapping.GetProtoVariableFromBooleanVariable(l.Variable()));
1827  if (!l.IsPositive()) {
1828  core_in_proto_format.back() = NegatedRef(core_in_proto_format.back());
1829  }
1830  }
1831  shared_response_manager->AddUnsatCore(core_in_proto_format);
1832  }
1833  } else {
1834  // Optimization problem.
1835  const auto& objective = *model->GetOrCreate<ObjectiveDefinition>();
1836  const IntegerVariable objective_var = objective.objective_var;
1837  CHECK_NE(objective_var, kNoIntegerVariable);
1838 
1839  if (parameters.optimize_with_lb_tree_search()) {
1840  auto* search = model->GetOrCreate<LbTreeSearch>();
1841  status = search->Search(solution_observer);
1842  } else if (parameters.optimize_with_core()) {
1843  // TODO(user): This doesn't work with splitting in chunk for now. It
1844  // shouldn't be too hard to fix.
1845  if (parameters.optimize_with_max_hs()) {
1846  status = model->Mutable<HittingSetOptimizer>()->Optimize();
1847  } else {
1848  status = model->Mutable<CoreBasedOptimizer>()->Optimize();
1849  }
1850  } else {
1851  // TODO(user): This parameter breaks the splitting in chunk of a Solve().
1852  // It should probably be moved into another SubSolver altogether.
1853  if (parameters.binary_search_num_conflicts() >= 0) {
1855  solution_observer, model);
1856  }
1858  objective_var, solution_observer, model);
1859  }
1860 
1861  // The search is done in both case.
1862  //
1863  // TODO(user): Remove the weird translation INFEASIBLE->FEASIBLE in the
1864  // function above?
1866  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1867  model->Name());
1868  }
1869  }
1870 }
1871 
1872 // Try to find a solution by following the hint and using a low conflict limit.
1873 // The CpModelProto must already be loaded in the Model.
1874 void QuickSolveWithHint(const CpModelProto& model_proto, Model* model) {
1875  if (!model_proto.has_solution_hint()) return;
1876 
1877  auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1878  if (shared_response_manager->ProblemIsSolved()) return;
1879 
1880  // Temporarily change the parameters.
1881  auto* parameters = model->GetOrCreate<SatParameters>();
1882 
1883  // If the model was loaded with "optimize_with_core" then the objective
1884  // variable is not linked to its linear expression. Because of that, we can
1885  // return a solution that does not satisfy the objective domain.
1886  //
1887  // TODO(user): This is fixable, but then do we need the hint when optimizing
1888  // with core?
1889  if (parameters->optimize_with_core()) return;
1890 
1891  const SatParameters saved_params = *parameters;
1892  parameters->set_max_number_of_conflicts(parameters->hint_conflict_limit());
1893  parameters->set_search_branching(SatParameters::HINT_SEARCH);
1894  parameters->set_optimize_with_core(false);
1895  auto cleanup = ::absl::MakeCleanup(
1896  [parameters, saved_params]() { *parameters = saved_params; });
1897 
1898  // Solve decision problem.
1900  const auto& mapping = *model->GetOrCreate<CpModelMapping>();
1902  mapping.Literals(model_proto.assumptions()), model);
1903 
1904  const std::string& solution_info = model->Name();
1906  const std::vector<int64_t> solution =
1907  GetSolutionValues(model_proto, *model);
1908  shared_response_manager->NewSolution(
1909  solution, absl::StrCat(solution_info, " [hint]"), model);
1910 
1911  if (!model_proto.has_objective()) {
1912  if (parameters->enumerate_all_solutions()) {
1914  }
1915  } else {
1916  // Restrict the objective.
1917  const IntegerVariable objective_var =
1918  model->GetOrCreate<ObjectiveDefinition>()->objective_var;
1919  model->GetOrCreate<SatSolver>()->Backtrack(0);
1920  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1921  if (!integer_trail->Enqueue(
1923  objective_var,
1924  shared_response_manager->GetInnerObjectiveUpperBound()),
1925  {}, {})) {
1926  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1927  absl::StrCat(solution_info, " [hint]"));
1928  }
1929  }
1930  return;
1931  }
1932 
1933  // This code is here to debug bad presolve during LNS that corrupt the hint.
1934  // Note that sometime the deterministic limit is hit before the hint can be
1935  // completed, so we don't report that has an error.
1936  //
1937  // Tricky: We can only test that if we don't already have a feasible solution
1938  // like we do if the hint is complete.
1939  if (parameters->debug_crash_on_bad_hint() &&
1940  shared_response_manager->SolutionsRepository().NumSolutions() == 0 &&
1941  !model->GetOrCreate<TimeLimit>()->LimitReached() &&
1943  LOG(FATAL) << "QuickSolveWithHint() didn't find a feasible solution."
1944  << " The model name is '" << model_proto.name() << "'."
1945  << " Status: " << status << ".";
1946  }
1947 
1948  if (status == SatSolver::INFEASIBLE) {
1949  shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1950  absl::StrCat(solution_info, " [hint]"));
1951  return;
1952  }
1953 }
1954 
1955 // Solve a model with a different objective consisting of minimizing the L1
1956 // distance with the provided hint. Note that this method creates an in-memory
1957 // copy of the model and loads a local Model object from the copied model.
1958 void MinimizeL1DistanceWithHint(const CpModelProto& model_proto, Model* model) {
1959  Model local_model;
1960 
1961  // Forward some shared class.
1962  local_model.Register<ModelSharedTimeLimit>(
1963  model->GetOrCreate<ModelSharedTimeLimit>());
1964  local_model.Register<WallTimer>(model->GetOrCreate<WallTimer>());
1965 
1966  if (!model_proto.has_solution_hint()) return;
1967 
1968  auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1969  if (shared_response_manager->ProblemIsSolved()) return;
1970 
1971  auto* parameters = local_model.GetOrCreate<SatParameters>();
1972  // TODO(user): As of now the repair hint doesn't support when
1973  // enumerate_all_solutions is set since the solution is created on a different
1974  // model.
1975  if (parameters->enumerate_all_solutions()) return;
1976 
1977  // Change the parameters.
1978  const SatParameters saved_params = *model->GetOrCreate<SatParameters>();
1979  *parameters = saved_params;
1980  parameters->set_max_number_of_conflicts(parameters->hint_conflict_limit());
1981  parameters->set_optimize_with_core(false);
1982 
1983  // Update the model to introduce penalties to go away from hinted values.
1984  CpModelProto updated_model_proto = model_proto;
1985  updated_model_proto.clear_objective();
1986 
1987  // TODO(user): For boolean variables we can avoid creating new variables.
1988  for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
1989  const int var = model_proto.solution_hint().vars(i);
1990  const int64_t value = model_proto.solution_hint().values(i);
1991 
1992  // Add a new var to represent the difference between var and value.
1993  const int new_var_index = updated_model_proto.variables_size();
1994  IntegerVariableProto* var_proto = updated_model_proto.add_variables();
1995  const int64_t min_domain = model_proto.variables(var).domain(0) - value;
1996  const int64_t max_domain =
1997  model_proto.variables(var).domain(
1998  model_proto.variables(var).domain_size() - 1) -
1999  value;
2000  var_proto->add_domain(min_domain);
2001  var_proto->add_domain(max_domain);
2002 
2003  // new_var = var - value.
2004  ConstraintProto* const linear_constraint_proto =
2005  updated_model_proto.add_constraints();
2006  LinearConstraintProto* linear = linear_constraint_proto->mutable_linear();
2007  linear->add_vars(new_var_index);
2008  linear->add_coeffs(1);
2009  linear->add_vars(var);
2010  linear->add_coeffs(-1);
2011  linear->add_domain(-value);
2012  linear->add_domain(-value);
2013 
2014  // abs_var = abs(new_var).
2015  const int abs_var_index = updated_model_proto.variables_size();
2016  IntegerVariableProto* abs_var_proto = updated_model_proto.add_variables();
2017  const int64_t abs_min_domain = 0;
2018  const int64_t abs_max_domain =
2019  std::max(std::abs(min_domain), std::abs(max_domain));
2020  abs_var_proto->add_domain(abs_min_domain);
2021  abs_var_proto->add_domain(abs_max_domain);
2022  auto* abs_ct = updated_model_proto.add_constraints()->mutable_lin_max();
2023  abs_ct->mutable_target()->add_vars(abs_var_index);
2024  abs_ct->mutable_target()->add_coeffs(1);
2025  LinearExpressionProto* left = abs_ct->add_exprs();
2026  left->add_vars(new_var_index);
2027  left->add_coeffs(1);
2028  LinearExpressionProto* right = abs_ct->add_exprs();
2029  right->add_vars(new_var_index);
2030  right->add_coeffs(-1);
2031 
2032  updated_model_proto.mutable_objective()->add_vars(abs_var_index);
2033  updated_model_proto.mutable_objective()->add_coeffs(1);
2034  }
2035 
2036  auto* local_response_manager =
2037  local_model.GetOrCreate<SharedResponseManager>();
2038  local_response_manager->InitializeObjective(updated_model_proto);
2039 
2040  // Solve optimization problem.
2041  LoadCpModel(updated_model_proto, &local_model);
2042 
2043  ConfigureSearchHeuristics(&local_model);
2044  const auto& mapping = *local_model.GetOrCreate<CpModelMapping>();
2046  mapping.Literals(updated_model_proto.assumptions()), &local_model);
2047 
2048  const std::string& solution_info = model->Name();
2050  const std::vector<int64_t> solution =
2051  GetSolutionValues(model_proto, local_model);
2052  if (DEBUG_MODE) {
2053  const std::vector<int64_t> updated_solution =
2054  GetSolutionValues(updated_model_proto, local_model);
2055  LOG(INFO) << "Found solution with repaired hint penalty = "
2056  << ComputeInnerObjective(updated_model_proto.objective(),
2057  updated_solution);
2058  }
2059  shared_response_manager->NewSolution(
2060  solution, absl::StrCat(solution_info, " [repaired]"), &local_model);
2061  }
2062 }
2063 
2064 // TODO(user): If this ever shows up in the profile, we could avoid copying
2065 // the mapping_proto if we are careful about how we modify the variable domain
2066 // before postsolving it. Note that 'num_variables_in_original_model' refers to
2067 // the model before presolve.
2068 void PostsolveResponseWithFullSolver(int num_variables_in_original_model,
2069  CpModelProto mapping_proto,
2070  const std::vector<int>& postsolve_mapping,
2071  std::vector<int64_t>* solution) {
2073  wall_timer.Start();
2074 
2075  // Fix the correct variable in the mapping_proto.
2076  for (int i = 0; i < solution->size(); ++i) {
2077  auto* var_proto = mapping_proto.mutable_variables(postsolve_mapping[i]);
2078  var_proto->clear_domain();
2079  var_proto->add_domain((*solution)[i]);
2080  var_proto->add_domain((*solution)[i]);
2081  }
2082 
2083  // Postosolve parameters.
2084  // TODO(user): this problem is usually trivial, but we may still want to
2085  // impose a time limit or copy some of the parameters passed by the user.
2086  Model postsolve_model;
2087  postsolve_model.Register<WallTimer>(&wall_timer);
2088  {
2089  SatParameters& params = *postsolve_model.GetOrCreate<SatParameters>();
2090  params.set_linearization_level(0);
2091  params.set_cp_model_probing_level(0);
2092  }
2093 
2094  auto* response_manager = postsolve_model.GetOrCreate<SharedResponseManager>();
2095  response_manager->InitializeObjective(mapping_proto);
2096 
2097  LoadCpModel(mapping_proto, &postsolve_model);
2098  SolveLoadedCpModel(mapping_proto, &postsolve_model);
2099  const CpSolverResponse postsolve_response = response_manager->GetResponse();
2100  CHECK(postsolve_response.status() == CpSolverStatus::FEASIBLE ||
2101  postsolve_response.status() == CpSolverStatus::OPTIMAL)
2102  << CpSolverResponseStats(postsolve_response);
2103 
2104  // We only copy the solution from the postsolve_response to the response.
2105  CHECK_LE(num_variables_in_original_model,
2106  postsolve_response.solution().size());
2107  solution->assign(
2108  postsolve_response.solution().begin(),
2109  postsolve_response.solution().begin() + num_variables_in_original_model);
2110 }
2111 
2112 void PostsolveResponseWrapper(const SatParameters& params,
2113  int num_variable_in_original_model,
2114  const CpModelProto& mapping_proto,
2115  const std::vector<int>& postsolve_mapping,
2116  std::vector<int64_t>* solution) {
2117  if (params.debug_postsolve_with_full_solver()) {
2118  PostsolveResponseWithFullSolver(num_variable_in_original_model,
2119  mapping_proto, postsolve_mapping, solution);
2120  } else {
2121  PostsolveResponse(num_variable_in_original_model, mapping_proto,
2122  postsolve_mapping, solution);
2123  }
2124 }
2125 
2126 // TODO(user): Uniformize this function with the other one.
2127 CpSolverResponse SolvePureSatModel(const CpModelProto& model_proto,
2128  WallTimer* wall_timer, Model* model,
2129  SolverLogger* logger) {
2130  std::unique_ptr<SatSolver> solver(new SatSolver());
2131  SatParameters parameters = *model->GetOrCreate<SatParameters>();
2132  solver->SetParameters(parameters);
2133  model->GetOrCreate<TimeLimit>()->ResetLimitFromParameters(parameters);
2134 
2135  // Create a DratProofHandler?
2136  std::unique_ptr<DratProofHandler> drat_proof_handler;
2137 #if !defined(__PORTABLE_PLATFORM__)
2138  if (!absl::GetFlag(FLAGS_drat_output).empty() ||
2139  absl::GetFlag(FLAGS_drat_check)) {
2140  if (!absl::GetFlag(FLAGS_drat_output).empty()) {
2141  File* output;
2142  CHECK_OK(file::Open(absl::GetFlag(FLAGS_drat_output), "w", &output,
2143  file::Defaults()));
2144  drat_proof_handler = std::make_unique<DratProofHandler>(
2145  /*in_binary_format=*/false, output, absl::GetFlag(FLAGS_drat_check));
2146  } else {
2147  drat_proof_handler = std::make_unique<DratProofHandler>();
2148  }
2149  solver->SetDratProofHandler(drat_proof_handler.get());
2150  }
2151 #endif // __PORTABLE_PLATFORM__
2152 
2153  auto get_literal = [](int ref) {
2154  if (ref >= 0) return Literal(BooleanVariable(ref), true);
2155  return Literal(BooleanVariable(NegatedRef(ref)), false);
2156  };
2157 
2158  std::vector<Literal> temp;
2159  const int num_variables = model_proto.variables_size();
2160  solver->SetNumVariables(num_variables);
2161  if (drat_proof_handler != nullptr) {
2162  drat_proof_handler->SetNumVariables(num_variables);
2163 
2164  // We load the model in the drat_proof_handler for the case where we want
2165  // to do in-memory checking.
2166  for (int ref = 0; ref < num_variables; ++ref) {
2167  const Domain domain = ReadDomainFromProto(model_proto.variables(ref));
2168  if (domain.IsFixed()) {
2169  const Literal ref_literal =
2170  domain.Min() == 0 ? get_literal(ref).Negated() : get_literal(ref);
2171  drat_proof_handler->AddProblemClause({ref_literal});
2172  }
2173  }
2174  for (const ConstraintProto& ct : model_proto.constraints()) {
2175  switch (ct.constraint_case()) {
2176  case ConstraintProto::ConstraintCase::kBoolAnd: {
2177  if (ct.enforcement_literal_size() == 0) {
2178  for (const int ref : ct.bool_and().literals()) {
2179  drat_proof_handler->AddProblemClause({get_literal(ref)});
2180  }
2181  } else {
2182  // a => b
2183  const Literal not_a =
2184  get_literal(ct.enforcement_literal(0)).Negated();
2185  for (const int ref : ct.bool_and().literals()) {
2186  drat_proof_handler->AddProblemClause({not_a, get_literal(ref)});
2187  }
2188  }
2189  break;
2190  }
2191  case ConstraintProto::ConstraintCase::kBoolOr:
2192  temp.clear();
2193  for (const int ref : ct.bool_or().literals()) {
2194  temp.push_back(get_literal(ref));
2195  }
2196  for (const int ref : ct.enforcement_literal()) {
2197  temp.push_back(get_literal(ref).Negated());
2198  }
2199  drat_proof_handler->AddProblemClause(temp);
2200  break;
2201  default:
2202  LOG(FATAL) << "Not supported";
2203  }
2204  }
2205  }
2206 
2207  for (const ConstraintProto& ct : model_proto.constraints()) {
2208  switch (ct.constraint_case()) {
2209  case ConstraintProto::ConstraintCase::kBoolAnd: {
2210  if (ct.enforcement_literal_size() == 0) {
2211  for (const int ref : ct.bool_and().literals()) {
2212  const Literal b = get_literal(ref);
2213  solver->AddUnitClause(b);
2214  }
2215  } else {
2216  // a => b
2217  const Literal not_a =
2218  get_literal(ct.enforcement_literal(0)).Negated();
2219  for (const int ref : ct.bool_and().literals()) {
2220  const Literal b = get_literal(ref);
2221  solver->AddProblemClause({not_a, b}, /*is_safe=*/false);
2222  }
2223  }
2224  break;
2225  }
2226  case ConstraintProto::ConstraintCase::kBoolOr:
2227  temp.clear();
2228  for (const int ref : ct.bool_or().literals()) {
2229  temp.push_back(get_literal(ref));
2230  }
2231  for (const int ref : ct.enforcement_literal()) {
2232  temp.push_back(get_literal(ref).Negated());
2233  }
2234  solver->AddProblemClause(temp, /*is_safe=*/false);
2235  break;
2236  default:
2237  LOG(FATAL) << "Not supported";
2238  }
2239  }
2240 
2241  // Deal with fixed variables.
2242  for (int ref = 0; ref < num_variables; ++ref) {
2243  const Domain domain = ReadDomainFromProto(model_proto.variables(ref));
2244  if (domain.Min() == domain.Max()) {
2245  const Literal ref_literal =
2246  domain.Min() == 0 ? get_literal(ref).Negated() : get_literal(ref);
2247  solver->AddUnitClause(ref_literal);
2248  }
2249  }
2250 
2252  CpSolverResponse response;
2253  if (parameters.cp_model_presolve()) {
2254  std::vector<bool> solution;
2255  status = SolveWithPresolve(&solver, model->GetOrCreate<TimeLimit>(),
2256  &solution, drat_proof_handler.get(), logger);
2257  if (status == SatSolver::FEASIBLE) {
2258  response.clear_solution();
2259  for (int ref = 0; ref < num_variables; ++ref) {
2260  response.add_solution(solution[ref]);
2261  }
2262  }
2263  } else {
2264  status = solver->SolveWithTimeLimit(model->GetOrCreate<TimeLimit>());
2265  if (status == SatSolver::FEASIBLE) {
2266  response.clear_solution();
2267  for (int ref = 0; ref < num_variables; ++ref) {
2268  response.add_solution(
2269  solver->Assignment().LiteralIsTrue(get_literal(ref)) ? 1 : 0);
2270  }
2271  }
2272  }
2273 
2274  // Tricky: the model local time limit is updated by the new functions, but
2275  // the old ones update time_limit directly.
2276  model->GetOrCreate<TimeLimit>()->AdvanceDeterministicTime(
2277  solver->model()->GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
2278 
2279  switch (status) {
2280  case SatSolver::LIMIT_REACHED: {
2281  response.set_status(CpSolverStatus::UNKNOWN);
2282  break;
2283  }
2284  case SatSolver::FEASIBLE: {
2285  CHECK(SolutionIsFeasible(
2286  model_proto, std::vector<int64_t>(response.solution().begin(),
2287  response.solution().end())));
2288  response.set_status(CpSolverStatus::OPTIMAL);
2289  break;
2290  }
2291  case SatSolver::INFEASIBLE: {
2293  break;
2294  }
2295  default:
2296  LOG(FATAL) << "Unexpected SatSolver::Status " << status;
2297  }
2298  response.set_num_booleans(solver->NumVariables());
2299  response.set_num_branches(solver->num_branches());
2300  response.set_num_conflicts(solver->num_failures());
2301  response.set_num_binary_propagations(solver->num_propagations());
2302  response.set_num_integer_propagations(0);
2303  response.set_wall_time(wall_timer->Get());
2304  response.set_deterministic_time(
2305  model->Get<TimeLimit>()->GetElapsedDeterministicTime());
2306 
2307  if (status == SatSolver::INFEASIBLE && drat_proof_handler != nullptr) {
2308  WallTimer drat_timer;
2309  drat_timer.Start();
2310  DratChecker::Status drat_status = drat_proof_handler->Check(
2311  absl::GetFlag(FLAGS_max_drat_time_in_seconds));
2312  switch (drat_status) {
2313  case DratChecker::UNKNOWN:
2314  LOG(INFO) << "DRAT status: UNKNOWN";
2315  break;
2316  case DratChecker::VALID:
2317  LOG(INFO) << "DRAT status: VALID";
2318  break;
2319  case DratChecker::INVALID:
2320  LOG(ERROR) << "DRAT status: INVALID";
2321  break;
2322  default:
2323  // Should not happen.
2324  break;
2325  }
2326  LOG(INFO) << "DRAT wall time: " << drat_timer.Get();
2327  } else if (drat_proof_handler != nullptr) {
2328  // Always log a DRAT status to make it easier to extract it from a multirun
2329  // result with awk.
2330  LOG(INFO) << "DRAT status: NA";
2331  LOG(INFO) << "DRAT wall time: NA";
2332  LOG(INFO) << "DRAT user time: NA";
2333  }
2334  return response;
2335 }
2336 
2337 #if !defined(__PORTABLE_PLATFORM__)
2338 
2339 // Small wrapper to simplify the constructions of the two SubSolver below.
2340 struct SharedClasses {
2341  CpModelProto const* model_proto;
2343  ModelSharedTimeLimit* time_limit;
2344  SharedBoundsManager* bounds;
2345  SharedResponseManager* response;
2346  SharedRelaxationSolutionRepository* relaxation_solutions;
2347  SharedLPSolutionRepository* lp_solutions;
2348  SharedIncompleteSolutionManager* incomplete_solutions;
2349  SharedClausesManager* clauses;
2351 
2352  bool SearchIsDone() {
2353  if (response->ProblemIsSolved()) return true;
2354  if (time_limit->LimitReached()) return true;
2355  return false;
2356  }
2357 };
2358 
2359 // Encapsulate a full CP-SAT solve without presolve in the SubSolver API.
2360 class FullProblemSolver : public SubSolver {
2361  public:
2362  FullProblemSolver(const std::string& name,
2363  const SatParameters& local_parameters, bool split_in_chunks,
2364  SharedClasses* shared, bool stop_at_first_solution = false)
2365  : SubSolver(name, stop_at_first_solution ? FIRST_SOLUTION : FULL_PROBLEM),
2366  shared_(shared),
2367  split_in_chunks_(split_in_chunks),
2368  local_model_(std::make_unique<Model>(name)),
2369  stop_at_first_solution_(stop_at_first_solution) {
2370  // Setup the local model parameters and time limit.
2371  *(local_model_->GetOrCreate<SatParameters>()) = local_parameters;
2372  shared_->time_limit->UpdateLocalLimit(
2373  local_model_->GetOrCreate<TimeLimit>());
2374 
2375  if (stop_at_first_solution) {
2376  local_model_->GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
2377  shared_->response->first_solution_solvers_should_stop());
2378  }
2379 
2380  if (shared->response != nullptr) {
2381  local_model_->Register<SharedResponseManager>(shared->response);
2382  }
2383 
2384  if (shared->relaxation_solutions != nullptr) {
2385  local_model_->Register<SharedRelaxationSolutionRepository>(
2386  shared->relaxation_solutions);
2387  }
2388 
2389  if (shared->lp_solutions != nullptr) {
2390  local_model_->Register<SharedLPSolutionRepository>(shared->lp_solutions);
2391  }
2392 
2393  if (shared->incomplete_solutions != nullptr) {
2394  local_model_->Register<SharedIncompleteSolutionManager>(
2395  shared->incomplete_solutions);
2396  }
2397 
2398  if (shared->bounds != nullptr) {
2399  local_model_->Register<SharedBoundsManager>(shared->bounds);
2400  }
2401 
2402  if (shared->clauses != nullptr) {
2403  local_model_->Register<SharedClausesManager>(shared->clauses);
2404  }
2405 
2406  // TODO(user): For now we do not count LNS statistics. We could easily
2407  // by registering the SharedStatistics class with LNS local model.
2408  local_model_->Register<SharedStatistics>(
2409  shared->global_model->GetOrCreate<SharedStatistics>());
2410  }
2411 
2412  ~FullProblemSolver() override {
2413  CpSolverResponse response;
2414  FillSolveStatsInResponse(local_model_.get(), &response);
2415  shared_->response->AppendResponseToBeMerged(response);
2416  }
2417 
2418  bool TaskIsAvailable() override {
2419  if (shared_->SearchIsDone()) return false;
2420 
2421  absl::MutexLock mutex_lock(&mutex_);
2422  if (stop_at_first_solution_) {
2423  return shared_->response->SolutionsRepository().NumSolutions() == 0 &&
2424  previous_task_is_completed_;
2425  } else {
2426  return previous_task_is_completed_;
2427  }
2428  }
2429 
2430  std::function<void()> GenerateTask(int64_t /*task_id*/) override {
2431  {
2432  absl::MutexLock mutex_lock(&mutex_);
2433  previous_task_is_completed_ = false;
2434  }
2435  return [this]() {
2436  if (solving_first_chunk_) {
2437  LoadCpModel(*shared_->model_proto, local_model_.get());
2438 
2439  // Level zero variable bounds sharing. It is important to register
2440  // that after the probing that takes place in LoadCpModel() otherwise
2441  // we will have a mutex contention issue when all the thread probes
2442  // at the same time.
2443  if (shared_->bounds != nullptr) {
2444  RegisterVariableBoundsLevelZeroExport(
2445  *shared_->model_proto, shared_->bounds, local_model_.get());
2446  RegisterVariableBoundsLevelZeroImport(
2447  *shared_->model_proto, shared_->bounds, local_model_.get());
2448  }
2449 
2450  // Note that this is done after the loading, so we will never export
2451  // problem clauses. We currently also never export binary clauses added
2452  // by the initial probing.
2453  if (shared_->clauses != nullptr) {
2454  const int id = shared_->clauses->RegisterNewId();
2455  shared_->clauses->SetWorkerNameForId(id, local_model_->Name());
2456 
2457  RegisterClausesLevelZeroImport(id, shared_->clauses,
2458  local_model_.get());
2459  RegisterClausesExport(id, shared_->clauses, local_model_.get());
2460  }
2461 
2462  if (local_model_->GetOrCreate<SatParameters>()->repair_hint()) {
2463  MinimizeL1DistanceWithHint(*shared_->model_proto, local_model_.get());
2464  } else {
2465  QuickSolveWithHint(*shared_->model_proto, local_model_.get());
2466  }
2467 
2468  // No need for mutex since we only run one task at the time.
2469  solving_first_chunk_ = false;
2470 
2471  if (split_in_chunks_) {
2472  // Abort first chunk and allow to schedule the next.
2473  absl::MutexLock mutex_lock(&mutex_);
2474  previous_task_is_completed_ = true;
2475  return;
2476  }
2477  }
2478 
2479  auto* time_limit = local_model_->GetOrCreate<TimeLimit>();
2480  if (split_in_chunks_) {
2481  // Configure time limit for chunk solving. Note that we do not want
2482  // to do that for the hint search for now.
2483  auto* params = local_model_->GetOrCreate<SatParameters>();
2484  params->set_max_deterministic_time(1);
2485  time_limit->ResetLimitFromParameters(*params);
2486  shared_->time_limit->UpdateLocalLimit(time_limit);
2487  }
2488 
2489  const double saved_dtime = time_limit->GetElapsedDeterministicTime();
2490  SolveLoadedCpModel(*shared_->model_proto, local_model_.get());
2491  {
2492  absl::MutexLock mutex_lock(&mutex_);
2493  deterministic_time_since_last_synchronize_ +=
2494  time_limit->GetElapsedDeterministicTime() - saved_dtime;
2495  }
2496 
2497  // Abort if the problem is solved.
2498  if (shared_->SearchIsDone()) {
2499  shared_->time_limit->Stop();
2500  return;
2501  }
2502 
2503  // In this mode, we allow to generate more task.
2504  if (split_in_chunks_) {
2505  absl::MutexLock mutex_lock(&mutex_);
2506  previous_task_is_completed_ = true;
2507  return;
2508  }
2509 
2510  // Once a solver is done clear its memory and do not wait for the
2511  // destruction of the SubSolver. This is important because the full solve
2512  // might not be done at all, for instance this might have been configured
2513  // with stop_after_first_solution.
2514  local_model_.reset();
2515  };
2516  }
2517 
2518  // TODO(user): A few of the information sharing we do between threads does not
2519  // happen here (bound sharing, RINS neighborhood, objective). Fix that so we
2520  // can have a deterministic parallel mode.
2521  void Synchronize() override {
2522  absl::MutexLock mutex_lock(&mutex_);
2523  deterministic_time_ += deterministic_time_since_last_synchronize_;
2524  shared_->time_limit->AdvanceDeterministicTime(
2525  deterministic_time_since_last_synchronize_);
2526  deterministic_time_since_last_synchronize_ = 0.0;
2527  }
2528 
2529  std::string StatisticsString() const override {
2530  // The local model may have been deleted at the end of GenerateTask.
2531  // Do not crash in this case.
2532  // TODO(user): Revisit this case.
2533  if (local_model_ == nullptr) return std::string();
2534 
2535  // Padding.
2536  const std::string p4(4, ' ');
2537  const std::string p6(6, ' ');
2538 
2539  std::string s;
2540  CpSolverResponse r;
2541  FillSolveStatsInResponse(local_model_.get(), &r);
2542  absl::StrAppend(&s, p4, "Search statistics:\n");
2543  absl::StrAppend(&s, p6, "booleans: ", FormatCounter(r.num_booleans()),
2544  "\n");
2545  absl::StrAppend(&s, p6, "conflicts: ", FormatCounter(r.num_conflicts()),
2546  "\n");
2547  absl::StrAppend(&s, p6, "branches: ", FormatCounter(r.num_branches()),
2548  "\n");
2549  absl::StrAppend(&s, p6, "binary_propagations: ",
2550  FormatCounter(r.num_binary_propagations()), "\n");
2551  absl::StrAppend(&s, p6, "integer_propagations: ",
2552  FormatCounter(r.num_integer_propagations()), "\n");
2553  absl::StrAppend(&s, p6, "restarts: ", FormatCounter(r.num_restarts()),
2554  "\n");
2555 
2556  const auto& lps =
2557  *local_model_->GetOrCreate<LinearProgrammingConstraintCollection>();
2558  int num_displayed = 0;
2559  for (const auto* lp : lps) {
2560  if (num_displayed++ > 6) {
2561  absl::StrAppend(&s, p4, "Skipping other LPs...\n");
2562  absl::StrAppend(&s, p6, "- ", lps.size(), " total independent LPs.\n");
2563  break;
2564  }
2565 
2566  const std::string raw_statistics = lp->Statistics();
2567  const std::vector<absl::string_view> lines =
2568  absl::StrSplit(raw_statistics, '\n', absl::SkipEmpty());
2569  for (const absl::string_view& line : lines) {
2570  absl::StrAppend(&s, p4, line, "\n");
2571  }
2572  }
2573  return s;
2574  }
2575 
2576  private:
2577  SharedClasses* shared_;
2578  const bool split_in_chunks_;
2579  std::unique_ptr<Model> local_model_;
2580 
2581  // The first chunk is special. It is the one in which we load the model and
2582  // try to follow the hint.
2583  bool solving_first_chunk_ = true;
2584 
2585  absl::Mutex mutex_;
2586  double deterministic_time_since_last_synchronize_ ABSL_GUARDED_BY(mutex_) =
2587  0.0;
2588  bool previous_task_is_completed_ ABSL_GUARDED_BY(mutex_) = true;
2589  bool stop_at_first_solution_;
2590 };
2591 
2592 class FeasibilityPumpSolver : public SubSolver {
2593  public:
2594  FeasibilityPumpSolver(const SatParameters& local_parameters,
2595  SharedClasses* shared)
2596  : SubSolver("feasibility_pump", INCOMPLETE),
2597  shared_(shared),
2598  local_model_(std::make_unique<Model>(name_)) {
2599  // Setup the local model parameters and time limit.
2600  *(local_model_->GetOrCreate<SatParameters>()) = local_parameters;
2601  shared_->time_limit->UpdateLocalLimit(
2602  local_model_->GetOrCreate<TimeLimit>());
2603 
2604  if (shared->response != nullptr) {
2605  local_model_->Register<SharedResponseManager>(shared->response);
2606  }
2607 
2608  if (shared->relaxation_solutions != nullptr) {
2609  local_model_->Register<SharedRelaxationSolutionRepository>(
2610  shared->relaxation_solutions);
2611  }
2612 
2613  if (shared->lp_solutions != nullptr) {
2614  local_model_->Register<SharedLPSolutionRepository>(shared->lp_solutions);
2615  }
2616 
2617  if (shared->incomplete_solutions != nullptr) {
2618  local_model_->Register<SharedIncompleteSolutionManager>(
2619  shared->incomplete_solutions);
2620  }
2621 
2622  // Level zero variable bounds sharing.
2623  if (shared_->bounds != nullptr) {
2624  RegisterVariableBoundsLevelZeroImport(
2625  *shared_->model_proto, shared_->bounds, local_model_.get());
2626  }
2627  }
2628 
2629  bool TaskIsAvailable() override {
2630  if (shared_->SearchIsDone()) return false;
2631  absl::MutexLock mutex_lock(&mutex_);
2632  return previous_task_is_completed_;
2633  }
2634 
2635  std::function<void()> GenerateTask(int64_t /*task_id*/) override {
2636  return [this]() {
2637  {
2638  absl::MutexLock mutex_lock(&mutex_);
2639  if (!previous_task_is_completed_) return;
2640  previous_task_is_completed_ = false;
2641  }
2642  {
2643  absl::MutexLock mutex_lock(&mutex_);
2644  if (solving_first_chunk_) {
2645  LoadFeasibilityPump(*shared_->model_proto, local_model_.get());
2646  // No new task will be scheduled for this worker if there is no
2647  // linear relaxation.
2648  if (local_model_->Get<FeasibilityPump>() == nullptr) return;
2649  solving_first_chunk_ = false;
2650  // Abort first chunk and allow to schedule the next.
2651  previous_task_is_completed_ = true;
2652  return;
2653  }
2654  }
2655 
2656  auto* time_limit = local_model_->GetOrCreate<TimeLimit>();
2657  const double saved_dtime = time_limit->GetElapsedDeterministicTime();
2658  auto* feasibility_pump = local_model_->Mutable<FeasibilityPump>();
2659  if (!feasibility_pump->Solve()) {
2660  shared_->response->NotifyThatImprovingProblemIsInfeasible(name_);
2661  }
2662 
2663  {
2664  absl::MutexLock mutex_lock(&mutex_);
2665  deterministic_time_since_last_synchronize_ +=
2666  time_limit->GetElapsedDeterministicTime() - saved_dtime;
2667  }
2668 
2669  // Abort if the problem is solved.
2670  if (shared_->SearchIsDone()) {
2671  shared_->time_limit->Stop();
2672  return;
2673  }
2674 
2675  absl::MutexLock mutex_lock(&mutex_);
2676  previous_task_is_completed_ = true;
2677  };
2678  }
2679 
2680  void Synchronize() override {
2681  absl::MutexLock mutex_lock(&mutex_);
2682  deterministic_time_ += deterministic_time_since_last_synchronize_;
2683  shared_->time_limit->AdvanceDeterministicTime(
2684  deterministic_time_since_last_synchronize_);
2685  deterministic_time_since_last_synchronize_ = 0.0;
2686  }
2687 
2688  // TODO(user): Display feasibility pump statistics.
2689 
2690  private:
2691  SharedClasses* shared_;
2692  std::unique_ptr<Model> local_model_;
2693 
2694  absl::Mutex mutex_;
2695 
2696  // The first chunk is special. It is the one in which we load the linear
2697  // constraints.
2698  bool solving_first_chunk_ ABSL_GUARDED_BY(mutex_) = true;
2699 
2700  double deterministic_time_since_last_synchronize_ ABSL_GUARDED_BY(mutex_) =
2701  0.0;
2702  bool previous_task_is_completed_ ABSL_GUARDED_BY(mutex_) = true;
2703 };
2704 
2705 // A Subsolver that generate LNS solve from a given neighborhood.
2706 class LnsSolver : public SubSolver {
2707  public:
2708  LnsSolver(std::unique_ptr<NeighborhoodGenerator> generator,
2709  const SatParameters& parameters,
2710  NeighborhoodGeneratorHelper* helper, SharedClasses* shared)
2711  : SubSolver(generator->name(), INCOMPLETE),
2712  generator_(std::move(generator)),
2713  helper_(helper),
2714  parameters_(parameters),
2715  shared_(shared) {}
2716 
2717  bool TaskIsAvailable() override {
2718  if (shared_->SearchIsDone()) return false;
2719  return generator_->ReadyToGenerate();
2720  }
2721 
2722  std::function<void()> GenerateTask(int64_t task_id) override {
2723  return [task_id, this]() {
2724  if (shared_->SearchIsDone()) return;
2725 
2726  // Create a random number generator whose seed depends both on the task_id
2727  // and on the parameters_.random_seed() so that changing the later will
2728  // change the LNS behavior.
2729  const int32_t low = static_cast<int32_t>(task_id);
2730  const int32_t high = static_cast<int32_t>(task_id >> 32);
2731  std::seed_seq seed{low, high, parameters_.random_seed()};
2732  random_engine_t random(seed);
2733 
2734  NeighborhoodGenerator::SolveData data;
2735  data.difficulty = generator_->difficulty();
2736  data.deterministic_limit = generator_->deterministic_limit();
2737 
2738  // Choose a base solution for this neighborhood.
2739  CpSolverResponse base_response;
2740  {
2741  const SharedSolutionRepository<int64_t>& repo =
2742  shared_->response->SolutionsRepository();
2743  if (repo.NumSolutions() > 0) {
2744  base_response.set_status(CpSolverStatus::FEASIBLE);
2745  const SharedSolutionRepository<int64_t>::Solution solution =
2746  repo.GetRandomBiasedSolution(random);
2747  for (const int64_t value : solution.variable_values) {
2748  base_response.add_solution(value);
2749  }
2750 
2751  // Note: We assume that the solution rank is the solution internal
2752  // objective.
2753  data.initial_best_objective = repo.GetSolution(0).rank;
2754  data.base_objective = solution.rank;
2755  } else {
2756  base_response.set_status(CpSolverStatus::UNKNOWN);
2757 
2758  // If we do not have a solution, we use the current objective upper
2759  // bound so that our code that compute an "objective" improvement
2760  // works.
2761  //
2762  // TODO(user): this is non-deterministic. Fix.
2763  data.initial_best_objective =
2764  shared_->response->GetInnerObjectiveUpperBound();
2765  data.base_objective = data.initial_best_objective;
2766  }
2767  }
2768 
2769  Neighborhood neighborhood =
2770  generator_->Generate(base_response, data.difficulty, random);
2771 
2772  if (!neighborhood.is_generated) return;
2773 
2774  const int64_t num_calls = std::max(int64_t{1}, generator_->num_calls());
2775  const double fully_solved_proportion =
2776  static_cast<double>(generator_->num_fully_solved_calls()) /
2777  static_cast<double>(num_calls);
2778  std::string source_info = name();
2779  if (!neighborhood.source_info.empty()) {
2780  absl::StrAppend(&source_info, "_", neighborhood.source_info);
2781  }
2782  const std::string lns_info = absl::StrFormat(
2783  "%s(d=%0.2f s=%i t=%0.2f p=%0.2f)", source_info, data.difficulty,
2784  task_id, data.deterministic_limit, fully_solved_proportion);
2785 
2786  SatParameters local_params(parameters_);
2787  local_params.set_max_deterministic_time(data.deterministic_limit);
2788  local_params.set_stop_after_first_solution(false);
2789  local_params.set_cp_model_presolve(true);
2790  local_params.set_log_search_progress(false);
2791  local_params.set_cp_model_probing_level(0);
2792  local_params.set_symmetry_level(0);
2793  local_params.set_find_big_linear_overlap(false);
2794  local_params.set_solution_pool_size(1); // Keep the best solution found.
2795 
2796  Model local_model(lns_info);
2797  *(local_model.GetOrCreate<SatParameters>()) = local_params;
2798  TimeLimit* local_time_limit = local_model.GetOrCreate<TimeLimit>();
2799  local_time_limit->ResetLimitFromParameters(local_params);
2800  shared_->time_limit->UpdateLocalLimit(local_time_limit);
2801 
2802  // Presolve and solve the LNS fragment.
2803  CpModelProto lns_fragment;
2804  CpModelProto mapping_proto;
2805  auto context = std::make_unique<PresolveContext>(
2806  &local_model, &lns_fragment, &mapping_proto);
2807 
2808  *lns_fragment.mutable_variables() = neighborhood.delta.variables();
2809  {
2810  ModelCopy copier(context.get());
2811 
2812  // Copy and simplify the constraints from the initial model.
2813  if (!copier.ImportAndSimplifyConstraints(
2814  helper_->ModelProto(), neighborhood.constraints_to_ignore)) {
2815  return;
2816  }
2817 
2818  // Copy and simplify the constraints from the delta model.
2819  if (!neighborhood.delta.constraints().empty() &&
2820  !copier.ImportAndSimplifyConstraints(neighborhood.delta, {})) {
2821  return;
2822  }
2823  }
2824 
2825  // Copy the rest of the model and overwrite the name.
2827  helper_->ModelProto(), context.get());
2828  lns_fragment.set_name(absl::StrCat("lns_", task_id));
2829 
2830  // Overwrite solution hinting.
2831  if (neighborhood.delta.has_solution_hint()) {
2832  *lns_fragment.mutable_solution_hint() =
2833  neighborhood.delta.solution_hint();
2834  }
2835 
2836  CpModelProto debug_copy;
2837  if (absl::GetFlag(FLAGS_cp_model_dump_problematic_lns)) {
2838  // We need to make a copy because the presolve is destructive.
2839  // It is why we do not do that by default.
2840  debug_copy = lns_fragment;
2841  }
2842 
2843 #if !defined(__PORTABLE_PLATFORM__)
2844 #endif // __PORTABLE_PLATFORM__
2845 
2846  if (absl::GetFlag(FLAGS_cp_model_dump_lns)) {
2847  // TODO(user): export the delta too if needed.
2848  const std::string lns_name =
2849  absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
2850  lns_fragment.name(), ".pb.txt");
2851  LOG(INFO) << "Dumping LNS model to '" << lns_name << "'.";
2852  CHECK(WriteModelProtoToFile(lns_fragment, lns_name));
2853  }
2854 
2855  std::vector<int> postsolve_mapping;
2856  const CpSolverStatus presolve_status =
2857  PresolveCpModel(context.get(), &postsolve_mapping);
2858 
2859  // Release the context.
2860  context.reset(nullptr);
2861  neighborhood.delta.Clear();
2862 
2863  // TODO(user): Depending on the problem, we should probably use the
2864  // parameters that work bests (core, linearization_level, etc...) or
2865  // maybe we can just randomize them like for the base solution used.
2866  auto* local_response_manager =
2867  local_model.GetOrCreate<SharedResponseManager>();
2868  local_response_manager->InitializeObjective(lns_fragment);
2869  local_response_manager->SetSynchronizationMode(true);
2870 
2871  CpSolverResponse local_response;
2872  if (presolve_status == CpSolverStatus::UNKNOWN) {
2873  LoadCpModel(lns_fragment, &local_model);
2874  QuickSolveWithHint(lns_fragment, &local_model);
2875  SolveLoadedCpModel(lns_fragment, &local_model);
2876  local_response = local_response_manager->GetResponse();
2877  // In case the LNS model is empty after presolve, the solution
2878  // repository does not add the solution, and thus does not store the
2879  // solution info. In that case, we put it back.
2880  if (local_response.solution_info().empty()) {
2881  local_response.set_solution_info(
2882  absl::StrCat(lns_info, " [presolve]"));
2883  }
2884  } else {
2885  // TODO(user): Clean this up? when the model is closed by presolve,
2886  // we don't have a nice api to get the response with stats. That said
2887  // for LNS, we don't really need it.
2888  if (presolve_status == CpSolverStatus::INFEASIBLE) {
2889  local_response_manager->NotifyThatImprovingProblemIsInfeasible(
2890  "presolve");
2891  }
2892  local_response = local_response_manager->GetResponse();
2893  local_response.set_status(presolve_status);
2894  }
2895  const std::string solution_info = local_response.solution_info();
2896  std::vector<int64_t> solution_values(local_response.solution().begin(),
2897  local_response.solution().end());
2898 
2899  data.status = local_response.status();
2900  // TODO(user): we actually do not need to postsolve if the solution is
2901  // not going to be used...
2902  if (data.status == CpSolverStatus::OPTIMAL ||
2903  data.status == CpSolverStatus::FEASIBLE) {
2904  PostsolveResponseWrapper(
2905  local_params, helper_->ModelProto().variables_size(), mapping_proto,
2906  postsolve_mapping, &solution_values);
2907  local_response.mutable_solution()->Assign(solution_values.begin(),
2908  solution_values.end());
2909  }
2910 
2911  data.deterministic_time = local_time_limit->GetElapsedDeterministicTime();
2912 
2913  bool new_solution = false;
2914  bool display_lns_info = VLOG_IS_ON(2);
2915  if (!local_response.solution().empty()) {
2916  // A solution that does not pass our validator indicates a bug. We
2917  // abort and dump the problematic model to facilitate debugging.
2918  //
2919  // TODO(user): In a production environment, we should probably just
2920  // ignore this fragment and continue.
2921  const bool feasible =
2922  SolutionIsFeasible(*shared_->model_proto, solution_values);
2923  if (!feasible) {
2924  if (absl::GetFlag(FLAGS_cp_model_dump_problematic_lns)) {
2925  const std::string name =
2926  absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
2927  debug_copy.name(), ".pb.txt");
2928  LOG(INFO) << "Dumping problematic LNS model to '" << name << "'.";
2929  CHECK(WriteModelProtoToFile(debug_copy, name));
2930  }
2931  LOG(FATAL) << "Infeasible LNS solution! " << solution_info
2932  << " solved with params "
2933  << local_params.ShortDebugString();
2934  }
2935 
2936  // Special case if we solved a part of the full problem!
2937  //
2938  // TODO(user): This do not work if they are symmetries loaded into SAT.
2939  // For now we just disable this if there is any symmetry. See for
2940  // instance spot5_1401.fzn. Be smarter about that.
2941  //
2942  // The issue is that as we fix level zero variables from a partial
2943  // solution, the symmetry propagator could wrongly fix other variables
2944  // since it assumes that if we could infer such fixing, then we could
2945  // do the same in any symmetric situation.
2946  //
2947  // Note sure how to address that, we could disable symmetries if there
2948  // is a lot of connected components. Or use a different mechanism than
2949  // just fixing variables. Or remove symmetry on the fly?
2950  //
2951  // TODO(user): At least enable it if there is no Boolean symmetries
2952  // since we currently do not use the other ones past the presolve.
2953  //
2954  // TODO(user): We could however fix it in the LNS Helper!
2955  if (data.status == CpSolverStatus::OPTIMAL &&
2956  !shared_->model_proto->has_symmetry() && !solution_values.empty() &&
2957  neighborhood.is_simple &&
2958  !neighborhood.variables_that_can_be_fixed_to_local_optimum
2959  .empty()) {
2960  display_lns_info = true;
2961  shared_->bounds->FixVariablesFromPartialSolution(
2962  solution_values,
2963  neighborhood.variables_that_can_be_fixed_to_local_optimum);
2964  }
2965 
2966  // Finish to fill the SolveData now that the local solve is done.
2967  data.new_objective = data.base_objective;
2968  if (data.status == CpSolverStatus::OPTIMAL ||
2969  data.status == CpSolverStatus::FEASIBLE) {
2970  data.new_objective = IntegerValue(ComputeInnerObjective(
2971  shared_->model_proto->objective(), solution_values));
2972  }
2973 
2974  // Report any feasible solution we have. Optimization: We don't do that
2975  // if we just recovered the base solution.
2976  if (data.status == CpSolverStatus::OPTIMAL ||
2977  data.status == CpSolverStatus::FEASIBLE) {
2978  const std::vector<int64_t> base_solution(
2979  base_response.solution().begin(), base_response.solution().end());
2980  if (solution_values != base_solution) {
2981  new_solution = true;
2982  shared_->response->NewSolution(solution_values, solution_info,
2983  /*model=*/nullptr);
2984  }
2985  }
2986  if (!neighborhood.is_reduced &&
2987  (data.status == CpSolverStatus::OPTIMAL ||
2988  data.status == CpSolverStatus::INFEASIBLE)) {
2989  shared_->response->NotifyThatImprovingProblemIsInfeasible(
2990  solution_info);
2991  shared_->time_limit->Stop();
2992  }
2993  }
2994 
2995  generator_->AddSolveData(data);
2996 
2997  if (VLOG_IS_ON(1) && display_lns_info) {
2998  auto* logger = shared_->global_model->GetOrCreate<SolverLogger>();
2999  std::string s = absl::StrCat(" LNS ", name(), ":");
3000  if (new_solution) {
3001  const double base_obj = ScaleObjectiveValue(
3002  shared_->model_proto->objective(),
3003  ComputeInnerObjective(shared_->model_proto->objective(),
3004  base_response.solution()));
3005  const double new_obj = ScaleObjectiveValue(
3006  shared_->model_proto->objective(),
3007  ComputeInnerObjective(shared_->model_proto->objective(),
3008  solution_values));
3009  absl::StrAppend(&s, " [new_sol:", base_obj, " -> ", new_obj, "]");
3010  }
3011  if (neighborhood.is_simple) {
3012  absl::StrAppend(
3013  &s, " [", "relaxed:", neighborhood.num_relaxed_variables,
3014  " in_obj:", neighborhood.num_relaxed_variables_in_objective,
3015  " compo:",
3016  neighborhood.variables_that_can_be_fixed_to_local_optimum.size(),
3017  "]");
3018  }
3019  SOLVER_LOG(logger, s, " [d:", data.difficulty, ", id:", task_id,
3020  ", dtime:", data.deterministic_time, "/",
3021  data.deterministic_limit,
3022  ", status:", ProtoEnumToString<CpSolverStatus>(data.status),
3023  ", #calls:", generator_->num_calls(),
3024  ", p:", fully_solved_proportion, "]");
3025  }
3026  };
3027  }
3028 
3029  void Synchronize() override {
3030  generator_->Synchronize();
3031  const double old = deterministic_time_;
3032  deterministic_time_ = generator_->deterministic_time();
3033  shared_->time_limit->AdvanceDeterministicTime(deterministic_time_ - old);
3034  }
3035 
3036  // TODO(user): Display LNS success rate.
3037 
3038  private:
3039  std::unique_ptr<NeighborhoodGenerator> generator_;
3040  NeighborhoodGeneratorHelper* helper_;
3041  const SatParameters parameters_;
3042  SharedClasses* shared_;
3043 };
3044 
3045 void SolveCpModelParallel(const CpModelProto& model_proto,
3046  Model* global_model) {
3047  const SatParameters& params = *global_model->GetOrCreate<SatParameters>();
3048  CHECK(!params.enumerate_all_solutions())
3049  << "Enumerating all solutions in parallel is not supported.";
3050  if (global_model->GetOrCreate<TimeLimit>()->LimitReached()) return;
3051 
3052  std::unique_ptr<SharedBoundsManager> shared_bounds_manager;
3053  if (params.share_level_zero_bounds()) {
3054  shared_bounds_manager = std::make_unique<SharedBoundsManager>(model_proto);
3055  shared_bounds_manager->LoadDebugSolution(
3056  global_model->GetOrCreate<SharedResponseManager>()->DebugSolution());
3057  }
3058 
3059  std::unique_ptr<SharedRelaxationSolutionRepository>
3060  shared_relaxation_solutions;
3061 
3062  auto shared_lp_solutions = std::make_unique<SharedLPSolutionRepository>(
3063  /*num_solutions_to_keep=*/10);
3064  global_model->Register<SharedLPSolutionRepository>(shared_lp_solutions.get());
3065 
3066  // We currently only use the feasiblity pump if it is enabled and some other
3067  // parameters are not on.
3068  std::unique_ptr<SharedIncompleteSolutionManager> shared_incomplete_solutions;
3069  const bool use_feasibility_pump =
3070  params.use_feasibility_pump() && params.linearization_level() > 0 &&
3071  !params.use_lns_only() && !params.interleave_search();
3072  if (use_feasibility_pump) {
3073  shared_incomplete_solutions =
3074  std::make_unique<SharedIncompleteSolutionManager>();
3075  global_model->Register<SharedIncompleteSolutionManager>(
3076  shared_incomplete_solutions.get());
3077  }
3078 
3079  // Set up synchronization mode in parallel.
3080  const bool always_synchronize =
3081  !params.interleave_search() || params.num_workers() <= 1;
3082 
3083  std::unique_ptr<SharedClausesManager> shared_clauses;
3084  if (params.share_binary_clauses()) {
3085  shared_clauses = std::make_unique<SharedClausesManager>(always_synchronize);
3086  }
3087 
3088  SharedResponseManager* shared_response_manager =
3089  global_model->GetOrCreate<SharedResponseManager>();
3090  shared_response_manager->SetSynchronizationMode(always_synchronize);
3091 
3092  SharedClasses shared;
3093  shared.model_proto = &model_proto;
3094  shared.wall_timer = global_model->GetOrCreate<WallTimer>();
3095  shared.time_limit = global_model->GetOrCreate<ModelSharedTimeLimit>();
3096  shared.bounds = shared_bounds_manager.get();
3097  shared.response = shared_response_manager;
3098  shared.relaxation_solutions = shared_relaxation_solutions.get();
3099  shared.lp_solutions = shared_lp_solutions.get();
3100  shared.incomplete_solutions = shared_incomplete_solutions.get();
3101  shared.clauses = shared_clauses.get();
3102  shared.global_model = global_model;
3103 
3104  // The list of all the SubSolver that will be used in this parallel search.
3105  std::vector<std::unique_ptr<SubSolver>> subsolvers;
3106  std::vector<std::unique_ptr<SubSolver>> incomplete_subsolvers;
3107 
3108  // Add a synchronization point for the shared classes.
3109  subsolvers.push_back(std::make_unique<SynchronizationPoint>(
3110  "synchronization_agent", [&shared]() {
3111  shared.response->Synchronize();
3112  shared.response->MutableSolutionsRepository()->Synchronize();
3113  if (shared.bounds != nullptr) {
3114  shared.bounds->Synchronize();
3115  }
3116  if (shared.relaxation_solutions != nullptr) {
3117  shared.relaxation_solutions->Synchronize();
3118  }
3119  if (shared.lp_solutions != nullptr) {
3120  shared.lp_solutions->Synchronize();
3121  }
3122  if (shared.clauses != nullptr) {
3123  shared.clauses->Synchronize();
3124  }
3125  if (shared.time_limit->LimitReached()) {
3126  *(shared.response->first_solution_solvers_should_stop()) = true;
3127  }
3128  }));
3129 
3130  int num_full_problem_solvers = 0;
3131  if (params.use_lns_only()) {
3132  // Register something to find a first solution. Note that this is mainly
3133  // used for experimentation, and using no LP ususally result in a faster
3134  // first solution.
3135  //
3136  // TODO(user): merge code with standard solver. Just make sure that all
3137  // full solvers die after the first solution has been found.
3138  SatParameters local_params = params;
3139  local_params.set_stop_after_first_solution(true);
3140  local_params.set_linearization_level(0);
3141  subsolvers.push_back(std::make_unique<FullProblemSolver>(
3142  "first_solution", local_params,
3143  /*split_in_chunks=*/false, &shared));
3144  } else {
3145  for (const SatParameters& local_params :
3147  // TODO(user): This is currently not supported here.
3148  if (params.optimize_with_max_hs()) continue;
3149 
3150  subsolvers.push_back(std::make_unique<FullProblemSolver>(
3151  local_params.name(), local_params,
3152  /*split_in_chunks=*/params.interleave_search(), &shared));
3153  num_full_problem_solvers++;
3154  }
3155  }
3156 
3157  // Add FeasibilityPumpSolver if enabled.
3158  if (use_feasibility_pump) {
3159  incomplete_subsolvers.push_back(
3160  std::make_unique<FeasibilityPumpSolver>(params, &shared));
3161  }
3162 
3163  // Add the NeighborhoodGeneratorHelper as a special subsolver so that its
3164  // Synchronize() is called before any LNS neighborhood solvers.
3165  auto unique_helper = std::make_unique<NeighborhoodGeneratorHelper>(
3166  &model_proto, &params, shared.response, shared.bounds);
3167  NeighborhoodGeneratorHelper* helper = unique_helper.get();
3168  subsolvers.push_back(std::move(unique_helper));
3169 
3170  // By default we use the user provided parameters.
3171  SatParameters local_params = params;
3172  local_params.set_name("default");
3173  // TODO(user): for now this is not deterministic so we disable it on
3174  // interleave search. Fix.
3175  if (params.use_rins_lns() && !params.interleave_search()) {
3176  // Note that we always create the SharedLPSolutionRepository. This meets
3177  // the requirement of having at least one of
3178  // SharedRelaxationSolutionRepository or SharedLPSolutionRepository to
3179  // create RINS/RENS lns generators.
3180 
3181  // RINS.
3182  incomplete_subsolvers.push_back(std::make_unique<LnsSolver>(
3183  std::make_unique<RelaxationInducedNeighborhoodGenerator>(
3184  helper, shared.response, shared.relaxation_solutions,
3185  shared.lp_solutions, /*incomplete_solutions=*/nullptr,
3186  absl::StrCat("rins_lns_", local_params.name())),
3187  local_params, helper, &shared));
3188 
3189  // RENS.
3190  incomplete_subsolvers.push_back(std::make_unique<LnsSolver>(
3191  std::make_unique<RelaxationInducedNeighborhoodGenerator>(
3192  helper, /*response_manager=*/nullptr, shared.relaxation_solutions,
3193  shared.lp_solutions, shared.incomplete_solutions,
3194  absl::StrCat("rens_lns_", local_params.name())),
3195  local_params, helper, &shared));
3196  }
3197 
3198  // Adds first solution subsolvers.
3199  //
3200  // The logic is the following. Before the first solution is found, we have (in
3201  // order):
3202  // - num_full_problem_solvers full problem solvers
3203  // - num_workers - num_full_problem_solvers -
3204  // num_dedicated_incomplete_solvers first solution solvers.
3205  // - num_workers - num_full_problem_solvers incomplete solvers. Only
3206  // num_dedicated_incomplete_solvers are active before the first solution
3207  // is found.
3208  //
3209  // After the first solution is found, all first solution solvers die, the we
3210  // have num_full_problem_solvers null problem solvers, and the rest are
3211  // incomplete solvers.
3212  //
3213  // TODO(user): Check with interleave_search.
3214  if (!model_proto.has_objective() || model_proto.objective().vars().empty() ||
3215  !params.interleave_search()) {
3216  const int max_num_incomplete_solvers_running_before_the_first_solution =
3217  params.num_workers() <= 8 ? 1 : (params.num_workers() <= 16 ? 2 : 3);
3218  const int num_reserved_incomplete_solvers = std::min<int>(
3219  max_num_incomplete_solvers_running_before_the_first_solution,
3220  incomplete_subsolvers.size());
3221  const int num_first_solution_subsolvers = params.num_workers() -
3222  num_full_problem_solvers -
3223  num_reserved_incomplete_solvers;
3224 
3225  for (const SatParameters& local_params : GetFirstSolutionParams(
3226  params, model_proto, num_first_solution_subsolvers)) {
3227  subsolvers.push_back(std::make_unique<FullProblemSolver>(
3228  local_params.name(), local_params,
3229  /*split_in_chunks=*/params.interleave_search(), &shared,
3230  /*stop_on_first_solution=*/true));
3231  }
3232  }
3233 
3234  // Now that first solutions solvers are in place, we can move the
3235  // incomplete_subsolvers into subsolvers.
3236  for (int i = 0; i < incomplete_subsolvers.size(); ++i) {
3237  subsolvers.push_back(std::move(incomplete_subsolvers[i]));
3238  }
3239  incomplete_subsolvers.clear();
3240 
3241  // Add incomplete subsolvers that require an objective.
3242  if (model_proto.has_objective() && !model_proto.objective().vars().empty()) {
3243  // Enqueue all the possible LNS neighborhood subsolvers.
3244  // Each will have their own metrics.
3245  subsolvers.push_back(std::make_unique<LnsSolver>(
3246  std::make_unique<RelaxRandomVariablesGenerator>(
3247  helper, absl::StrCat("rnd_var_lns_", local_params.name())),
3248  local_params, helper, &shared));
3249  subsolvers.push_back(std::make_unique<LnsSolver>(
3250  std::make_unique<RelaxRandomConstraintsGenerator>(
3251  helper, absl::StrCat("rnd_cst_lns_", local_params.name())),
3252  local_params, helper, &shared));
3253  subsolvers.push_back(std::make_unique<LnsSolver>(
3254  std::make_unique<VariableGraphNeighborhoodGenerator>(
3255  helper, absl::StrCat("graph_var_lns_", local_params.name())),
3256  local_params, helper, &shared));
3257  subsolvers.push_back(std::make_unique<LnsSolver>(
3258  std::make_unique<ConstraintGraphNeighborhoodGenerator>(
3259  helper, absl::StrCat("graph_cst_lns_", local_params.name())),
3260  local_params, helper, &shared));
3261 
3262  // Create the rnd_obj_lns worker if the number of terms in the objective is
3263  // big enough, and it is no more than half the number of variables in the
3264  // model.
3265  if (model_proto.objective().vars().size() >=
3266  params.objective_lns_min_size() &&
3267  model_proto.objective().vars_size() >=
3268  model_proto.objective().vars().size() * 2) {
3269  subsolvers.push_back(std::make_unique<LnsSolver>(
3270  std::make_unique<RelaxObjectiveVariablesGenerator>(
3271  helper, absl::StrCat("rnd_obj_lns_", local_params.name())),
3272  local_params, helper, &shared));
3273  }
3274 
3275  // TODO(user): If we have a model with scheduling + routing. We create
3276  // a lot of LNS generators. Investigate if we can reduce this number.
3277  if (!helper->TypeToConstraints(ConstraintProto::kNoOverlap).empty() ||
3278  !helper->TypeToConstraints(ConstraintProto::kNoOverlap2D).empty() ||
3279  !helper->TypeToConstraints(ConstraintProto::kCumulative).empty()) {
3280  subsolvers.push_back(std::make_unique<LnsSolver>(
3281  std::make_unique<RandomIntervalSchedulingNeighborhoodGenerator>(
3282  helper, absl::StrCat("scheduling_random_intervals_lns_",
3283  local_params.name())),
3284  local_params, helper, &shared));
3285  subsolvers.push_back(std::make_unique<LnsSolver>(
3286  std::make_unique<RandomPrecedenceSchedulingNeighborhoodGenerator>(
3287  helper, absl::StrCat("scheduling_random_precedences_lns_",
3288  local_params.name())),
3289  local_params, helper, &shared));
3290  subsolvers.push_back(std::make_unique<LnsSolver>(
3291  std::make_unique<SchedulingTimeWindowNeighborhoodGenerator>(
3292  helper,
3293  absl::StrCat("scheduling_time_window_lns_", local_params.name())),
3294  local_params, helper, &shared));
3295 
3296  const std::vector<std::vector<int>> intervals_in_constraints =
3297  helper->GetUniqueIntervalSets();
3298  if (intervals_in_constraints.size() > 2) {
3299  subsolvers.push_back(std::make_unique<LnsSolver>(
3300  std::make_unique<SchedulingResourceWindowsNeighborhoodGenerator>(
3301  helper, intervals_in_constraints,
3302  absl::StrCat("scheduling_resource_windows_lns_",
3303  local_params.name())),
3304  local_params, helper, &shared));
3305  }
3306  }
3307 
3308  const int num_circuit =
3309  helper->TypeToConstraints(ConstraintProto::kCircuit).size();
3310  const int num_routes =
3311  helper->TypeToConstraints(ConstraintProto::kRoutes).size();
3312  if (num_circuit + num_routes > 0) {
3313  subsolvers.push_back(std::make_unique<LnsSolver>(
3314  std::make_unique<RoutingRandomNeighborhoodGenerator>(
3315  helper, absl::StrCat("routing_random_lns_", local_params.name())),
3316  local_params, helper, &shared));
3317 
3318  subsolvers.push_back(std::make_unique<LnsSolver>(
3319  std::make_unique<RoutingPathNeighborhoodGenerator>(
3320  helper, absl::StrCat("routing_path_lns_", local_params.name())),
3321  local_params, helper, &shared));
3322  }
3323  if (num_routes > 0 || num_circuit > 1) {
3324  subsolvers.push_back(std::make_unique<LnsSolver>(
3325  std::make_unique<RoutingFullPathNeighborhoodGenerator>(
3326  helper,
3327  absl::StrCat("routing_full_path_lns_", local_params.name())),
3328  local_params, helper, &shared));
3329  }
3330  }
3331 
3332  // Add a synchronization point for the gap integral that is executed last.
3333  // This way, after each batch, the proper deterministic time is updated and
3334  // then the function to integrate take the value of the new gap.
3335  if (model_proto.has_objective() && !model_proto.objective().vars().empty()) {
3336  subsolvers.push_back(std::make_unique<SynchronizationPoint>(
3337  "update_gap_integral",
3338  [&shared]() { shared.response->UpdateGapIntegral(); }));
3339  }
3340 
3341  // Log the name of all our SubSolvers.
3342  auto* logger = global_model->GetOrCreate<SolverLogger>();
3343  if (logger->LoggingIsEnabled()) {
3344  // Collect subsolver names per type (full, lns, 1st solution).
3345  std::vector<std::string> full_problem_solver_names;
3346  std::vector<std::string> incomplete_solver_names;
3347  std::vector<std::string> first_solution_solver_names;
3348  std::vector<std::string> helper_solver_names;
3349  for (int i = 0; i < subsolvers.size(); ++i) {
3350  const auto& subsolver = subsolvers[i];
3351  switch (subsolver->type()) {
3353  full_problem_solver_names.push_back(subsolver->name());
3354  break;
3355  case SubSolver::INCOMPLETE:
3356  incomplete_solver_names.push_back(subsolver->name());
3357  break;
3359  first_solution_solver_names.push_back(subsolver->name());
3360  break;
3361  case SubSolver::HELPER:
3362  helper_solver_names.push_back(subsolver->name());
3363  break;
3364  }
3365  }
3366  SOLVER_LOG(logger, "");
3367 
3368  if (params.interleave_search()) {
3369  SOLVER_LOG(logger,
3370  absl::StrFormat("Starting deterministic search at %.2fs with "
3371  "%i workers and batch size of %d.",
3372  shared.wall_timer->Get(), params.num_workers(),
3373  params.interleave_batch_size()));
3374  } else {
3375  SOLVER_LOG(logger, absl::StrFormat(
3376  "Starting search at %.2fs with %i workers.",
3377  shared.wall_timer->Get(), params.num_workers()));
3378  }
3379 
3380  auto display_subsolver_list = [logger](
3381  const std::vector<std::string>& names,
3382  const absl::string_view type_name) {
3383  if (!names.empty()) {
3384  SOLVER_LOG(logger, names.size(), " ",
3385  absl::StrCat(type_name, names.size() == 1 ? "" : "s"), ": [",
3386  absl::StrJoin(names.begin(), names.end(), ", "), "]");
3387  }
3388  };
3389 
3390  display_subsolver_list(full_problem_solver_names, "full problem subsolver");
3391  display_subsolver_list(first_solution_solver_names,
3392  "first solution subsolver");
3393  display_subsolver_list(incomplete_solver_names, "incomplete subsolver");
3394  display_subsolver_list(helper_solver_names, "helper subsolver");
3395  }
3396 
3397  // Launch the main search loop.
3398  if (params.interleave_search()) {
3399  int batch_size = params.interleave_batch_size();
3400  if (batch_size == 0) {
3401  batch_size = params.num_workers() == 1 ? 1 : params.num_workers() * 3;
3402  SOLVER_LOG(
3403  logger,
3404  "Setting number of tasks in each batch of interleaved search to ",
3405  batch_size);
3406  }
3407  DeterministicLoop(subsolvers, params.num_workers(), batch_size);
3408  } else {
3409  NonDeterministicLoop(subsolvers, params.num_workers());
3410  }
3411 
3412  // Log statistics.
3413  if (logger->LoggingIsEnabled()) {
3414  if (params.log_subsolver_statistics()) {
3415  bool first = true;
3416  for (const auto& subsolver : subsolvers) {
3417  const std::string stats = subsolver->StatisticsString();
3418  if (stats.empty()) continue;
3419  if (first) {
3420  SOLVER_LOG(logger, "");
3421  SOLVER_LOG(logger, "Sub-solver search statistics:");
3422  first = false;
3423  }
3424  SOLVER_LOG(logger,
3425  absl::StrCat(" '", subsolver->name(), "':\n", stats));
3426  }
3427  }
3428 
3429  shared.response->DisplayImprovementStatistics();
3430 
3431  if (shared.bounds) {
3432  shared.bounds->LogStatistics(logger);
3433  }
3434 
3435  if (shared.clauses) {
3436  shared.clauses->LogStatistics(logger);
3437  }
3438  }
3439 
3440  // We delete manually as windows release vectors in the opposite order.
3441  for (int i = 0; i < subsolvers.size(); ++i) {
3442  subsolvers[i].reset();
3443  }
3444 }
3445 
3446 #endif // __PORTABLE_PLATFORM__
3447 
3448 // If the option use_sat_inprocessing is true, then before postsolving a
3449 // solution, we need to make sure we add any new clause required for postsolving
3450 // to the mapping_model.
3451 void AddPostsolveClauses(const std::vector<int>& postsolve_mapping,
3452  Model* model, CpModelProto* mapping_proto) {
3453  auto* mapping = model->GetOrCreate<CpModelMapping>();
3454  auto* postsolve = model->GetOrCreate<PostsolveClauses>();
3455  for (const auto& clause : postsolve->clauses) {
3456  auto* ct = mapping_proto->add_constraints()->mutable_bool_or();
3457  for (const Literal l : clause) {
3458  int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
3459  CHECK_NE(var, -1);
3460  var = postsolve_mapping[var];
3461  ct->add_literals(l.IsPositive() ? var : NegatedRef(var));
3462  }
3463  }
3464  postsolve->clauses.clear();
3465 }
3466 
3467 void TestSolutionHintForFeasibility(const CpModelProto& model_proto,
3468  SolverLogger* logger,
3469  SharedResponseManager* manager = nullptr) {
3470  if (!model_proto.has_solution_hint()) return;
3471 
3472  // TODO(user): If the hint specifies all non-fixed variables we could also
3473  // do the check.
3474  if (model_proto.solution_hint().vars_size() != model_proto.variables_size()) {
3475  return;
3476  }
3477 
3478  std::vector<int64_t> solution(model_proto.variables_size(), 0);
3479  for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
3480  const int ref = model_proto.solution_hint().vars(i);
3481  const int64_t value = model_proto.solution_hint().values(i);
3482  solution[PositiveRef(ref)] = RefIsPositive(ref) ? value : -value;
3483  }
3484  if (SolutionIsFeasible(model_proto, solution)) {
3485  if (manager != nullptr) {
3486  // Add it to the pool right away! Note that we already have a log in this
3487  // case, so we don't log anything more.
3488  manager->NewSolution(solution, "complete_hint", nullptr);
3489  } else {
3490  SOLVER_LOG(logger, "The solution hint is complete and is feasible.");
3491  }
3492  } else {
3493  // TODO(user): Change the code to make the solution checker more
3494  // informative by returning a message instead of just VLOGing it.
3495  SOLVER_LOG(logger,
3496  "The solution hint is complete, but it is infeasible! we "
3497  "will try to repair it.");
3498  }
3499 }
3500 
3501 } // namespace
3502 
3503 CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
3504  auto* wall_timer = model->GetOrCreate<WallTimer>();
3505  auto* user_timer = model->GetOrCreate<UserTimer>();
3506  wall_timer->Start();
3507  user_timer->Start();
3508 
3509 #if !defined(__PORTABLE_PLATFORM__)
3510 #endif // __PORTABLE_PLATFORM__
3511 
3512 #if !defined(__PORTABLE_PLATFORM__)
3513  // Dump initial model?
3514  if (absl::GetFlag(FLAGS_cp_model_dump_models)) {
3515  DumpModelProto(model_proto, "model");
3516  }
3517 #endif // __PORTABLE_PLATFORM__
3518 
3519 #if !defined(__PORTABLE_PLATFORM__)
3520  // Override parameters?
3521  if (!absl::GetFlag(FLAGS_cp_model_params).empty()) {
3522  SatParameters params = *model->GetOrCreate<SatParameters>();
3523  SatParameters flag_params;
3524  CHECK(google::protobuf::TextFormat::ParseFromString(
3525  absl::GetFlag(FLAGS_cp_model_params), &flag_params));
3526  params.MergeFrom(flag_params);
3527  *(model->GetOrCreate<SatParameters>()) = params;
3528  }
3529 #endif // __PORTABLE_PLATFORM__
3530 
3531  // Enable the logging component.
3532  const SatParameters& params = *model->GetOrCreate<SatParameters>();
3533  SolverLogger* logger = model->GetOrCreate<SolverLogger>();
3534  logger->EnableLogging(params.log_search_progress() || VLOG_IS_ON(1));
3535  logger->SetLogToStdOut(params.log_to_stdout());
3536  std::string log_string;
3537  if (params.log_to_response()) {
3538  logger->AddInfoLoggingCallback([&log_string](const std::string& message) {
3539  absl::StrAppend(&log_string, message, "\n");
3540  });
3541  }
3542 
3543  auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
3544  shared_response_manager->set_dump_prefix(
3545  absl::GetFlag(FLAGS_cp_model_dump_prefix));
3546 
3547 #if !defined(__PORTABLE_PLATFORM__)
3548  // Note that the postprocessors are executed in reverse order, so this
3549  // will always dump the response just before it is returned since it is
3550  // the first one we register.
3551  if (absl::GetFlag(FLAGS_cp_model_dump_response)) {
3552  shared_response_manager->AddFinalResponsePostprocessor(
3553  [](CpSolverResponse* response) {
3554  const std::string file = absl::StrCat(
3555  absl::GetFlag(FLAGS_cp_model_dump_prefix), "response.pb.txt");
3556  LOG(INFO) << "Dumping response proto to '" << file << "'.";
3558  });
3559  }
3560 #endif // __PORTABLE_PLATFORM__
3561 
3562  // Always display the final response stats if requested.
3563  // This also copy the logs to the response if requested.
3564  shared_response_manager->AddFinalResponsePostprocessor(
3565  [logger, &model_proto, &log_string](CpSolverResponse* response) {
3566  SOLVER_LOG(logger, "");
3568  *response,
3569  model_proto.has_objective() ||
3570  model_proto.has_floating_point_objective()));
3571  if (!log_string.empty()) {
3572  response->set_solve_log(log_string);
3573  }
3574  });
3575 
3576  // Always add the timing information to a response. Note that it is important
3577  // to add this after the log/dump postprocessor since we execute them in
3578  // reverse order.
3579  auto* shared_time_limit = model->GetOrCreate<ModelSharedTimeLimit>();
3580  shared_response_manager->AddResponsePostprocessor(
3581  [&wall_timer, &user_timer,
3582  &shared_time_limit](CpSolverResponse* response) {
3583  response->set_wall_time(wall_timer->Get());
3584  response->set_user_time(user_timer->Get());
3585  response->set_deterministic_time(
3586  shared_time_limit->GetElapsedDeterministicTime());
3587  });
3588 
3589  // Validate parameters.
3590  //
3591  // Note that the few parameters we use before that are Booleans and thus
3592  // "safe". We need to delay the validation to return a proper response.
3593  {
3594  const std::string error = ValidateParameters(params);
3595  if (!error.empty()) {
3596  SOLVER_LOG(logger, "Invalid parameters: ", error);
3597 
3598  // TODO(user): We currently reuse the MODEL_INVALID status even though it
3599  // is not the best name for this. Maybe we can add a PARAMETERS_INVALID
3600  // when it become needed. Or rename to INVALID_INPUT ?
3601  CpSolverResponse status_response;
3602  status_response.set_status(CpSolverStatus::MODEL_INVALID);
3603  status_response.set_solution_info(error);
3604  FillSolveStatsInResponse(model, &status_response);
3605  shared_response_manager->AppendResponseToBeMerged(status_response);
3606  return shared_response_manager->GetResponse();
3607  }
3608  }
3609 
3610  // Initialize the time limit from the parameters.
3611  model->GetOrCreate<TimeLimit>()->ResetLimitFromParameters(params);
3612 
3613 #if !defined(__PORTABLE_PLATFORM__)
3614  // Register SIGINT handler if requested by the parameters.
3615  if (params.catch_sigint_signal()) {
3616  model->GetOrCreate<SigintHandler>()->Register(
3617  [&shared_time_limit]() { shared_time_limit->Stop(); });
3618  }
3619 #endif // __PORTABLE_PLATFORM__
3620 
3621  SOLVER_LOG(logger, "");
3622  SOLVER_LOG(logger, "Starting ", CpSatSolverVersion());
3623  SOLVER_LOG(logger, "Parameters: ", params.ShortDebugString());
3624 
3625  // Update params.num_workers() if the old field was used.
3626  if (params.num_workers() == 0) {
3627  model->GetOrCreate<SatParameters>()->set_num_workers(
3628  params.num_search_workers());
3629  }
3630 
3631  // Initialize the number of workers if set to 0.
3632  if (params.num_workers() == 0) {
3633 #if !defined(__PORTABLE_PLATFORM__)
3634  // Sometimes, hardware_concurrency will return 0. So always default to 1.
3635  const int num_cores =
3636  params.enumerate_all_solutions() || !model_proto.assumptions().empty()
3637  ? 1
3638  : std::max<int>(std::thread::hardware_concurrency(), 1);
3639 #else
3640  const int num_cores = 1;
3641 #endif
3642  SOLVER_LOG(logger, "Setting number of workers to ", num_cores);
3643  model->GetOrCreate<SatParameters>()->set_num_workers(num_cores);
3644  }
3645 
3646  if (logger->LoggingIsEnabled() && params.use_absl_random()) {
3647  model->GetOrCreate<ModelRandomGenerator>()->LogSalt();
3648  }
3649 
3650  // Validate model_proto.
3651  // TODO(user): provide an option to skip this step for speed?
3652  {
3653  const std::string error = ValidateInputCpModel(params, model_proto);
3654  if (!error.empty()) {
3655  SOLVER_LOG(logger, "Invalid model: ", error);
3656  CpSolverResponse status_response;
3657  status_response.set_status(CpSolverStatus::MODEL_INVALID);
3658  status_response.set_solution_info(error);
3659  FillSolveStatsInResponse(model, &status_response);
3660  shared_response_manager->AppendResponseToBeMerged(status_response);
3661  return shared_response_manager->GetResponse();
3662  }
3663  }
3664 
3665  SOLVER_LOG(logger, "");
3666  SOLVER_LOG(logger, "Initial ", CpModelStats(model_proto));
3667 
3668  // Special case for pure-sat problem.
3669  // TODO(user): improve the normal presolver to do the same thing.
3670  // TODO(user): Support solution hint, but then the first TODO will make it
3671  // automatic.
3672  if (!params.use_sat_inprocessing() && !model_proto.has_objective() &&
3673  !model_proto.has_floating_point_objective() &&
3674  !model_proto.has_solution_hint() && !params.enumerate_all_solutions() &&
3675  !params.use_lns_only() && params.num_workers() <= 1 &&
3676  model_proto.assumptions().empty()) {
3677  bool is_pure_sat = true;
3678  for (const IntegerVariableProto& var : model_proto.variables()) {
3679  if (var.domain_size() != 2 || var.domain(0) < 0 || var.domain(1) > 1) {
3680  is_pure_sat = false;
3681  break;
3682  }
3683  }
3684  if (is_pure_sat) {
3685  for (const ConstraintProto& ct : model_proto.constraints()) {
3686  if (ct.constraint_case() != ConstraintProto::ConstraintCase::kBoolOr &&
3687  ct.constraint_case() != ConstraintProto::ConstraintCase::kBoolAnd) {
3688  is_pure_sat = false;
3689  break;
3690  }
3691  }
3692  }
3693  if (is_pure_sat) {
3694  // TODO(user): All this duplication will go away when we are fast enough
3695  // on pure-sat model with the CpModel presolve...
3696  CpSolverResponse final_response =
3697  SolvePureSatModel(model_proto, wall_timer, model, logger);
3698  if (params.fill_tightened_domains_in_response()) {
3699  *final_response.mutable_tightened_variables() = model_proto.variables();
3700  }
3701  shared_response_manager->AppendResponseToBeMerged(final_response);
3702  return shared_response_manager->GetResponse();
3703  }
3704  }
3705 
3706  // Presolve and expansions.
3707  SOLVER_LOG(logger, "");
3708  SOLVER_LOG(logger,
3709  absl::StrFormat("Starting presolve at %.2fs", wall_timer->Get()));
3710  CpModelProto new_cp_model_proto;
3711  CpModelProto mapping_proto;
3712  auto context = std::make_unique<PresolveContext>(model, &new_cp_model_proto,
3713  &mapping_proto);
3714 
3716  VLOG(1) << "Model found infeasible during copy";
3717  // TODO(user): At this point, the model is trivial, but we could exit
3718  // early.
3719  }
3720 
3721  if (absl::GetFlag(FLAGS_cp_model_ignore_objective) &&
3722  (context->working_model->has_objective() ||
3723  context->working_model->has_floating_point_objective())) {
3724  SOLVER_LOG(logger, "Ignoring objective");
3725  context->working_model->clear_objective();
3726  context->working_model->clear_floating_point_objective();
3727  }
3728 
3729  // Checks for hints early in case they are forced to be hard constraints.
3730  if (params.fix_variables_to_their_hinted_value() &&
3731  model_proto.has_solution_hint()) {
3732  SOLVER_LOG(logger, "Fixing ", model_proto.solution_hint().vars().size(),
3733  " variables to their value in the solution hints.");
3734  for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
3735  const int var = model_proto.solution_hint().vars(i);
3736  const int64_t value = model_proto.solution_hint().values(i);
3737  if (!context->IntersectDomainWith(var, Domain(value))) {
3738  const IntegerVariableProto& var_proto =
3739  context->working_model->variables(var);
3740  const std::string var_name = var_proto.name().empty()
3741  ? absl::StrCat("var(", var, ")")
3742  : var_proto.name();
3743 
3744  const Domain var_domain = ReadDomainFromProto(var_proto);
3745  SOLVER_LOG(logger, "Hint found infeasible when assigning variable '",
3746  var_name, "' with domain", var_domain.ToString(),
3747  " the value ", value);
3748  break;
3749  }
3750  }
3751  }
3752 
3753  // If the hint is complete, we can use the solution checker to do more
3754  // validation. Note that after the model has been validated, we are sure there
3755  // are do duplicate variables in the solution hint, so we can just check the
3756  // size.
3757  if (!context->ModelIsUnsat()) {
3758  TestSolutionHintForFeasibility(model_proto, logger);
3759  }
3760 
3761  // If the objective was a floating point one, do some postprocessing on the
3762  // final response.
3763  if (model_proto.has_floating_point_objective()) {
3764  shared_response_manager->AddFinalResponsePostprocessor(
3765  [&params, &model_proto, &mapping_proto,
3766  &logger](CpSolverResponse* response) {
3767  if (response->solution().empty()) return;
3768 
3769  // Compute the true objective of the best returned solution.
3770  const auto& float_obj = model_proto.floating_point_objective();
3771  double value = float_obj.offset();
3772  const int num_terms = float_obj.vars().size();
3773  for (int i = 0; i < num_terms; ++i) {
3774  value += float_obj.coeffs(i) *
3775  static_cast<double>(response->solution(float_obj.vars(i)));
3776  }
3777  response->set_objective_value(value);
3778 
3779  // Also copy the scaled objective which must be in the mapping model.
3780  // This can be useful for some client, like if they want to do
3781  // multi-objective optimization in stages.
3782  if (!mapping_proto.has_objective()) return;
3783  const CpObjectiveProto& integer_obj = mapping_proto.objective();
3784  *response->mutable_integer_objective() = integer_obj;
3785 
3786  // If requested, compute a correct lb from the one on the integer
3787  // objective. We only do that if some error were introduced by the
3788  // scaling algorithm.
3789  if (params.mip_compute_true_objective_bound() &&
3790  !integer_obj.scaling_was_exact()) {
3791  const int64_t integer_lb = response->inner_objective_lower_bound();
3792  const double lb = ComputeTrueObjectiveLowerBound(
3793  model_proto, integer_obj, integer_lb);
3794  SOLVER_LOG(logger, "[Scaling] scaled_objective_bound: ",
3795  response->best_objective_bound(),
3796  " corrected_bound: ", lb,
3797  " delta: ", response->best_objective_bound() - lb);
3798 
3799  // To avoid small errors that can be confusing, we take the
3800  // min/max with the objective value.
3801  if (float_obj.maximize()) {
3802  response->set_best_objective_bound(
3803  std::max(lb, response->objective_value()));
3804  } else {
3805  response->set_best_objective_bound(
3806  std::min(lb, response->objective_value()));
3807  }
3808  }
3809 
3810  // Check the absolute gap, and display warning if needed.
3811  // TODO(user): Change status to IMPRECISE?
3812  if (response->status() == CpSolverStatus::OPTIMAL) {
3813  const double gap = std::abs(response->objective_value() -
3814  response->best_objective_bound());
3815  if (gap > params.absolute_gap_limit()) {
3816  SOLVER_LOG(logger,
3817  "[Scaling] Warning: OPTIMAL was reported, yet the "
3818  "objective gap (",
3819  gap, ") is greater than requested absolute limit (",
3820  params.absolute_gap_limit(), ").");
3821  }
3822  }
3823  });
3824  }
3825 
3826  if (!model_proto.assumptions().empty() &&
3827  (params.num_workers() > 1 || model_proto.has_objective() ||
3828  model_proto.has_floating_point_objective() ||
3829  params.enumerate_all_solutions())) {
3830  SOLVER_LOG(
3831  logger,
3832  "Warning: solving with assumptions was requested in a non-fully "
3833  "supported setting.\nWe will assumes these assumptions true while "
3834  "solving, but if the model is infeasible, you will not get a useful "
3835  "'sufficient_assumptions_for_infeasibility' field in the response, it "
3836  "will include all assumptions.");
3837 
3838  // For the case where the assumptions are currently not supported, we just
3839  // assume they are fixed, and will always report all of them in the UNSAT
3840  // core if the problem turn out to be UNSAT.
3841  //
3842  // If the mode is not degraded, we will hopefully report a small subset
3843  // in case there is no feasible solution under these assumptions.
3844  shared_response_manager->AddFinalResponsePostprocessor(
3845  [&model_proto](CpSolverResponse* response) {
3846  if (response->status() != CpSolverStatus::INFEASIBLE) return;
3847 
3848  // For now, just pass in all assumptions.
3849  *response->mutable_sufficient_assumptions_for_infeasibility() =
3850  model_proto.assumptions();
3851  });
3852 
3853  // Clear them from the new proto.
3854  new_cp_model_proto.clear_assumptions();
3855 
3856  context->InitializeNewDomains();
3857  for (const int ref : model_proto.assumptions()) {
3858  if (!context->SetLiteralToTrue(ref)) {
3859  CpSolverResponse status_response;
3860  status_response.set_status(CpSolverStatus::INFEASIBLE);
3861  status_response.add_sufficient_assumptions_for_infeasibility(ref);
3862  FillSolveStatsInResponse(model, &status_response);
3863  shared_response_manager->AppendResponseToBeMerged(status_response);
3864  return shared_response_manager->GetResponse();
3865  }
3866  }
3867  }
3868 
3869  // Do the actual presolve.
3870  std::vector<int> postsolve_mapping;
3871  const CpSolverStatus presolve_status =
3872  PresolveCpModel(context.get(), &postsolve_mapping);
3873  if (presolve_status != CpSolverStatus::UNKNOWN) {
3874  SOLVER_LOG(logger, "Problem closed by presolve.");
3875  CpSolverResponse status_response;
3876  status_response.set_status(presolve_status);
3877  FillSolveStatsInResponse(model, &status_response);
3878  shared_response_manager->AppendResponseToBeMerged(status_response);
3879  return shared_response_manager->GetResponse();
3880  }
3881 
3882  SOLVER_LOG(logger, "");
3883  SOLVER_LOG(logger, "Presolved ", CpModelStats(new_cp_model_proto));
3884 
3885  if (params.cp_model_presolve()) {
3886  shared_response_manager->AddSolutionPostprocessor(
3887  [&model_proto, &params, &mapping_proto, &model,
3888  &postsolve_mapping](std::vector<int64_t>* solution) {
3889  AddPostsolveClauses(postsolve_mapping, model, &mapping_proto);
3890  PostsolveResponseWrapper(params, model_proto.variables_size(),
3891  mapping_proto, postsolve_mapping, solution);
3892  });
3893  shared_response_manager->AddResponsePostprocessor(
3894  [&model_proto, &params, &mapping_proto,
3895  &postsolve_mapping](CpSolverResponse* response) {
3896  // Map back the sufficient assumptions for infeasibility.
3897  for (int& ref :
3898  *(response
3899  ->mutable_sufficient_assumptions_for_infeasibility())) {
3900  ref = RefIsPositive(ref)
3901  ? postsolve_mapping[ref]
3902  : NegatedRef(postsolve_mapping[PositiveRef(ref)]);
3903  }
3904  if (!response->solution().empty()) {
3905  CHECK(SolutionIsFeasible(
3906  model_proto,
3907  std::vector<int64_t>(response->solution().begin(),
3908  response->solution().end()),
3909  &mapping_proto, &postsolve_mapping))
3910  << "postsolved solution";
3911  }
3912  if (params.fill_tightened_domains_in_response()) {
3913  // TODO(user): for now, we just use the domain infered during
3914  // presolve.
3915  if (mapping_proto.variables().size() >=
3916  model_proto.variables().size()) {
3917  for (int i = 0; i < model_proto.variables().size(); ++i) {
3918  *response->add_tightened_variables() =
3919  mapping_proto.variables(i);
3920  }
3921  }
3922  }
3923  });
3924  } else {
3925  shared_response_manager->AddFinalResponsePostprocessor(
3926  [&model_proto](CpSolverResponse* response) {
3927  if (!response->solution().empty()) {
3928  CHECK(SolutionIsFeasible(
3929  model_proto, std::vector<int64_t>(response->solution().begin(),
3930  response->solution().end())));
3931  }
3932  });
3933  shared_response_manager->AddResponsePostprocessor(
3934  [&model_proto, &params](CpSolverResponse* response) {
3935  // Truncate the solution in case model expansion added more variables.
3936  const int initial_size = model_proto.variables_size();
3937  if (response->solution_size() > 0) {
3938  response->mutable_solution()->Truncate(initial_size);
3939  if (DEBUG_MODE ||
3940  absl::GetFlag(FLAGS_cp_model_check_intermediate_solutions)) {
3941  CHECK(SolutionIsFeasible(
3942  model_proto,
3943  std::vector<int64_t>(response->solution().begin(),
3944  response->solution().end())));
3945  }
3946  }
3947  if (params.fill_tightened_domains_in_response()) {
3948  *response->mutable_tightened_variables() = model_proto.variables();
3949  }
3950  });
3951  }
3952 
3953  // Delete the context.
3954  context.reset(nullptr);
3955 
3956  const auto& observers = model->GetOrCreate<SolutionObservers>()->observers;
3957  if (!observers.empty()) {
3958  shared_response_manager->AddSolutionCallback(
3959  [&observers](const CpSolverResponse& response) {
3960  for (const auto& observer : observers) {
3961  observer(response);
3962  }
3963  });
3964  }
3965 
3966  // Make sure everything stops when we have a first solution if requested.
3967  if (params.stop_after_first_solution()) {
3968  shared_response_manager->AddSolutionCallback(
3969  [shared_time_limit](const CpSolverResponse&) {
3970  shared_time_limit->Stop();
3971  });
3972  }
3973 
3974 #if !defined(__PORTABLE_PLATFORM__)
3975  if (absl::GetFlag(FLAGS_cp_model_dump_models)) {
3976  DumpModelProto(new_cp_model_proto, "presolved_model");
3977  DumpModelProto(mapping_proto, "mapping_model");
3978 
3979  // If the model is convertible to a MIP, we dump it too.
3980  //
3981  // TODO(user): We could try to dump our linear relaxation too.
3982  MPModelProto mip_model;
3983  if (ConvertCpModelProtoToMPModelProto(new_cp_model_proto, &mip_model)) {
3984  DumpModelProto(mip_model, "presolved_mp_model");
3985  }
3986  }
3987 #endif // __PORTABLE_PLATFORM__
3988 
3989  if (params.stop_after_presolve() || shared_time_limit->LimitReached()) {
3990  int64_t num_terms = 0;
3991  for (const ConstraintProto& ct : new_cp_model_proto.constraints()) {
3992  num_terms += UsedVariables(ct).size();
3993  }
3994  SOLVER_LOG(
3995  logger, "Stopped after presolve.",
3996  "\nPresolvedNumVariables: ", new_cp_model_proto.variables().size(),
3997  "\nPresolvedNumConstraints: ", new_cp_model_proto.constraints().size(),
3998  "\nPresolvedNumTerms: ", num_terms);
3999 
4000  CpSolverResponse status_response;
4001  FillSolveStatsInResponse(model, &status_response);
4002  shared_response_manager->AppendResponseToBeMerged(status_response);
4003  return shared_response_manager->GetResponse();
4004  }
4005 
4006  SOLVER_LOG(logger, "");
4007  SOLVER_LOG(logger, "Preloading model.");
4008 
4009  // If specified, we load the initial objective domain right away in the
4010  // response manager. Note that the presolve will always fill it with the
4011  // trivial min/max value if the user left it empty. This avoids to display
4012  // [-infinity, infinity] for the initial objective search space.
4013  if (new_cp_model_proto.has_objective()) {
4014  shared_response_manager->InitializeObjective(new_cp_model_proto);
4015  shared_response_manager->SetGapLimitsFromParameters(params);
4016  }
4017 
4018  // Start counting the primal integral from the current determistic time and
4019  // initial objective domain gap that we just filled.
4020  shared_response_manager->UpdateGapIntegral();
4021 
4022  // Re-test a complete solution hint to see if it survived the presolve.
4023  // If it is feasible, we load it right away.
4024  //
4025  // Tricky: when we enumerate all solutions, we cannot properly exclude the
4026  // current solution if we didn't find it via full propagation, so we don't
4027  // load it in this case.
4028  //
4029  // TODO(user): Even for an optimization, if we load the solution right away,
4030  // we might not have the same behavior as the initial search that follow the
4031  // hint will be infeasible, so the activities of the variables will be
4032  // different.
4033  if (!params.enumerate_all_solutions()) {
4034  TestSolutionHintForFeasibility(new_cp_model_proto, logger,
4035  shared_response_manager);
4036  } else {
4037  TestSolutionHintForFeasibility(new_cp_model_proto, logger, nullptr);
4038  }
4039 
4040  if (params.symmetry_level() > 1) {
4041  DetectAndAddSymmetryToProto(params, &new_cp_model_proto, logger);
4042  }
4043 
4044  LoadDebugSolution(new_cp_model_proto, model);
4045 
4046 #if defined(__PORTABLE_PLATFORM__)
4047  if (/* DISABLES CODE */ (false)) {
4048  // We ignore the multithreading parameter in this case.
4049 #else // __PORTABLE_PLATFORM__
4050  if (params.num_workers() > 1 || params.interleave_search() ||
4051  !params.subsolvers().empty()) {
4052  SolveCpModelParallel(new_cp_model_proto, model);
4053 #endif // __PORTABLE_PLATFORM__
4054  } else if (!model->GetOrCreate<TimeLimit>()->LimitReached()) {
4055  SOLVER_LOG(logger, "");
4056  SOLVER_LOG(logger, absl::StrFormat("Starting to load the model at %.2fs",
4057  wall_timer->Get()));
4058  shared_response_manager->SetUpdateGapIntegralOnEachChange(true);
4059 
4060  // We use a local_model to share statistic report mechanism with the
4061  // parallel case. When this model will be destroyed, we will collect some
4062  // stats that are used to debug/improve internal algorithm.
4063  Model local_model;
4064  local_model.Register<TimeLimit>(model->GetOrCreate<TimeLimit>());
4065  local_model.Register<SatParameters>(model->GetOrCreate<SatParameters>());
4066  local_model.Register<SharedStatistics>(
4067  model->GetOrCreate<SharedStatistics>());
4068  local_model.Register<SharedResponseManager>(shared_response_manager);
4069 
4070  LoadCpModel(new_cp_model_proto, &local_model);
4071 
4072  SOLVER_LOG(logger, "");
4073  SOLVER_LOG(logger, absl::StrFormat("Starting sequential search at %.2fs",
4074  wall_timer->Get()));
4075  if (params.repair_hint()) {
4076  MinimizeL1DistanceWithHint(new_cp_model_proto, &local_model);
4077  } else {
4078  QuickSolveWithHint(new_cp_model_proto, &local_model);
4079  }
4080  SolveLoadedCpModel(new_cp_model_proto, &local_model);
4081  // Export statistics.
4082  CpSolverResponse status_response;
4083  FillSolveStatsInResponse(&local_model, &status_response);
4084  shared_response_manager->AppendResponseToBeMerged(status_response);
4085 
4086  // Sequential logging of LP statistics.
4087  if (logger->LoggingIsEnabled()) {
4088  const auto& lps =
4089  *local_model.GetOrCreate<LinearProgrammingConstraintCollection>();
4090  if (!lps.empty()) {
4091  SOLVER_LOG(logger, "");
4092  for (const auto* lp : lps) {
4093  SOLVER_LOG(logger, lp->Statistics());
4094  }
4095  }
4096  }
4097  }
4098 
4099  // Extra logging if needed.
4100  if (logger->LoggingIsEnabled()) {
4101  model->GetOrCreate<SharedStatistics>()->Log(logger);
4102  }
4103  return shared_response_manager->GetResponse();
4104 }
4105 
4106 CpSolverResponse Solve(const CpModelProto& model_proto) {
4107  Model model;
4108  return SolveCpModel(model_proto, &model);
4109 }
4110 
4111 CpSolverResponse SolveWithParameters(const CpModelProto& model_proto,
4112  const SatParameters& params) {
4113  Model model;
4114  model.Add(NewSatParameters(params));
4115  return SolveCpModel(model_proto, &model);
4116 }
4117 
4118 #if !defined(__PORTABLE_PLATFORM__)
4119 CpSolverResponse SolveWithParameters(const CpModelProto& model_proto,
4120  const std::string& params) {
4121  Model model;
4122  model.Add(NewSatParameters(params));
4123  return SolveCpModel(model_proto, &model);
4124 }
4125 #endif // !__PORTABLE_PLATFORM__
4126 
4127 } // namespace sat
4128 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
bool AddEdge(int node1, int node2)
Definition: base/file.h:33
void Start()
Definition: timer.h:31
double Get() const
Definition: timer.h:45
We call domain any subset of Int64 = [kint64min, kint64max].
std::string ToString() const
Returns a compact string of a vector of intervals like "[1,4][6][10,20]".
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
Literal(int signed_value)
Definition: sat_base.h:74
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
Definition: sat/model.h:175
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:110
void set_dump_prefix(const std::string &dump_prefix)
int64_t b
SatParameters parameters
CpModelProto proto
SharedBoundsManager * bounds
SharedClausesManager * clauses
SharedRelaxationSolutionRepository * relaxation_solutions
SharedLPSolutionRepository * lp_solutions
CpModelProto const * model_proto
SharedIncompleteSolutionManager * incomplete_solutions
Model * global_model
ABSL_FLAG(std::string, cp_model_dump_prefix, "/tmp/", "Prefix filename for all dumped files")
SharedResponseManager * response
WallTimer * wall_timer
ModelSharedTimeLimit * time_limit
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
GurobiMPCallbackContext * context
const bool DEBUG_MODE
Definition: macros.h:24
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:125
absl::Status GetTextProto(const absl::string_view &filename, google::protobuf::Message *proto, int flags)
Definition: base/file.cc:289
Options Defaults()
Definition: base/file.h:123
absl::Status Open(const absl::string_view &filename, const absl::string_view &mode, File **f, int flags)
Definition: base/file.cc:143
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &observer)
Creates a solution observer with the model with model.Add(NewFeasibleSolutionObserver([](response){....
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition: integer.h:1781
void DetectAndAddSymmetryToProto(const SatParameters &params, CpModelProto *proto, SolverLogger *logger)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
void RestrictObjectiveDomainWithBinarySearch(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
Creates parameters for the solver, which you can add to the model with.
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
void LoadVariables(const CpModelProto &model_proto, bool view_all_booleans_as_integers, Model *m)
std::string CpSolverResponseStats(const CpSolverResponse &response, bool has_objective)
Returns a string with some statistics on the solver response.
bool LoadConstraint(const ConstraintProto &ct, Model *m)
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool ConvertCpModelProtoToMPModelProto(const CpModelProto &input, MPModelProto *output)
bool RefIsPositive(int ref)
std::string ValidateParameters(const SatParameters &params)
void ExtractElementEncoding(const CpModelProto &model_proto, Model *m)
CpSolverResponse SolveWithParameters(const CpModelProto &model_proto, const std::string &params)
Solves the given CpModelProto with the given sat parameters as string in JSon format,...
std::string CpSatSolverVersion()
Returns a string that describes the version of the solver.
std::string ValidateInputCpModel(const SatParameters &params, const CpModelProto &model)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> ConstructUserSearchStrategy(const CpModelProto &cp_model_proto, Model *model)
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition: integer.h:1795
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
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)
void LoadBooleanSymmetries(const CpModelProto &model_proto, Model *m)
void DeterministicLoop(const std::vector< std::unique_ptr< SubSolver >> &subsolvers, int num_threads, int batch_size)
Definition: subsolver.cc:94
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
std::function< BooleanOrIntegerLiteral()> FollowHint(const std::vector< BooleanOrIntegerVariable > &vars, const std::vector< IntegerValue > &values, Model *model)
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
std::function< BooleanOrIntegerLiteral()> ConstructFixedSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, IntegerVariable objective_var, Model *model)
void ConfigureSearchHeuristics(Model *model)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:149
void NonDeterministicLoop(const std::vector< std::unique_ptr< SubSolver >> &subsolvers, int num_threads)
Definition: subsolver.cc:133
void SplitAndLoadIntermediateConstraints(bool lb_required, bool ub_required, std::vector< IntegerVariable > *vars, std::vector< int64_t > *coeffs, Model *m)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
std::function< SatParameters(Model *)> NewSatParameters(const sat::SatParameters &parameters)
std::function< void(Model *)> WeightedSumLowerOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t upper_bound)
Definition: integer_expr.h:369
std::string FormatCounter(int64_t num)
Definition: sat/util.cc:48
std::string CpModelStats(const CpModelProto &model_proto)
Returns a string with some statistics on the given CpModelProto.
std::vector< SatParameters > GetDiverseSetOfParameters(const SatParameters &base_params, const CpModelProto &cp_model)
std::function< IntegerVariable(Model *)> NewIntegerVariable(int64_t lb, int64_t ub)
Definition: integer.h:1734
void DetectOptionalVariables(const CpModelProto &model_proto, Model *m)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
std::function< void(Model *)> ExcludeCurrentSolutionWithoutIgnoredVariableAndBacktrack()
Definition: integer.cc:2336
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, absl::Span< const int64_t > solution)
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
void FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
constexpr uint64_t kDefaultFingerprintSeed
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
SatSolver::Status SolveWithPresolve(std::unique_ptr< SatSolver > *solver, TimeLimit *time_limit, std::vector< bool > *solution, DratProofHandler *drat_proof_handler, SolverLogger *logger)
std::vector< SatParameters > GetFirstSolutionParams(const SatParameters &base_params, const CpModelProto &cp_model, int num_params_to_generate)
void AddFullEncodingFromSearchBranching(const CpModelProto &model_proto, Model *m)
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
void ExtractEncoding(const CpModelProto &model_proto, Model *m)
void PropagateEncodingFromEquivalenceRelations(const CpModelProto &model_proto, Model *m)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition: integer.h:1775
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:145
uint64_t FingerprintModel(const CpModelProto &model, uint64_t seed)
std::function< IntegerVariable(Model *)> ConstantIntegerVariable(int64_t value)
Definition: integer.h:1726
LinearRelaxation ComputeLinearRelaxation(const CpModelProto &model_proto, Model *m)
std::function< void(Model *)> WeightedSumGreaterOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t lower_bound)
Definition: integer_expr.h:427
CpSolverResponse Solve(const CpModelProto &model_proto)
Solves the given CpModelProto and returns an instance of CpSolverResponse.
std::function< BooleanOrIntegerLiteral()> InstrumentSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, const std::function< BooleanOrIntegerLiteral()> &instrumented_strategy, Model *model)
SatSolver::Status MinimizeIntegerVariableWithLinearScanAndLazyEncoding(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
Collection of objects used to extend the Constraint Solver library.
const absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
std::string OrToolsVersionString()
Definition: version.cc:28
std::string ProtobufDebugString(const P &message)
std::mt19937_64 random_engine_t
Definition: random_engine.h:23
Literal literal
Definition: optimization.cc:88
int line
Definition: parse_proto.cc:31
if(!yyg->yy_init)
Definition: parser.yy.cc:965
static int input(yyscan_t yyscanner)
IntervalVar * interval
Definition: resource.cc:101
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1505
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499
std::vector< std::function< void(const CpSolverResponse &response)> > observers
std::string message
Definition: trace.cc:399
double objective_value
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47