Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <atomic>
18#include <cmath>
19#include <cstdint>
20#include <cstdlib>
21#include <deque>
22#include <functional>
23#include <limits>
24#include <memory>
25#include <random>
26#include <string>
27#include <thread>
28#include <utility>
29#include <vector>
30
32#include "ortools/base/timer.h"
33#if !defined(__PORTABLE_PLATFORM__)
36#endif // __PORTABLE_PLATFORM__
37#include "absl/base/thread_annotations.h"
38#include "absl/container/btree_map.h"
39#include "absl/container/btree_set.h"
40#include "absl/container/flat_hash_map.h"
41#include "absl/flags/flag.h"
42#include "absl/log/check.h"
43#include "absl/random/distributions.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/string_view.h"
48#include "absl/synchronization/mutex.h"
49#include "absl/types/span.h"
50#include "google/protobuf/arena.h"
51#include "google/protobuf/text_format.h"
54#include "ortools/sat/cp_model.pb.h"
66#include "ortools/sat/integer.h"
69#include "ortools/sat/model.h"
74#include "ortools/sat/sat_parameters.pb.h"
79#include "ortools/sat/util.h"
83#if !defined(__PORTABLE_PLATFORM__)
84#include "ortools/util/sigint.h"
85#endif // __PORTABLE_PLATFORM__
89
91 bool, cp_model_export_model, false,
92 "DEBUG ONLY. When set to true, SolveCpModel() will dump its input model "
93 "protos in text format to 'FLAGS_cp_model_dump_prefix'{name}.pb.txt.");
94
95ABSL_FLAG(bool, cp_model_dump_text_proto, true,
96 "DEBUG ONLY, dump models in text proto instead of binary proto.");
97
99 bool, cp_model_dump_problematic_lns, false,
100 "DEBUG ONLY. Similar to --cp_model_dump_submodels, but only dump fragment "
101 "for which we got an issue while validating the postsolved solution. This "
102 "allows to debug presolve issues without dumping all the models.");
103
104ABSL_FLAG(bool, cp_model_dump_response, false,
105 "DEBUG ONLY. If true, the final response of each solve will be "
106 "dumped to 'FLAGS_cp_model_dump_prefix'response.pb.txt");
107
108ABSL_FLAG(std::string, cp_model_params, "",
109 "This is interpreted as a text SatParameters proto. The "
110 "specified fields will override the normal ones for all solves.");
111
112ABSL_FLAG(bool, debug_model_copy, false,
113 "If true, copy the input model as if with no basic presolve");
114
115ABSL_FLAG(bool, cp_model_ignore_objective, false,
116 "If true, ignore the objective.");
117ABSL_FLAG(bool, cp_model_ignore_hints, false,
118 "If true, ignore any supplied hints.");
119ABSL_FLAG(bool, cp_model_fingerprint_model, true, "Fingerprint the model.");
120
121namespace operations_research {
122namespace sat {
123
124std::string CpSatSolverVersion() {
125 return absl::StrCat("CP-SAT solver v", OrToolsVersionString());
126}
127
128namespace {
129
130// Makes the string fit in one line by cutting it in the middle if necessary.
131std::string Summarize(absl::string_view input) {
132 if (input.size() < 105) return std::string(input);
133 const int half = 50;
134 return absl::StrCat(input.substr(0, half), " ... ",
135 input.substr(input.size() - half, half));
136}
137
138template <class M>
139void DumpModelProto(const M& proto, absl::string_view name) {
140 std::string filename;
141 if (absl::GetFlag(FLAGS_cp_model_dump_text_proto)) {
142 filename = absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix), name,
143 ".pb.txt");
144 LOG(INFO) << "Dumping " << name << " text proto to '" << filename << "'.";
145 } else {
146 const std::string filename =
147 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix), name, ".bin");
148 LOG(INFO) << "Dumping " << name << " binary proto to '" << filename << "'.";
149 }
150 CHECK(WriteModelProtoToFile(proto, filename));
151}
152
153} // namespace.
154
155// =============================================================================
156// Public API.
157// =============================================================================
158
159std::string CpModelStats(const CpModelProto& model_proto) {
160 // Note that we only store pointer to "constant" string literals. This is
161 // slightly faster and take less space for model with millions of constraints.
162 absl::flat_hash_map<char const*, int> name_to_num_constraints;
163 absl::flat_hash_map<char const*, int> name_to_num_reified;
164 absl::flat_hash_map<char const*, int> name_to_num_multi_reified;
165 absl::flat_hash_map<char const*, int> name_to_num_literals;
166 absl::flat_hash_map<char const*, int> name_to_num_terms;
167 absl::flat_hash_map<char const*, int> name_to_num_complex_domain;
168 absl::flat_hash_map<char const*, int> name_to_num_expressions;
169
170 int no_overlap_2d_num_rectangles = 0;
171 int no_overlap_2d_num_optional_rectangles = 0;
172 int no_overlap_2d_num_linear_areas = 0;
173 int no_overlap_2d_num_quadratic_areas = 0;
174 int no_overlap_2d_num_fixed_rectangles = 0;
175
176 int cumulative_num_intervals = 0;
177 int cumulative_num_optional_intervals = 0;
178 int cumulative_num_variable_sizes = 0;
179 int cumulative_num_variable_demands = 0;
180 int cumulative_num_fixed_intervals = 0;
181
182 int no_overlap_num_intervals = 0;
183 int no_overlap_num_optional_intervals = 0;
184 int no_overlap_num_variable_sizes = 0;
185 int no_overlap_num_fixed_intervals = 0;
186
187 int num_fixed_intervals = 0;
188
189 for (const ConstraintProto& ct : model_proto.constraints()) {
190 // We split the linear constraints into 3 buckets has it gives more insight
191 // on the type of problem we are facing.
192 char const* name;
193 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
194 if (ct.linear().vars_size() == 0) name = "kLinear0";
195 if (ct.linear().vars_size() == 1) name = "kLinear1";
196 if (ct.linear().vars_size() == 2) name = "kLinear2";
197 if (ct.linear().vars_size() == 3) name = "kLinear3";
198 if (ct.linear().vars_size() > 3) name = "kLinearN";
199 } else {
200 name = ConstraintCaseName(ct.constraint_case()).data();
201 }
202
203 name_to_num_constraints[name]++;
204 if (!ct.enforcement_literal().empty()) {
205 name_to_num_reified[name]++;
206 if (ct.enforcement_literal().size() > 1) {
207 name_to_num_multi_reified[name]++;
208 }
209 }
210
211 auto variable_is_fixed = [&model_proto](int ref) {
212 const IntegerVariableProto& proto =
213 model_proto.variables(PositiveRef(ref));
214 return proto.domain_size() == 2 && proto.domain(0) == proto.domain(1);
215 };
216
217 auto expression_is_fixed =
218 [&variable_is_fixed](const LinearExpressionProto& expr) {
219 for (const int ref : expr.vars()) {
220 if (!variable_is_fixed(ref)) {
221 return false;
222 }
223 }
224 return true;
225 };
226
227 auto interval_has_fixed_size = [&model_proto, &expression_is_fixed](int c) {
228 return expression_is_fixed(model_proto.constraints(c).interval().size());
229 };
230
231 auto constraint_is_optional = [&model_proto](int i) {
232 return !model_proto.constraints(i).enforcement_literal().empty();
233 };
234
235 auto interval_is_fixed = [&variable_is_fixed,
236 expression_is_fixed](const ConstraintProto& ct) {
237 if (ct.constraint_case() != ConstraintProto::ConstraintCase::kInterval) {
238 return false;
239 }
240 for (const int lit : ct.enforcement_literal()) {
241 if (!variable_is_fixed(lit)) return false;
242 }
243 return (expression_is_fixed(ct.interval().start()) &&
244 expression_is_fixed(ct.interval().size()) &&
245 expression_is_fixed(ct.interval().end()));
246 };
247
248 // For pure Boolean constraints, we also display the total number of literal
249 // involved as this gives a good idea of the problem size.
250 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
251 name_to_num_literals[name] += ct.bool_or().literals().size();
252 } else if (ct.constraint_case() ==
253 ConstraintProto::ConstraintCase::kBoolAnd) {
254 name_to_num_literals[name] +=
255 ct.enforcement_literal().size() + ct.bool_and().literals().size();
256 } else if (ct.constraint_case() ==
257 ConstraintProto::ConstraintCase::kAtMostOne) {
258 name_to_num_literals[name] += ct.at_most_one().literals().size();
259 } else if (ct.constraint_case() ==
260 ConstraintProto::ConstraintCase::kExactlyOne) {
261 name_to_num_literals[name] += ct.exactly_one().literals().size();
262 } else if (ct.constraint_case() ==
263 ConstraintProto::ConstraintCase::kLinMax) {
264 name_to_num_expressions[name] += ct.lin_max().exprs().size();
265 } else if (ct.constraint_case() ==
266 ConstraintProto::ConstraintCase::kInterval) {
267 if (interval_is_fixed(ct)) num_fixed_intervals++;
268 } else if (ct.constraint_case() ==
269 ConstraintProto::ConstraintCase::kNoOverlap2D) {
270 const int num_boxes = ct.no_overlap_2d().x_intervals_size();
271 no_overlap_2d_num_rectangles += num_boxes;
272 for (int i = 0; i < num_boxes; ++i) {
273 const int x_interval = ct.no_overlap_2d().x_intervals(i);
274 const int y_interval = ct.no_overlap_2d().y_intervals(i);
275 if (constraint_is_optional(x_interval) ||
276 constraint_is_optional(y_interval)) {
277 no_overlap_2d_num_optional_rectangles++;
278 }
279 const int num_fixed = interval_has_fixed_size(x_interval) +
280 interval_has_fixed_size(y_interval);
281 if (num_fixed == 0) {
282 no_overlap_2d_num_quadratic_areas++;
283 } else if (num_fixed == 1) {
284 no_overlap_2d_num_linear_areas++;
285 }
286 if (interval_is_fixed(model_proto.constraints(x_interval)) &&
287 interval_is_fixed(model_proto.constraints(y_interval))) {
288 no_overlap_2d_num_fixed_rectangles++;
289 }
290 }
291 } else if (ct.constraint_case() ==
292 ConstraintProto::ConstraintCase::kNoOverlap) {
293 const int num_intervals = ct.no_overlap().intervals_size();
294 no_overlap_num_intervals += num_intervals;
295 for (int i = 0; i < num_intervals; ++i) {
296 const int interval = ct.no_overlap().intervals(i);
297 if (constraint_is_optional(interval)) {
298 no_overlap_num_optional_intervals++;
299 }
300 if (!interval_has_fixed_size(interval)) {
301 no_overlap_num_variable_sizes++;
302 }
303 if (interval_is_fixed(model_proto.constraints(interval))) {
304 no_overlap_num_fixed_intervals++;
305 }
306 }
307 } else if (ct.constraint_case() ==
308 ConstraintProto::ConstraintCase::kCumulative) {
309 const int num_intervals = ct.cumulative().intervals_size();
310 cumulative_num_intervals += num_intervals;
311 for (int i = 0; i < num_intervals; ++i) {
312 const int interval = ct.cumulative().intervals(i);
313 if (constraint_is_optional(interval)) {
314 cumulative_num_optional_intervals++;
315 }
316 if (!interval_has_fixed_size(interval)) {
317 cumulative_num_variable_sizes++;
318 }
319 if (!expression_is_fixed(ct.cumulative().demands(i))) {
320 cumulative_num_variable_demands++;
321 }
322 if (interval_is_fixed(model_proto.constraints(interval))) {
323 cumulative_num_fixed_intervals++;
324 }
325 }
326 }
327
328 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
329 ct.linear().vars_size() > 3) {
330 name_to_num_terms[name] += ct.linear().vars_size();
331 }
332 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
333 ct.linear().vars_size() > 1 && ct.linear().domain().size() > 2) {
334 name_to_num_complex_domain[name]++;
335 }
336 }
337
338 int num_constants = 0;
339 absl::btree_set<int64_t> constant_values;
340 absl::btree_map<Domain, int> num_vars_per_domains;
341 for (const IntegerVariableProto& var : model_proto.variables()) {
342 if (var.domain_size() == 2 && var.domain(0) == var.domain(1)) {
343 ++num_constants;
344 constant_values.insert(var.domain(0));
345 } else {
346 num_vars_per_domains[ReadDomainFromProto(var)]++;
347 }
348 }
349
350 std::string result;
351 const std::string model_fingerprint_str =
352 (absl::GetFlag(FLAGS_cp_model_fingerprint_model))
353 ? absl::StrFormat(" (model_fingerprint: %#x)",
354 FingerprintModel(model_proto))
355 : "";
356
357 if (model_proto.has_objective() ||
358 model_proto.has_floating_point_objective()) {
359 absl::StrAppend(&result, "optimization model '", model_proto.name(),
360 "':", model_fingerprint_str, "\n");
361 } else {
362 absl::StrAppend(&result, "satisfaction model '", model_proto.name(),
363 "':", model_fingerprint_str, "\n");
364 }
365
366 for (const DecisionStrategyProto& strategy : model_proto.search_strategy()) {
367 absl::StrAppend(
368 &result, "Search strategy: on ",
369 strategy.exprs().size() + strategy.variables().size(), " variables, ",
371 strategy.variable_selection_strategy()),
372 ", ",
374 strategy.domain_reduction_strategy()),
375 "\n");
376 }
377
378 auto count_variables_by_type =
379 [&model_proto](const google::protobuf::RepeatedField<int>& vars,
380 int* num_booleans, int* num_integers) {
381 for (const int ref : vars) {
382 const auto& var_proto = model_proto.variables(PositiveRef(ref));
383 if (var_proto.domain_size() == 2 && var_proto.domain(0) == 0 &&
384 var_proto.domain(1) == 1) {
385 (*num_booleans)++;
386 }
387 }
388 *num_integers = vars.size() - *num_booleans;
389 };
390
391 {
392 int num_boolean_variables_in_objective = 0;
393 int num_integer_variables_in_objective = 0;
394 if (model_proto.has_objective()) {
395 count_variables_by_type(model_proto.objective().vars(),
396 &num_boolean_variables_in_objective,
397 &num_integer_variables_in_objective);
398 }
399 if (model_proto.has_floating_point_objective()) {
400 count_variables_by_type(model_proto.floating_point_objective().vars(),
401 &num_boolean_variables_in_objective,
402 &num_integer_variables_in_objective);
403 }
404
405 std::vector<std::string> obj_vars_strings;
406 if (num_boolean_variables_in_objective > 0) {
407 obj_vars_strings.push_back(absl::StrCat(
408 "#bools: ", FormatCounter(num_boolean_variables_in_objective)));
409 }
410 if (num_integer_variables_in_objective > 0) {
411 obj_vars_strings.push_back(absl::StrCat(
412 "#ints: ", FormatCounter(num_integer_variables_in_objective)));
413 }
414
415 const std::string objective_string =
416 model_proto.has_objective()
417 ? absl::StrCat(" (", absl::StrJoin(obj_vars_strings, " "),
418 " in objective)")
419 : (model_proto.has_floating_point_objective()
420 ? absl::StrCat(" (", absl::StrJoin(obj_vars_strings, " "),
421 " in floating point objective)")
422 : "");
423 absl::StrAppend(&result,
424 "#Variables: ", FormatCounter(model_proto.variables_size()),
425 objective_string, "\n");
426 }
427 if (num_vars_per_domains.contains(Domain(0, 1))) {
428 // We always list Boolean first.
429 const int num_bools = num_vars_per_domains[Domain(0, 1)];
430 const std::string temp =
431 absl::StrCat(" - ", FormatCounter(num_bools), " Booleans in ",
432 Domain(0, 1).ToString(), "\n");
433 absl::StrAppend(&result, Summarize(temp));
434 num_vars_per_domains.erase(Domain(0, 1));
435 }
436 if (num_vars_per_domains.size() < 100) {
437 for (const auto& entry : num_vars_per_domains) {
438 const std::string temp =
439 absl::StrCat(" - ", FormatCounter(entry.second), " in ",
440 entry.first.ToString(), "\n");
441 absl::StrAppend(&result, Summarize(temp));
442 }
443 } else {
444 int64_t max_complexity = 0;
445 int64_t min = std::numeric_limits<int64_t>::max();
446 int64_t max = std::numeric_limits<int64_t>::min();
447 for (const auto& entry : num_vars_per_domains) {
448 min = std::min(min, entry.first.Min());
449 max = std::max(max, entry.first.Max());
450 max_complexity = std::max(
451 max_complexity, static_cast<int64_t>(entry.first.NumIntervals()));
452 }
453 absl::StrAppend(&result, " - ", FormatCounter(num_vars_per_domains.size()),
454 " different domains in [", min, ",", max,
455 "] with a largest complexity of ", max_complexity, ".\n");
456 }
457
458 if (num_constants > 0) {
459 const std::string temp =
460 absl::StrCat(" - ", num_constants, " constants in {",
461 absl::StrJoin(constant_values, ","), "} \n");
462 absl::StrAppend(&result, Summarize(temp));
463 }
464
465 std::vector<std::string> constraints;
466 constraints.reserve(name_to_num_constraints.size());
467 for (const auto& [c_name, count] : name_to_num_constraints) {
468 const std::string name(c_name);
469 constraints.push_back(absl::StrCat("#", name, ": ", FormatCounter(count)));
470 if (name_to_num_reified.contains(c_name)) {
471 if (name_to_num_multi_reified.contains(c_name)) {
472 absl::StrAppend(
473 &constraints.back(),
474 " (#enforced: ", FormatCounter(name_to_num_reified[c_name]),
475 " #multi: ", FormatCounter(name_to_num_multi_reified[c_name]), ")");
476 } else {
477 absl::StrAppend(&constraints.back(), " (#enforced: ",
478 FormatCounter(name_to_num_reified[c_name]), ")");
479 }
480 }
481 if (name_to_num_literals.contains(c_name)) {
482 absl::StrAppend(&constraints.back(), " (#literals: ",
483 FormatCounter(name_to_num_literals[c_name]), ")");
484 }
485 if (name_to_num_terms.contains(c_name)) {
486 absl::StrAppend(&constraints.back(),
487 " (#terms: ", FormatCounter(name_to_num_terms[c_name]),
488 ")");
489 }
490 if (name_to_num_expressions.contains(c_name)) {
491 absl::StrAppend(&constraints.back(), " (#expressions: ",
492 FormatCounter(name_to_num_expressions[c_name]), ")");
493 }
494 if (name_to_num_complex_domain.contains(c_name)) {
495 absl::StrAppend(&constraints.back(), " (#complex_domain: ",
496 FormatCounter(name_to_num_complex_domain[c_name]), ")");
497 }
498 if (name == "kInterval" && num_fixed_intervals > 0) {
499 absl::StrAppend(&constraints.back(), " (#fixed: ", num_fixed_intervals,
500 ")");
501 } else if (name == "kNoOverlap2D") {
502 absl::StrAppend(&constraints.back(),
503 " (#rectangles: ", no_overlap_2d_num_rectangles);
504 if (no_overlap_2d_num_optional_rectangles > 0) {
505 absl::StrAppend(&constraints.back(),
506 ", #optional: ", no_overlap_2d_num_optional_rectangles);
507 }
508 if (no_overlap_2d_num_linear_areas > 0) {
509 absl::StrAppend(&constraints.back(),
510 ", #linear_areas: ", no_overlap_2d_num_linear_areas);
511 }
512 if (no_overlap_2d_num_quadratic_areas > 0) {
513 absl::StrAppend(&constraints.back(), ", #quadratic_areas: ",
514 no_overlap_2d_num_quadratic_areas);
515 }
516 if (no_overlap_2d_num_fixed_rectangles > 0) {
517 absl::StrAppend(&constraints.back(), ", #fixed_rectangles: ",
518 no_overlap_2d_num_fixed_rectangles);
519 }
520 absl::StrAppend(&constraints.back(), ")");
521 } else if (name == "kCumulative") {
522 absl::StrAppend(&constraints.back(),
523 " (#intervals: ", cumulative_num_intervals);
524 if (cumulative_num_optional_intervals > 0) {
525 absl::StrAppend(&constraints.back(),
526 ", #optional: ", cumulative_num_optional_intervals);
527 }
528 if (cumulative_num_variable_sizes > 0) {
529 absl::StrAppend(&constraints.back(),
530 ", #variable_sizes: ", cumulative_num_variable_sizes);
531 }
532 if (cumulative_num_variable_demands > 0) {
533 absl::StrAppend(&constraints.back(), ", #variable_demands: ",
534 cumulative_num_variable_demands);
535 }
536 if (cumulative_num_fixed_intervals > 0) {
537 absl::StrAppend(&constraints.back(),
538 ", #fixed_intervals: ", cumulative_num_fixed_intervals);
539 }
540 absl::StrAppend(&constraints.back(), ")");
541 } else if (name == "kNoOverlap") {
542 absl::StrAppend(&constraints.back(),
543 " (#intervals: ", no_overlap_num_intervals);
544 if (no_overlap_num_optional_intervals > 0) {
545 absl::StrAppend(&constraints.back(),
546 ", #optional: ", no_overlap_num_optional_intervals);
547 }
548 if (no_overlap_num_variable_sizes > 0) {
549 absl::StrAppend(&constraints.back(),
550 ", #variable_sizes: ", no_overlap_num_variable_sizes);
551 }
552 if (no_overlap_num_fixed_intervals > 0) {
553 absl::StrAppend(&constraints.back(),
554 ", #fixed_intervals: ", no_overlap_num_fixed_intervals);
555 }
556 absl::StrAppend(&constraints.back(), ")");
557 }
558 }
559 std::sort(constraints.begin(), constraints.end());
560 absl::StrAppend(&result, absl::StrJoin(constraints, "\n"));
561
562 return result;
563}
564
565std::string CpSolverResponseStats(const CpSolverResponse& response,
566 bool has_objective) {
567 std::string result;
568 absl::StrAppend(&result, "CpSolverResponse summary:");
569 absl::StrAppend(&result, "\nstatus: ",
570 ProtoEnumToString<CpSolverStatus>(response.status()));
571
572 if (has_objective && response.status() != CpSolverStatus::INFEASIBLE) {
573 absl::StrAppendFormat(&result, "\nobjective: %.16g",
574 response.objective_value());
575 absl::StrAppendFormat(&result, "\nbest_bound: %.16g",
576 response.best_objective_bound());
577 } else {
578 absl::StrAppend(&result, "\nobjective: NA");
579 absl::StrAppend(&result, "\nbest_bound: NA");
580 }
581
582 absl::StrAppend(&result, "\nintegers: ", response.num_integers());
583 absl::StrAppend(&result, "\nbooleans: ", response.num_booleans());
584 absl::StrAppend(&result, "\nconflicts: ", response.num_conflicts());
585 absl::StrAppend(&result, "\nbranches: ", response.num_branches());
586
587 // TODO(user): This is probably better named "binary_propagation", but we just
588 // output "propagations" to be consistent with sat/analyze.sh.
589 absl::StrAppend(&result,
590 "\npropagations: ", response.num_binary_propagations());
591 absl::StrAppend(
592 &result, "\ninteger_propagations: ", response.num_integer_propagations());
593
594 absl::StrAppend(&result, "\nrestarts: ", response.num_restarts());
595 absl::StrAppend(&result, "\nlp_iterations: ", response.num_lp_iterations());
596 absl::StrAppend(&result, "\nwalltime: ", response.wall_time());
597 absl::StrAppend(&result, "\nusertime: ", response.user_time());
598 absl::StrAppend(&result,
599 "\ndeterministic_time: ", response.deterministic_time());
600 absl::StrAppend(&result, "\ngap_integral: ", response.gap_integral());
601 if (!response.solution().empty()) {
602 absl::StrAppendFormat(
603 &result, "\nsolution_fingerprint: %#x",
605 }
606 absl::StrAppend(&result, "\n");
607 return result;
608}
609
610namespace {
611
612void LogSubsolverNames(absl::Span<const std::unique_ptr<SubSolver>> subsolvers,
613 absl::Span<const std::string> ignored,
614 SolverLogger* logger) {
615 if (!logger->LoggingIsEnabled()) return;
616
617 std::vector<std::string> full_problem_solver_names;
618 std::vector<std::string> incomplete_solver_names;
619 std::vector<std::string> first_solution_solver_names;
620 std::vector<std::string> helper_solver_names;
621 for (int i = 0; i < subsolvers.size(); ++i) {
622 const auto& subsolver = subsolvers[i];
623 switch (subsolver->type()) {
625 full_problem_solver_names.push_back(subsolver->name());
626 break;
628 incomplete_solver_names.push_back(subsolver->name());
629 break;
631 first_solution_solver_names.push_back(subsolver->name());
632 break;
634 helper_solver_names.push_back(subsolver->name());
635 break;
636 }
637 }
638
639 // TODO(user): We might not want to sort the subsolver by name to keep our
640 // ordered list by importance? not sure.
641 auto display_subsolver_list = [logger](absl::Span<const std::string> names,
642 const absl::string_view type_name) {
643 if (!names.empty()) {
644 absl::btree_map<std::string, int> solvers_and_count;
645 for (const auto& name : names) {
646 solvers_and_count[name]++;
647 }
648 std::vector<std::string> counted_names;
649 for (const auto& [name, count] : solvers_and_count) {
650 if (count == 1) {
651 counted_names.push_back(name);
652 } else {
653 counted_names.push_back(absl::StrCat(name, "(", count, ")"));
654 }
655 }
657 logger, names.size(), " ",
658 absl::StrCat(type_name, names.size() == 1 ? "" : "s"), ": [",
659 absl::StrJoin(counted_names.begin(), counted_names.end(), ", "), "]");
660 }
661 };
662
663 display_subsolver_list(full_problem_solver_names, "full problem subsolver");
664 display_subsolver_list(first_solution_solver_names,
665 "first solution subsolver");
666 display_subsolver_list(incomplete_solver_names, "interleaved subsolver");
667 display_subsolver_list(helper_solver_names, "helper subsolver");
668 if (!ignored.empty()) {
669 display_subsolver_list(ignored, "ignored subsolver");
670 }
671
672 SOLVER_LOG(logger, "");
673}
674
675void LogFinalStatistics(SharedClasses* shared) {
676 if (!shared->logger->LoggingIsEnabled()) return;
677
678 shared->logger->FlushPendingThrottledLogs(/*ignore_rates=*/true);
679 SOLVER_LOG(shared->logger, "");
680
681 shared->stat_tables.Display(shared->logger);
682 shared->response->DisplayImprovementStatistics();
683
684 std::vector<std::vector<std::string>> table;
685 table.push_back({"Solution repositories", "Added", "Queried", "Synchro"});
686 table.push_back(shared->response->SolutionsRepository().TableLineStats());
687 if (shared->lp_solutions != nullptr) {
688 table.push_back(shared->lp_solutions->TableLineStats());
689 }
690 if (shared->incomplete_solutions != nullptr) {
691 table.push_back(shared->incomplete_solutions->TableLineStats());
692 }
693 SOLVER_LOG(shared->logger, FormatTable(table));
694
695 if (shared->bounds) {
696 shared->bounds->LogStatistics(shared->logger);
697 }
698
699 if (shared->clauses) {
700 shared->clauses->LogStatistics(shared->logger);
701 }
702
703 // Extra logging if needed. Note that these are mainly activated on
704 // --vmodule *some_file*=1 and are here for development.
705 shared->stats->Log(shared->logger);
706}
707
708void LaunchSubsolvers(const SatParameters& params, SharedClasses* shared,
709 std::vector<std::unique_ptr<SubSolver>>& subsolvers,
710 absl::Span<const std::string> ignored) {
711 // Initial logging.
712 SOLVER_LOG(shared->logger, "");
713 if (params.interleave_search()) {
714 SOLVER_LOG(shared->logger,
715 absl::StrFormat("Starting deterministic search at %.2fs with "
716 "%i workers and batch size of %d.",
717 shared->wall_timer->Get(), params.num_workers(),
718 params.interleave_batch_size()));
719 } else {
721 shared->logger,
722 absl::StrFormat("Starting search at %.2fs with %i workers.",
723 shared->wall_timer->Get(), params.num_workers()));
724 }
725 LogSubsolverNames(subsolvers, ignored, shared->logger);
726
727 // Launch the main search loop.
728 if (params.interleave_search()) {
729 int batch_size = params.interleave_batch_size();
730 if (batch_size == 0) {
731 batch_size = params.num_workers() == 1 ? 1 : params.num_workers() * 3;
733 shared->logger,
734 "Setting number of tasks in each batch of interleaved search to ",
735 batch_size);
736 }
737 DeterministicLoop(subsolvers, params.num_workers(), batch_size,
738 params.max_num_deterministic_batches());
739 } else {
740 NonDeterministicLoop(subsolvers, params.num_workers());
741 }
742
743 // We need to delete the subsolvers in order to fill the stat tables. Note
744 // that first solution should already be deleted. We delete manually as
745 // windows release vectors in the opposite order.
746 for (int i = 0; i < subsolvers.size(); ++i) {
747 subsolvers[i].reset();
748 }
749 LogFinalStatistics(shared);
750}
751
752// Encapsulate a full CP-SAT solve without presolve in the SubSolver API.
753class FullProblemSolver : public SubSolver {
754 public:
755 FullProblemSolver(absl::string_view name,
756 const SatParameters& local_parameters, bool split_in_chunks,
757 SharedClasses* shared, bool stop_at_first_solution = false)
758 : SubSolver(name, stop_at_first_solution ? FIRST_SOLUTION : FULL_PROBLEM),
759 shared_(shared),
760 split_in_chunks_(split_in_chunks),
761 stop_at_first_solution_(stop_at_first_solution),
762 local_model_(SubSolver::name()) {
763 // Setup the local model parameters and time limit.
764 *(local_model_.GetOrCreate<SatParameters>()) = local_parameters;
765 shared_->time_limit->UpdateLocalLimit(
766 local_model_.GetOrCreate<TimeLimit>());
767
768 if (stop_at_first_solution) {
769 local_model_.GetOrCreate<TimeLimit>()
770 ->RegisterSecondaryExternalBooleanAsLimit(
771 shared_->response->first_solution_solvers_should_stop());
772 }
773
774 if (shared->response != nullptr) {
775 local_model_.Register<SharedResponseManager>(shared->response);
776 }
777
778 if (shared->lp_solutions != nullptr) {
779 local_model_.Register<SharedLPSolutionRepository>(
780 shared->lp_solutions.get());
781 }
782
783 if (shared->incomplete_solutions != nullptr) {
784 local_model_.Register<SharedIncompleteSolutionManager>(
785 shared->incomplete_solutions.get());
786 }
787
788 if (shared->bounds != nullptr) {
789 local_model_.Register<SharedBoundsManager>(shared->bounds.get());
790 }
791
792 if (shared->clauses != nullptr) {
793 local_model_.Register<SharedClausesManager>(shared->clauses.get());
794 }
795
796 if (local_parameters.use_shared_tree_search()) {
797 local_model_.Register<SharedTreeManager>(shared->shared_tree_manager);
798 }
799
800 // TODO(user): For now we do not count LNS statistics. We could easily
801 // by registering the SharedStatistics class with LNS local model.
802 local_model_.Register<SharedStatistics>(shared_->stats);
803
804 // Setup the local logger, in multi-thread log_search_progress should be
805 // false by default, but we might turn it on for debugging. It is on by
806 // default in single-thread mode.
807 auto* logger = local_model_.GetOrCreate<SolverLogger>();
808 logger->EnableLogging(local_parameters.log_search_progress());
809 logger->SetLogToStdOut(local_parameters.log_to_stdout());
810 }
811
812 ~FullProblemSolver() override {
813 CpSolverResponse response;
814 FillSolveStatsInResponse(&local_model_, &response);
815 shared_->response->AppendResponseToBeMerged(response);
816 shared_->stat_tables.AddTimingStat(*this);
817 shared_->stat_tables.AddLpStat(name(), &local_model_);
818 shared_->stat_tables.AddSearchStat(name(), &local_model_);
819 shared_->stat_tables.AddClausesStat(name(), &local_model_);
820 }
821
822 bool IsDone() override {
823 // On large problem, deletion can take a while, so is is better to do it
824 // while waiting for the slower worker to finish.
825 if (shared_->SearchIsDone()) return true;
826
827 return stop_at_first_solution_ &&
828 shared_->response->first_solution_solvers_should_stop()->load();
829 }
830
831 bool TaskIsAvailable() override {
832 if (IsDone()) return false;
833
834 // Tricky: we don't want this in IsDone() otherwise the order of destruction
835 // is unclear, and currently we always report the stats of the last
836 // destroyed full solver (i.e. the first created with is the one with the
837 // parameter provided by the user).
838 if (shared_->SearchIsDone()) return false;
839
840 absl::MutexLock mutex_lock(&mutex_);
841 if (previous_task_is_completed_) {
842 if (solving_first_chunk_) return true;
843 if (split_in_chunks_) return true;
844 }
845 return false;
846 }
847
848 std::function<void()> GenerateTask(int64_t /*task_id*/) override {
849 {
850 absl::MutexLock mutex_lock(&mutex_);
851 previous_task_is_completed_ = false;
852 }
853 return [this]() {
854 if (solving_first_chunk_) {
855 LoadCpModel(shared_->model_proto, &local_model_);
856
857 // Level zero variable bounds sharing. It is important to register
858 // that after the probing that takes place in LoadCpModel() otherwise
859 // we will have a mutex contention issue when all the thread probes
860 // at the same time.
861 if (shared_->bounds != nullptr) {
863 shared_->model_proto, shared_->bounds.get(), &local_model_);
865 shared_->model_proto, shared_->bounds.get(), &local_model_);
866 }
867
868 // Note that this is done after the loading, so we will never export
869 // problem clauses.
870 if (shared_->clauses != nullptr) {
871 const int id = shared_->clauses->RegisterNewId();
872 shared_->clauses->SetWorkerNameForId(id, local_model_.Name());
873
874 RegisterClausesLevelZeroImport(id, shared_->clauses.get(),
875 &local_model_);
876 RegisterClausesExport(id, shared_->clauses.get(), &local_model_);
877 }
878
879 auto* logger = local_model_.GetOrCreate<SolverLogger>();
880 SOLVER_LOG(logger, "");
881 SOLVER_LOG(logger, absl::StrFormat(
882 "Starting subsolver \'%s\' hint search at %.2fs",
883 name(), shared_->wall_timer->Get()));
884
885 if (local_model_.GetOrCreate<SatParameters>()->repair_hint()) {
886 MinimizeL1DistanceWithHint(shared_->model_proto, &local_model_);
887 } else {
888 QuickSolveWithHint(shared_->model_proto, &local_model_);
889 }
890
891 SOLVER_LOG(logger,
892 absl::StrFormat("Starting subsolver \'%s\' search at %.2fs",
893 name(), shared_->wall_timer->Get()));
894
895 // No need for mutex since we only run one task at the time.
896 solving_first_chunk_ = false;
897
898 if (split_in_chunks_) {
899 // Abort first chunk and allow to schedule the next.
900 absl::MutexLock mutex_lock(&mutex_);
901 previous_task_is_completed_ = true;
902 return;
903 }
904 }
905
906 auto* time_limit = local_model_.GetOrCreate<TimeLimit>();
907 if (split_in_chunks_) {
908 // Configure time limit for chunk solving. Note that we do not want
909 // to do that for the hint search for now.
910 auto* params = local_model_.GetOrCreate<SatParameters>();
911 params->set_max_deterministic_time(1);
912 time_limit->ResetLimitFromParameters(*params);
913 shared_->time_limit->UpdateLocalLimit(time_limit);
914 }
915
916 const double saved_dtime = time_limit->GetElapsedDeterministicTime();
917 SolveLoadedCpModel(shared_->model_proto, &local_model_);
918
919 absl::MutexLock mutex_lock(&mutex_);
920 previous_task_is_completed_ = true;
921 dtime_since_last_sync_ +=
922 time_limit->GetElapsedDeterministicTime() - saved_dtime;
923 };
924 }
925
926 // TODO(user): A few of the information sharing we do between threads does not
927 // happen here (bound sharing, RINS neighborhood, objective). Fix that so we
928 // can have a deterministic parallel mode.
929 void Synchronize() override {
930 absl::MutexLock mutex_lock(&mutex_);
931 AddTaskDeterministicDuration(dtime_since_last_sync_);
932 shared_->time_limit->AdvanceDeterministicTime(dtime_since_last_sync_);
933 dtime_since_last_sync_ = 0.0;
934 }
935
936 private:
937 SharedClasses* shared_;
938 const bool split_in_chunks_;
939 const bool stop_at_first_solution_;
940 Model local_model_;
941
942 // The first chunk is special. It is the one in which we load the model and
943 // try to follow the hint.
944 bool solving_first_chunk_ = true;
945
946 absl::Mutex mutex_;
947 double dtime_since_last_sync_ ABSL_GUARDED_BY(mutex_) = 0.0;
948 bool previous_task_is_completed_ ABSL_GUARDED_BY(mutex_) = true;
949};
950
951#if !defined(__PORTABLE_PLATFORM__)
952
953class FeasibilityPumpSolver : public SubSolver {
954 public:
955 FeasibilityPumpSolver(const SatParameters& local_parameters,
956 SharedClasses* shared)
957 : SubSolver("feasibility_pump", INCOMPLETE),
958 shared_(shared),
959 local_model_(std::make_unique<Model>(name())) {
960 // Setup the local model parameters and time limit.
961 *(local_model_->GetOrCreate<SatParameters>()) = local_parameters;
962 shared_->time_limit->UpdateLocalLimit(
963 local_model_->GetOrCreate<TimeLimit>());
964
965 if (shared->response != nullptr) {
966 local_model_->Register<SharedResponseManager>(shared->response);
967 }
968
969 if (shared->lp_solutions != nullptr) {
970 local_model_->Register<SharedLPSolutionRepository>(
971 shared->lp_solutions.get());
972 }
973
974 if (shared->incomplete_solutions != nullptr) {
975 local_model_->Register<SharedIncompleteSolutionManager>(
976 shared->incomplete_solutions.get());
977 }
978
979 // Level zero variable bounds sharing.
980 if (shared_->bounds != nullptr) {
981 RegisterVariableBoundsLevelZeroImport(
982 shared_->model_proto, shared_->bounds.get(), local_model_.get());
983 }
984 }
985
986 ~FeasibilityPumpSolver() override {
987 shared_->stat_tables.AddTimingStat(*this);
988 }
989
990 bool IsDone() override { return shared_->SearchIsDone(); }
991
992 bool TaskIsAvailable() override {
993 if (shared_->SearchIsDone()) return false;
994 absl::MutexLock mutex_lock(&mutex_);
995 return previous_task_is_completed_;
996 }
997
998 std::function<void()> GenerateTask(int64_t /*task_id*/) override {
999 {
1000 absl::MutexLock mutex_lock(&mutex_);
1001 previous_task_is_completed_ = false;
1002 }
1003 return [this]() {
1004 {
1005 absl::MutexLock mutex_lock(&mutex_);
1006 if (solving_first_chunk_) {
1007 LoadFeasibilityPump(shared_->model_proto, local_model_.get());
1008 // No new task will be scheduled for this worker if there is no
1009 // linear relaxation.
1010 if (local_model_->Get<FeasibilityPump>() == nullptr) return;
1011 solving_first_chunk_ = false;
1012 // Abort first chunk and allow to schedule the next.
1013 previous_task_is_completed_ = true;
1014 return;
1015 }
1016 }
1017
1018 auto* time_limit = local_model_->GetOrCreate<TimeLimit>();
1019 const double saved_dtime = time_limit->GetElapsedDeterministicTime();
1020 auto* feasibility_pump = local_model_->Mutable<FeasibilityPump>();
1021 if (!feasibility_pump->Solve()) {
1022 shared_->response->NotifyThatImprovingProblemIsInfeasible(name());
1023 }
1024
1025 {
1026 absl::MutexLock mutex_lock(&mutex_);
1027 dtime_since_last_sync_ +=
1028 time_limit->GetElapsedDeterministicTime() - saved_dtime;
1029 }
1030
1031 // Abort if the problem is solved.
1032 if (shared_->SearchIsDone()) {
1033 shared_->time_limit->Stop();
1034 return;
1035 }
1036
1037 absl::MutexLock mutex_lock(&mutex_);
1038 previous_task_is_completed_ = true;
1039 };
1040 }
1041
1042 void Synchronize() override {
1043 absl::MutexLock mutex_lock(&mutex_);
1044 AddTaskDeterministicDuration(dtime_since_last_sync_);
1045 shared_->time_limit->AdvanceDeterministicTime(dtime_since_last_sync_);
1046 dtime_since_last_sync_ = 0.0;
1047 }
1048
1049 private:
1050 SharedClasses* shared_;
1051 std::unique_ptr<Model> local_model_;
1052
1053 absl::Mutex mutex_;
1054
1055 // The first chunk is special. It is the one in which we load the linear
1056 // constraints.
1057 bool solving_first_chunk_ ABSL_GUARDED_BY(mutex_) = true;
1058
1059 double dtime_since_last_sync_ ABSL_GUARDED_BY(mutex_) = 0.0;
1060 bool previous_task_is_completed_ ABSL_GUARDED_BY(mutex_) = true;
1061};
1062
1063// A Subsolver that generate LNS solve from a given neighborhood.
1064class LnsSolver : public SubSolver {
1065 public:
1066 LnsSolver(std::unique_ptr<NeighborhoodGenerator> generator,
1067 const SatParameters& lns_parameters,
1068 NeighborhoodGeneratorHelper* helper, SharedClasses* shared)
1069 : SubSolver(generator->name(), INCOMPLETE),
1070 generator_(std::move(generator)),
1071 helper_(helper),
1072 lns_parameters_(lns_parameters),
1073 shared_(shared) {}
1074
1075 ~LnsSolver() override {
1076 shared_->stat_tables.AddTimingStat(*this);
1077 shared_->stat_tables.AddLnsStat(name(), *generator_);
1078 }
1079
1080 bool TaskIsAvailable() override {
1081 if (shared_->SearchIsDone()) return false;
1082 return generator_->ReadyToGenerate();
1083 }
1084
1085 std::function<void()> GenerateTask(int64_t task_id) override {
1086 return [task_id, this]() {
1087 if (shared_->SearchIsDone()) return;
1088
1089 // Create a random number generator whose seed depends both on the task_id
1090 // and on the parameters_.random_seed() so that changing the later will
1091 // change the LNS behavior.
1092 const int32_t low = static_cast<int32_t>(task_id);
1093 const int32_t high = static_cast<int32_t>(task_id >> 32);
1094 std::seed_seq seed{low, high, lns_parameters_.random_seed()};
1095 random_engine_t random(seed);
1096
1097 NeighborhoodGenerator::SolveData data;
1098 data.difficulty = generator_->difficulty();
1099 data.deterministic_limit = generator_->deterministic_limit();
1100
1101 // Choose a base solution for this neighborhood.
1102 CpSolverResponse base_response;
1103 {
1104 const SharedSolutionRepository<int64_t>& repo =
1105 shared_->response->SolutionsRepository();
1106 if (repo.NumSolutions() > 0) {
1107 base_response.set_status(CpSolverStatus::FEASIBLE);
1108 const SharedSolutionRepository<int64_t>::Solution solution =
1109 repo.GetRandomBiasedSolution(random);
1110 for (const int64_t value : solution.variable_values) {
1111 base_response.add_solution(value);
1112 }
1113
1114 // Note: We assume that the solution rank is the solution internal
1115 // objective.
1116 data.initial_best_objective = repo.GetSolution(0).rank;
1117 data.base_objective = solution.rank;
1118 } else {
1119 base_response.set_status(CpSolverStatus::UNKNOWN);
1120
1121 // If we do not have a solution, we use the current objective upper
1122 // bound so that our code that compute an "objective" improvement
1123 // works.
1124 //
1125 // TODO(user): this is non-deterministic. Fix.
1126 data.initial_best_objective =
1127 shared_->response->GetInnerObjectiveUpperBound();
1128 data.base_objective = data.initial_best_objective;
1129 }
1130 }
1131
1132 Neighborhood neighborhood =
1133 generator_->Generate(base_response, data.difficulty, random);
1134
1135 if (!neighborhood.is_generated) return;
1136
1137 SatParameters local_params(lns_parameters_);
1138 local_params.set_max_deterministic_time(data.deterministic_limit);
1139
1140 // TODO(user): Tune these.
1141 // TODO(user): This could be a good candidate for bandits.
1142 const int64_t stall = generator_->num_consecutive_non_improving_calls();
1143 const int search_index = stall < 10 ? 0 : task_id % 2;
1144 absl::string_view search_info;
1145 switch (search_index) {
1146 case 0:
1147 local_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
1148 local_params.set_linearization_level(0);
1149 search_info = "auto_l0";
1150 break;
1151 default:
1152 local_params.set_search_branching(SatParameters::PORTFOLIO_SEARCH);
1153 local_params.set_search_random_variable_pool_size(5);
1154 search_info = "folio_rnd";
1155 break;
1156 }
1157
1158 std::string source_info =
1159 neighborhood.source_info.empty() ? name() : neighborhood.source_info;
1160 const int64_t num_calls = std::max(int64_t{1}, generator_->num_calls());
1161 const double fully_solved_proportion =
1162 static_cast<double>(generator_->num_fully_solved_calls()) /
1163 static_cast<double>(num_calls);
1164 const std::string lns_info = absl::StrFormat(
1165 "%s (d=%0.2f s=%i t=%0.2f p=%0.2f stall=%d h=%s)", source_info,
1166 data.difficulty, task_id, data.deterministic_limit,
1167 fully_solved_proportion, stall, search_info);
1168
1169 Model local_model(lns_info);
1170 *(local_model.GetOrCreate<SatParameters>()) = local_params;
1171 TimeLimit* local_time_limit = local_model.GetOrCreate<TimeLimit>();
1172 local_time_limit->ResetLimitFromParameters(local_params);
1173 shared_->time_limit->UpdateLocalLimit(local_time_limit);
1174
1175 // Presolve and solve the LNS fragment.
1176 CpModelProto lns_fragment;
1177 CpModelProto mapping_proto;
1178 auto context = std::make_unique<PresolveContext>(
1179 &local_model, &lns_fragment, &mapping_proto);
1180
1181 *lns_fragment.mutable_variables() = neighborhood.delta.variables();
1182 {
1183 ModelCopy copier(context.get());
1184
1185 // Copy and simplify the constraints from the initial model.
1186 if (!copier.ImportAndSimplifyConstraints(helper_->ModelProto())) {
1187 return;
1188 }
1189
1190 // Copy and simplify the constraints from the delta model.
1191 if (!neighborhood.delta.constraints().empty() &&
1192 !copier.ImportAndSimplifyConstraints(neighborhood.delta)) {
1193 return;
1194 }
1195
1196 // This is not strictly needed, but useful for properly debugging an
1197 // infeasible LNS.
1198 context->WriteVariableDomainsToProto();
1199 }
1200
1201 // Copy the rest of the model and overwrite the name.
1203 helper_->ModelProto(), context.get());
1204 lns_fragment.set_name(absl::StrCat("lns_", task_id, "_", source_info));
1205
1206 // Overwrite solution hinting.
1207 if (neighborhood.delta.has_solution_hint()) {
1208 *lns_fragment.mutable_solution_hint() =
1209 neighborhood.delta.solution_hint();
1210 }
1211 if (generator_->num_consecutive_non_improving_calls() > 10 &&
1212 absl::Bernoulli(random, 0.5)) {
1213 // If we seems to be stalling, lets try to solve without the hint in
1214 // order to diversify our solution pool. Otherwise non-improving
1215 // neighborhood will just return the base solution always.
1216 lns_fragment.clear_solution_hint();
1217 }
1218 if (neighborhood.is_simple &&
1219 neighborhood.num_relaxed_variables_in_objective == 0) {
1220 // If we didn't relax the objective, there can be no improving solution.
1221 // However, we might have some diversity if they are multiple feasible
1222 // solution.
1223 //
1224 // TODO(user): How can we teak the search to favor diversity.
1225 if (generator_->num_consecutive_non_improving_calls() > 10) {
1226 // We have been staling, try to find diverse solution?
1227 lns_fragment.clear_solution_hint();
1228 } else {
1229 // Just regenerate.
1230 // Note that we do not change the difficulty.
1231 return;
1232 }
1233 }
1234
1235 CpModelProto debug_copy;
1236 if (absl::GetFlag(FLAGS_cp_model_dump_problematic_lns)) {
1237 // We need to make a copy because the presolve is destructive.
1238 // It is why we do not do that by default.
1239 debug_copy = lns_fragment;
1240 }
1241
1242 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
1243 // TODO(user): export the delta too if needed.
1244 const std::string lns_name =
1245 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
1246 lns_fragment.name(), ".pb.txt");
1247 LOG(INFO) << "Dumping LNS model to '" << lns_name << "'.";
1248 CHECK(WriteModelProtoToFile(lns_fragment, lns_name));
1249 }
1250
1251 std::vector<int> postsolve_mapping;
1252 const CpSolverStatus presolve_status =
1253 PresolveCpModel(context.get(), &postsolve_mapping);
1254
1255 // Release the context.
1256 context.reset(nullptr);
1257 neighborhood.delta.Clear();
1258
1259 // TODO(user): Depending on the problem, we should probably use the
1260 // parameters that work bests (core, linearization_level, etc...) or
1261 // maybe we can just randomize them like for the base solution used.
1262 auto* local_response_manager =
1263 local_model.GetOrCreate<SharedResponseManager>();
1264 local_response_manager->InitializeObjective(lns_fragment);
1265 local_response_manager->SetSynchronizationMode(true);
1266
1267 CpSolverResponse local_response;
1268 if (presolve_status == CpSolverStatus::UNKNOWN) {
1269 // Sometimes when presolve is aborted in the middle, we don't want to
1270 // load the model as it might fail some DCHECK.
1271 if (shared_->SearchIsDone()) return;
1272
1273 LoadCpModel(lns_fragment, &local_model);
1274 QuickSolveWithHint(lns_fragment, &local_model);
1275 SolveLoadedCpModel(lns_fragment, &local_model);
1276 local_response = local_response_manager->GetResponse();
1277
1278 // In case the LNS model is empty after presolve, the solution
1279 // repository does not add the solution, and thus does not store the
1280 // solution info. In that case, we put it back.
1281 if (local_response.solution_info().empty()) {
1282 local_response.set_solution_info(
1283 absl::StrCat(lns_info, " [presolve]"));
1284 }
1285 } else {
1286 // TODO(user): Clean this up? when the model is closed by presolve,
1287 // we don't have a nice api to get the response with stats. That said
1288 // for LNS, we don't really need it.
1289 if (presolve_status == CpSolverStatus::INFEASIBLE) {
1290 local_response_manager->NotifyThatImprovingProblemIsInfeasible(
1291 "presolve");
1292 }
1293 local_response = local_response_manager->GetResponse();
1294 local_response.set_status(presolve_status);
1295 }
1296 const std::string solution_info = local_response.solution_info();
1297 std::vector<int64_t> solution_values(local_response.solution().begin(),
1298 local_response.solution().end());
1299
1300 data.status = local_response.status();
1301 // TODO(user): we actually do not need to postsolve if the solution is
1302 // not going to be used...
1303 if (data.status == CpSolverStatus::OPTIMAL ||
1304 data.status == CpSolverStatus::FEASIBLE) {
1306 local_params, helper_->ModelProto().variables_size(), mapping_proto,
1307 postsolve_mapping, &solution_values);
1308 local_response.mutable_solution()->Assign(solution_values.begin(),
1309 solution_values.end());
1310 }
1311
1312 data.deterministic_time = local_time_limit->GetElapsedDeterministicTime();
1313
1314 bool new_solution = false;
1315 bool display_lns_info = VLOG_IS_ON(2);
1316 if (!local_response.solution().empty()) {
1317 // A solution that does not pass our validator indicates a bug. We
1318 // abort and dump the problematic model to facilitate debugging.
1319 //
1320 // TODO(user): In a production environment, we should probably just
1321 // ignore this fragment and continue.
1322 const bool feasible =
1323 SolutionIsFeasible(shared_->model_proto, solution_values);
1324 if (!feasible) {
1325 if (absl::GetFlag(FLAGS_cp_model_dump_problematic_lns)) {
1326 const std::string name =
1327 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
1328 debug_copy.name(), ".pb.txt");
1329 LOG(INFO) << "Dumping problematic LNS model to '" << name << "'.";
1330 CHECK(WriteModelProtoToFile(debug_copy, name));
1331 }
1332 LOG(ERROR) << "Infeasible LNS solution! " << solution_info
1333 << " solved with params " << local_params;
1334 return;
1335 }
1336
1337 // Special case if we solved a part of the full problem!
1338 //
1339 // TODO(user): This do not work if they are symmetries loaded into SAT.
1340 // For now we just disable this if there is any symmetry. See for
1341 // instance spot5_1401.fzn. Be smarter about that.
1342 //
1343 // The issue is that as we fix level zero variables from a partial
1344 // solution, the symmetry propagator could wrongly fix other variables
1345 // since it assumes that if we could infer such fixing, then we could
1346 // do the same in any symmetric situation.
1347 //
1348 // Note sure how to address that, we could disable symmetries if there
1349 // is a lot of connected components. Or use a different mechanism than
1350 // just fixing variables. Or remove symmetry on the fly?
1351 //
1352 // TODO(user): At least enable it if there is no Boolean symmetries
1353 // since we currently do not use the other ones past the presolve.
1354 //
1355 // TODO(user): We could however fix it in the LNS Helper!
1356 if (data.status == CpSolverStatus::OPTIMAL &&
1357 !shared_->model_proto.has_symmetry() && !solution_values.empty() &&
1358 neighborhood.is_simple &&
1359 !neighborhood.variables_that_can_be_fixed_to_local_optimum
1360 .empty()) {
1361 display_lns_info = true;
1362 shared_->bounds->FixVariablesFromPartialSolution(
1363 solution_values,
1364 neighborhood.variables_that_can_be_fixed_to_local_optimum);
1365 }
1366
1367 // Finish to fill the SolveData now that the local solve is done.
1368 data.new_objective = data.base_objective;
1369 if (data.status == CpSolverStatus::OPTIMAL ||
1370 data.status == CpSolverStatus::FEASIBLE) {
1371 data.new_objective = IntegerValue(ComputeInnerObjective(
1372 shared_->model_proto.objective(), solution_values));
1373 }
1374
1375 // Report any feasible solution we have. Optimization: We don't do that
1376 // if we just recovered the base solution.
1377 if (data.status == CpSolverStatus::OPTIMAL ||
1378 data.status == CpSolverStatus::FEASIBLE) {
1379 const std::vector<int64_t> base_solution(
1380 base_response.solution().begin(), base_response.solution().end());
1381 if (solution_values != base_solution) {
1382 new_solution = true;
1383 shared_->response->NewSolution(solution_values, solution_info,
1384 /*model=*/nullptr);
1385 }
1386 }
1387 if (!neighborhood.is_reduced &&
1388 (data.status == CpSolverStatus::OPTIMAL ||
1389 data.status == CpSolverStatus::INFEASIBLE)) {
1390 shared_->response->NotifyThatImprovingProblemIsInfeasible(
1391 solution_info);
1392 shared_->time_limit->Stop();
1393 }
1394 }
1395
1396 generator_->AddSolveData(data);
1397
1398 if (VLOG_IS_ON(2) && display_lns_info) {
1399 std::string s = absl::StrCat(" LNS ", name(), ":");
1400 if (new_solution) {
1401 const double base_obj = ScaleObjectiveValue(
1402 shared_->model_proto.objective(),
1403 ComputeInnerObjective(shared_->model_proto.objective(),
1404 base_response.solution()));
1405 const double new_obj = ScaleObjectiveValue(
1406 shared_->model_proto.objective(),
1407 ComputeInnerObjective(shared_->model_proto.objective(),
1408 solution_values));
1409 absl::StrAppend(&s, " [new_sol:", base_obj, " -> ", new_obj, "]");
1410 }
1411 if (neighborhood.is_simple) {
1412 absl::StrAppend(
1413 &s, " [", "relaxed:", neighborhood.num_relaxed_variables,
1414 " in_obj:", neighborhood.num_relaxed_variables_in_objective,
1415 " compo:",
1416 neighborhood.variables_that_can_be_fixed_to_local_optimum.size(),
1417 "]");
1418 }
1419 SOLVER_LOG(shared_->logger, s, " [d:", data.difficulty,
1420 ", id:", task_id, ", dtime:", data.deterministic_time, "/",
1421 data.deterministic_limit,
1422 ", status:", ProtoEnumToString<CpSolverStatus>(data.status),
1423 ", #calls:", generator_->num_calls(),
1424 ", p:", fully_solved_proportion, "]");
1425 }
1426 };
1427 }
1428
1429 void Synchronize() override {
1430 const double dtime = generator_->Synchronize();
1431 AddTaskDeterministicDuration(dtime);
1432 shared_->time_limit->AdvanceDeterministicTime(dtime);
1433 }
1434
1435 private:
1436 std::unique_ptr<NeighborhoodGenerator> generator_;
1437 NeighborhoodGeneratorHelper* helper_;
1438 const SatParameters lns_parameters_;
1439 SharedClasses* shared_;
1440};
1441
1442void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
1443 const SatParameters& params = *global_model->GetOrCreate<SatParameters>();
1444 CHECK(!params.enumerate_all_solutions())
1445 << "Enumerating all solutions in parallel is not supported.";
1446 if (global_model->GetOrCreate<TimeLimit>()->LimitReached()) return;
1447
1448 // If specified by the user, we might disable some parameters based on their
1449 // name.
1450 SubsolverNameFilter name_filter(params);
1451
1452 // The list of all the SubSolver that will be used in this parallel search.
1453 // These will be synchronized in order. Note that we will assemble this at
1454 // the end from the other list below.
1455 std::vector<std::unique_ptr<SubSolver>> subsolvers;
1456
1457 // We distinguish subsolver depending on their behavior:
1458 // - 'full' if a full thread is needed and they are not interleaved.
1459 // - 'first_solution' if they will be destroyed as soon as we have a solution.
1460 // - 'interleaved' if the work is cut into small chunk so that a few threads
1461 // can work on many of such subsolvers alternatively.
1462 // - 'reentrant' if one subsolver can generate many such task.
1463 //
1464 // TODO(user): Maybe we should just interleave everything for an easier
1465 // configuration.
1466 std::vector<std::unique_ptr<SubSolver>> full_worker_subsolvers;
1467 std::vector<std::unique_ptr<SubSolver>> first_solution_full_subsolvers;
1468 std::vector<std::unique_ptr<SubSolver>> reentrant_interleaved_subsolvers;
1469 std::vector<std::unique_ptr<SubSolver>> interleaved_subsolvers;
1470
1471 // Add a synchronization point for the shared classes.
1472 subsolvers.push_back(std::make_unique<SynchronizationPoint>(
1473 "synchronization_agent", [shared]() {
1474 shared->response->Synchronize();
1475 shared->response->MutableSolutionsRepository()->Synchronize();
1476 if (shared->bounds != nullptr) {
1477 shared->bounds->Synchronize();
1478 }
1479 if (shared->lp_solutions != nullptr) {
1480 shared->lp_solutions->Synchronize();
1481 }
1482 if (shared->clauses != nullptr) {
1483 shared->clauses->Synchronize();
1484 }
1485 }));
1486
1487 const auto name_to_params = GetNamedParameters(params);
1488 const SatParameters& lns_params = name_to_params.at("lns");
1489
1490 // Add the NeighborhoodGeneratorHelper as a special subsolver so that its
1491 // Synchronize() is called before any LNS neighborhood solvers.
1492 auto unique_helper = std::make_unique<NeighborhoodGeneratorHelper>(
1493 &shared->model_proto, &params, shared->response, shared->bounds.get());
1494 NeighborhoodGeneratorHelper* helper = unique_helper.get();
1495 subsolvers.push_back(std::move(unique_helper));
1496
1497 // Add shared tree workers if asked.
1498 if (params.shared_tree_num_workers() > 0 &&
1499 shared->model_proto.assumptions().empty()) {
1500 for (const SatParameters& local_params : RepeatParameters(
1501 name_filter.Filter({name_to_params.at("shared_tree")}),
1502 params.shared_tree_num_workers())) {
1503 full_worker_subsolvers.push_back(std::make_unique<FullProblemSolver>(
1504 local_params.name(), local_params,
1505 /*split_in_chunks=*/params.interleave_search(), shared));
1506 }
1507 }
1508
1509 // Add full problem solvers.
1510 for (const SatParameters& local_params : GetFullWorkerParameters(
1511 params, shared->model_proto,
1512 /*num_already_present=*/full_worker_subsolvers.size(),
1513 &name_filter)) {
1514 if (!name_filter.Keep(local_params.name())) continue;
1515
1516 // TODO(user): This is currently not supported here.
1517 if (params.optimize_with_max_hs()) continue;
1518
1519 // TODO(user): these should probably be interleaved_subsolvers.
1520 if (local_params.use_objective_shaving_search()) {
1521 full_worker_subsolvers.push_back(std::make_unique<ObjectiveShavingSolver>(
1522 local_params, helper, shared));
1523 continue;
1524 }
1525
1526 // TODO(user): these should probably be interleaved_subsolvers.
1527 if (local_params.use_variables_shaving_search()) {
1528 full_worker_subsolvers.push_back(
1529 std::make_unique<VariablesShavingSolver>(local_params, shared));
1530 continue;
1531 }
1532
1533 full_worker_subsolvers.push_back(std::make_unique<FullProblemSolver>(
1534 local_params.name(), local_params,
1535 /*split_in_chunks=*/params.interleave_search(), shared));
1536 }
1537
1538 // Add FeasibilityPumpSolver if enabled.
1539 int num_interleaved_subsolver_that_do_not_need_solution = 0;
1540 if (params.use_feasibility_pump() && name_filter.Keep("feasibility_pump")) {
1541 ++num_interleaved_subsolver_that_do_not_need_solution;
1542 interleaved_subsolvers.push_back(
1543 std::make_unique<FeasibilityPumpSolver>(params, shared));
1544 }
1545
1546 // Add rins/rens.
1547 // This behave like a LNS, it just construct starting solution differently.
1548 if (params.use_rins_lns() && name_filter.Keep("rins/rens")) {
1549 // Note that we always create the SharedLPSolutionRepository. This meets
1550 // the requirement of having a SharedLPSolutionRepository to
1551 // create RINS/RENS lns generators.
1552
1553 // TODO(user): Do we create a variable number of these workers.
1554 ++num_interleaved_subsolver_that_do_not_need_solution;
1555 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1556 std::make_unique<RelaxationInducedNeighborhoodGenerator>(
1557 helper, shared->response, shared->lp_solutions.get(),
1558 shared->incomplete_solutions.get(), name_filter.LastName()),
1559 lns_params, helper, shared));
1560 }
1561
1562 // Add incomplete subsolvers that require an objective.
1563 //
1564 // They are all re-entrant, so we do not need to specify more than the number
1565 // of workers. And they will all be interleaved, so it is okay to have many
1566 // even if we have a single thread for interleaved workers.
1567 if (params.use_lns() && shared->model_proto.has_objective() &&
1568 !shared->model_proto.objective().vars().empty()) {
1569 // Enqueue all the possible LNS neighborhood subsolvers.
1570 // Each will have their own metrics.
1571 if (name_filter.Keep("rnd_var_lns")) {
1572 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1573 std::make_unique<RelaxRandomVariablesGenerator>(
1574 helper, name_filter.LastName()),
1575 lns_params, helper, shared));
1576 }
1577 if (name_filter.Keep("rnd_cst_lns")) {
1578 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1579 std::make_unique<RelaxRandomConstraintsGenerator>(
1580 helper, name_filter.LastName()),
1581 lns_params, helper, shared));
1582 }
1583 if (name_filter.Keep("graph_var_lns")) {
1584 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1585 std::make_unique<VariableGraphNeighborhoodGenerator>(
1586 helper, name_filter.LastName()),
1587 lns_params, helper, shared));
1588 }
1589 if (name_filter.Keep("graph_arc_lns")) {
1590 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1591 std::make_unique<ArcGraphNeighborhoodGenerator>(
1592 helper, name_filter.LastName()),
1593 lns_params, helper, shared));
1594 }
1595 if (name_filter.Keep("graph_cst_lns")) {
1596 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1597 std::make_unique<ConstraintGraphNeighborhoodGenerator>(
1598 helper, name_filter.LastName()),
1599 lns_params, helper, shared));
1600 }
1601 if (name_filter.Keep("graph_dec_lns")) {
1602 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1603 std::make_unique<DecompositionGraphNeighborhoodGenerator>(
1604 helper, name_filter.LastName()),
1605 lns_params, helper, shared));
1606 }
1607 if (params.use_lb_relax_lns() && name_filter.Keep("lb_relax_lns")) {
1608 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1609 std::make_unique<LocalBranchingLpBasedNeighborhoodGenerator>(
1610 helper, name_filter.LastName(),
1611 [](const CpModelProto cp_model, Model* model) {
1612 model->GetOrCreate<SharedResponseManager>()
1613 ->InitializeObjective(cp_model);
1614 LoadCpModel(cp_model, model);
1615 SolveLoadedCpModel(cp_model, model);
1616 },
1617 shared->time_limit),
1618 lns_params, helper, shared));
1619 }
1620
1621 const bool has_no_overlap_or_cumulative =
1622 !helper->TypeToConstraints(ConstraintProto::kNoOverlap).empty() ||
1623 !helper->TypeToConstraints(ConstraintProto::kCumulative).empty();
1624
1625 // Scheduling (no_overlap and cumulative) specific LNS.
1626 if (has_no_overlap_or_cumulative) {
1627 if (name_filter.Keep("scheduling_intervals_lns")) {
1628 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1629 std::make_unique<RandomIntervalSchedulingNeighborhoodGenerator>(
1630 helper, name_filter.LastName()),
1631 lns_params, helper, shared));
1632 }
1633 if (name_filter.Keep("scheduling_time_window_lns")) {
1634 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1635 std::make_unique<SchedulingTimeWindowNeighborhoodGenerator>(
1636 helper, name_filter.LastName()),
1637 lns_params, helper, shared));
1638 }
1639 const std::vector<std::vector<int>> intervals_in_constraints =
1640 helper->GetUniqueIntervalSets();
1641 if (intervals_in_constraints.size() > 2 &&
1642 name_filter.Keep("scheduling_resource_windows_lns")) {
1643 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1644 std::make_unique<SchedulingResourceWindowsNeighborhoodGenerator>(
1645 helper, intervals_in_constraints, name_filter.LastName()),
1646 lns_params, helper, shared));
1647 }
1648 }
1649
1650 // Packing (no_overlap_2d) Specific LNS.
1651 const bool has_no_overlap2d =
1652 !helper->TypeToConstraints(ConstraintProto::kNoOverlap2D).empty();
1653 if (has_no_overlap2d) {
1654 if (name_filter.Keep("packing_rectangles_lns")) {
1655 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1656 std::make_unique<RandomRectanglesPackingNeighborhoodGenerator>(
1657 helper, name_filter.LastName()),
1658 lns_params, helper, shared));
1659 }
1660 if (name_filter.Keep("packing_precedences_lns")) {
1661 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1662 std::make_unique<RandomPrecedencesPackingNeighborhoodGenerator>(
1663 helper, name_filter.LastName()),
1664 lns_params, helper, shared));
1665 }
1666 if (name_filter.Keep("packing_slice_lns")) {
1667 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1668 std::make_unique<SlicePackingNeighborhoodGenerator>(
1669 helper, name_filter.LastName()),
1670 lns_params, helper, shared));
1671 }
1672 }
1673
1674 // Generic scheduling/packing LNS.
1675 if (has_no_overlap_or_cumulative || has_no_overlap2d) {
1676 if (name_filter.Keep("scheduling_precedences_lns")) {
1677 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1678 std::make_unique<RandomPrecedenceSchedulingNeighborhoodGenerator>(
1679 helper, name_filter.LastName()),
1680 lns_params, helper, shared));
1681 }
1682 }
1683
1684 const int num_circuit = static_cast<int>(
1685 helper->TypeToConstraints(ConstraintProto::kCircuit).size());
1686 const int num_routes = static_cast<int>(
1687 helper->TypeToConstraints(ConstraintProto::kRoutes).size());
1688 if (num_circuit + num_routes > 0) {
1689 if (name_filter.Keep("routing_random_lns")) {
1690 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1691 std::make_unique<RoutingRandomNeighborhoodGenerator>(
1692 helper, name_filter.LastName()),
1693 lns_params, helper, shared));
1694 }
1695 if (name_filter.Keep("routing_path_lns")) {
1696 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1697 std::make_unique<RoutingPathNeighborhoodGenerator>(
1698 helper, name_filter.LastName()),
1699 lns_params, helper, shared));
1700 }
1701 }
1702 if (num_routes > 0 || num_circuit > 1) {
1703 if (name_filter.Keep("routing_full_path_lns")) {
1704 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1705 std::make_unique<RoutingFullPathNeighborhoodGenerator>(
1706 helper, name_filter.LastName()),
1707 lns_params, helper, shared));
1708 }
1709 }
1710 }
1711
1712 // Used by LS and feasibility jump.
1713 // This will automatically be created (only once) if needed.
1714 const auto get_linear_model = [&]() {
1715 auto* candidate = global_model->Get<LinearModel>();
1716 if (candidate != nullptr) return candidate;
1717
1718 // Create it and transfer ownership.
1719 LinearModel* linear_model = new LinearModel(shared->model_proto);
1720 global_model->TakeOwnership(linear_model);
1721 global_model->Register(linear_model);
1722 return global_model->Get<LinearModel>();
1723 };
1724
1725 // Add violation LS workers.
1726 //
1727 // Compared to LNS, these are not re-entrant, so we need to schedule the
1728 // correct number for parallelism.
1729 if (shared->model_proto.has_objective()) {
1730 // If not forced by the parameters, we want one LS every two threads that
1731 // work on interleaved stuff. Note that by default they are many LNS, so
1732 // that shouldn't be too many.
1733 const int num_thread_for_interleaved_workers =
1734 params.num_workers() - full_worker_subsolvers.size();
1735 int num_violation_ls = params.has_num_violation_ls()
1736 ? params.num_violation_ls()
1737 : (num_thread_for_interleaved_workers + 1) / 2;
1738
1739 // If there is no rentrant solver, maybe increase the number to reach max
1740 // parallelism.
1741 if (reentrant_interleaved_subsolvers.empty()) {
1742 num_violation_ls =
1743 std::max(num_violation_ls,
1744 num_thread_for_interleaved_workers -
1745 static_cast<int>(interleaved_subsolvers.size()));
1746 }
1747
1748 const absl::string_view ls_name = "ls";
1749 const absl::string_view lin_ls_name = "ls_lin";
1750
1751 const int num_ls_lin =
1752 name_filter.Keep(lin_ls_name) ? num_violation_ls / 3 : 0;
1753 const int num_ls_default =
1754 name_filter.Keep(ls_name) ? num_violation_ls - num_ls_lin : 0;
1755
1756 if (num_ls_default > 0) {
1757 std::shared_ptr<SharedLsStates> states = std::make_shared<SharedLsStates>(
1758 ls_name, params, &shared->stat_tables);
1759 for (int i = 0; i < num_ls_default; ++i) {
1760 SatParameters local_params = params;
1761 local_params.set_random_seed(
1762 CombineSeed(params.random_seed(), interleaved_subsolvers.size()));
1763 local_params.set_feasibility_jump_linearization_level(0);
1764 interleaved_subsolvers.push_back(
1765 std::make_unique<FeasibilityJumpSolver>(
1766 ls_name, SubSolver::INCOMPLETE, get_linear_model(),
1767 local_params, states, shared->time_limit, shared->response,
1768 shared->bounds.get(), shared->stats, &shared->stat_tables));
1769 }
1770 }
1771
1772 if (num_ls_lin > 0) {
1773 std::shared_ptr<SharedLsStates> lin_states =
1774 std::make_shared<SharedLsStates>(lin_ls_name, params,
1775 &shared->stat_tables);
1776 for (int i = 0; i < num_ls_lin; ++i) {
1777 SatParameters local_params = params;
1778 local_params.set_random_seed(
1779 CombineSeed(params.random_seed(), interleaved_subsolvers.size()));
1780 local_params.set_feasibility_jump_linearization_level(2);
1781 interleaved_subsolvers.push_back(
1782 std::make_unique<FeasibilityJumpSolver>(
1783 lin_ls_name, SubSolver::INCOMPLETE, get_linear_model(),
1784 local_params, lin_states, shared->time_limit, shared->response,
1785 shared->bounds.get(), shared->stats, &shared->stat_tables));
1786 }
1787 }
1788 }
1789
1790 // Adds first solution subsolvers.
1791 // We have two kind, either full_worker_subsolvers or feasibility jump ones.
1792 //
1793 // These will be stopped and deleted as soon as the first solution is found,
1794 // leaving the resource for the other subsolvers (if we have an objective).
1795 {
1796 int num_thread_available =
1797 params.num_workers() - static_cast<int>(full_worker_subsolvers.size());
1798
1799 // We reserve 1 thread for all interleaved subsolved that can work without
1800 // a first solution. If we have feasibility jump, because these will be
1801 // interleaved, we don't do that.
1802 if (!params.use_feasibility_jump() &&
1803 num_interleaved_subsolver_that_do_not_need_solution > 0) {
1804 --num_thread_available;
1805 }
1806 num_thread_available = std::max(num_thread_available, 0);
1807
1808 const std::vector<SatParameters> all_params =
1809 RepeatParameters(name_filter.Filter(GetFirstSolutionBaseParams(params)),
1810 num_thread_available);
1811
1812 std::shared_ptr<SharedLsStates> fj_states;
1813 std::shared_ptr<SharedLsStates> fj_lin_states;
1814
1815 // Build the requested subsolvers.
1816 for (const SatParameters& local_params : all_params) {
1817 if (local_params.use_feasibility_jump()) {
1818 // Create the SharedLsStates if not already done.
1819 std::shared_ptr<SharedLsStates> states;
1820 if (local_params.feasibility_jump_linearization_level() == 0) {
1821 if (fj_states == nullptr) {
1822 fj_states = std::make_shared<SharedLsStates>(
1823 local_params.name(), params, &shared->stat_tables);
1824 }
1825 states = fj_states;
1826 } else {
1827 if (fj_lin_states == nullptr) {
1828 fj_lin_states = std::make_shared<SharedLsStates>(
1829 local_params.name(), params, &shared->stat_tables);
1830 }
1831 states = fj_lin_states;
1832 }
1833
1834 interleaved_subsolvers.push_back(
1835 std::make_unique<FeasibilityJumpSolver>(
1836 local_params.name(), SubSolver::FIRST_SOLUTION,
1837 get_linear_model(), local_params, states, shared->time_limit,
1838 shared->response, shared->bounds.get(), shared->stats,
1839 &shared->stat_tables));
1840 } else {
1841 first_solution_full_subsolvers.push_back(
1842 std::make_unique<FullProblemSolver>(
1843 local_params.name(), local_params,
1844 /*split_in_chunks=*/local_params.interleave_search(), shared,
1845 /*stop_on_first_solution=*/true));
1846 }
1847 }
1848 }
1849
1850 // Now that we are done with the logic, move all subsolver into a single
1851 // list. Note that the position of the "synchronization" subsolver matter.
1852 // Some are already in subsolvers, and we will add the gap one last.
1853 const auto move_all =
1854 [&subsolvers](std::vector<std::unique_ptr<SubSolver>>& from) {
1855 for (int i = 0; i < from.size(); ++i) {
1856 subsolvers.push_back(std::move(from[i]));
1857 }
1858 from.clear();
1859 };
1860 move_all(full_worker_subsolvers);
1861 move_all(first_solution_full_subsolvers);
1862 move_all(reentrant_interleaved_subsolvers);
1863 move_all(interleaved_subsolvers);
1864
1865 // Add a synchronization point for the gap integral that is executed last.
1866 // This way, after each batch, the proper deterministic time is updated and
1867 // then the function to integrate take the value of the new gap.
1868 if (shared->model_proto.has_objective() &&
1869 !shared->model_proto.objective().vars().empty()) {
1870 subsolvers.push_back(std::make_unique<SynchronizationPoint>(
1871 "update_gap_integral",
1872 [shared]() { shared->response->UpdateGapIntegral(); }));
1873 }
1874
1875 LaunchSubsolvers(params, shared, subsolvers, name_filter.AllIgnored());
1876}
1877
1878#endif // __PORTABLE_PLATFORM__
1879
1880// If the option use_sat_inprocessing is true, then before post-solving a
1881// solution, we need to make sure we add any new clause required for postsolving
1882// to the mapping_model.
1883void AddPostsolveClauses(const std::vector<int>& postsolve_mapping,
1884 Model* model, CpModelProto* mapping_proto) {
1885 auto* mapping = model->GetOrCreate<CpModelMapping>();
1886 auto* postsolve = model->GetOrCreate<PostsolveClauses>();
1887 for (const auto& clause : postsolve->clauses) {
1888 auto* ct = mapping_proto->add_constraints()->mutable_bool_or();
1889 for (const Literal l : clause) {
1890 int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
1891 CHECK_NE(var, -1);
1892 var = postsolve_mapping[var];
1893 ct->add_literals(l.IsPositive() ? var : NegatedRef(var));
1894 }
1895 }
1896 postsolve->clauses.clear();
1897}
1898
1899bool VarIsFixed(const CpModelProto& model_proto, int i) {
1900 return model_proto.variables(i).domain_size() == 2 &&
1901 model_proto.variables(i).domain(0) ==
1902 model_proto.variables(i).domain(1);
1903}
1904
1905void TestSolutionHintForFeasibility(const CpModelProto& model_proto,
1906 SolverLogger* logger,
1907 SharedResponseManager* manager = nullptr) {
1908 if (!model_proto.has_solution_hint()) return;
1909
1910 int num_active_variables = 0;
1911 int num_hinted_variables = 0;
1912 for (int var = 0; var < model_proto.variables_size(); ++var) {
1913 if (VarIsFixed(model_proto, var)) continue;
1914 ++num_active_variables;
1915 }
1916
1917 for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
1918 const int ref = model_proto.solution_hint().vars(i);
1919 if (VarIsFixed(model_proto, PositiveRef(ref))) continue;
1920 ++num_hinted_variables;
1921 }
1922 CHECK_LE(num_hinted_variables, num_active_variables);
1923
1924 if (num_active_variables != num_hinted_variables) {
1925 SOLVER_LOG(
1926 logger, "The solution hint is incomplete: ", num_hinted_variables,
1927 " out of ", num_active_variables, " non fixed variables hinted.");
1928 return;
1929 }
1930
1931 std::vector<int64_t> solution(model_proto.variables_size(), 0);
1932 // Pre-assign from fixed domains.
1933 for (int var = 0; var < model_proto.variables_size(); ++var) {
1934 if (VarIsFixed(model_proto, var)) {
1935 solution[var] = model_proto.variables(var).domain(0);
1936 }
1937 }
1938
1939 for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
1940 const int ref = model_proto.solution_hint().vars(i);
1941 const int var = PositiveRef(ref);
1942 const int64_t value = model_proto.solution_hint().values(i);
1943 const int64_t hinted_value = RefIsPositive(ref) ? value : -value;
1944 const Domain domain = ReadDomainFromProto(model_proto.variables(var));
1945 if (!domain.Contains(hinted_value)) {
1946 SOLVER_LOG(logger,
1947 "The solution hint is complete but it contains values outside "
1948 "of the domain of the variables.");
1949 return;
1950 }
1951 solution[var] = hinted_value;
1952 }
1953
1954 if (SolutionIsFeasible(model_proto, solution)) {
1955 if (manager != nullptr) {
1956 // Add it to the pool right away! Note that we already have a log in this
1957 // case, so we don't log anything more.
1958 manager->NewSolution(solution, "complete_hint", nullptr);
1959 } else {
1960 SOLVER_LOG(logger, "The solution hint is complete and is feasible.");
1961 }
1962 } else {
1963 // TODO(user): Change the code to make the solution checker more
1964 // informative by returning a message instead of just VLOGing it.
1965 SOLVER_LOG(logger,
1966 "The solution hint is complete, but it is infeasible! we "
1967 "will try to repair it.");
1968 }
1969}
1970
1971} // namespace
1972
1973std::function<void(Model*)> NewFeasibleSolutionObserver(
1974 const std::function<void(const CpSolverResponse& response)>& callback) {
1976 model->GetOrCreate<SharedResponseManager>()->AddSolutionCallback(callback);
1977 };
1978}
1979
1980std::function<void(Model*)> NewFeasibleSolutionLogCallback(
1981 const std::function<std::string(const CpSolverResponse& response)>&
1983 return [callback = callback](Model* model) {
1984 model->GetOrCreate<SharedResponseManager>()->AddLogCallback(callback);
1985 };
1986}
1987
1988std::function<void(Model*)> NewBestBoundCallback(
1989 const std::function<void(double)>& callback) {
1991 model->GetOrCreate<SharedResponseManager>()->AddBestBoundCallback(callback);
1992 };
1993}
1994
1995#if !defined(__PORTABLE_PLATFORM__)
1996// TODO(user): Support it on android.
1997std::function<SatParameters(Model*)> NewSatParameters(
1998 const std::string& params) {
1999 sat::SatParameters parameters;
2000 if (!params.empty()) {
2001 CHECK(google::protobuf::TextFormat::ParseFromString(params, &parameters))
2002 << params;
2003 }
2005}
2006#endif // __PORTABLE_PLATFORM__
2007
2008std::function<SatParameters(Model*)> NewSatParameters(
2009 const sat::SatParameters& parameters) {
2011 // Tricky: It is important to initialize the model parameters before any
2012 // of the solver object are created, so that by default they use the given
2013 // parameters.
2014 //
2015 // TODO(user): A notable exception to this is the TimeLimit which is
2016 // currently not initializing itself from the SatParameters in the model. It
2017 // will also starts counting from the time of its creation. It will be good
2018 // to find a solution that is less error prone.
2019 *model->GetOrCreate<SatParameters>() = parameters;
2020 return parameters;
2021 };
2022}
2023
2024CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
2025 auto* wall_timer = model->GetOrCreate<WallTimer>();
2026 auto* user_timer = model->GetOrCreate<UserTimer>();
2027 wall_timer->Start();
2028 user_timer->Start();
2029
2030#if !defined(__PORTABLE_PLATFORM__)
2031 // Dump initial model?
2032 if (absl::GetFlag(FLAGS_cp_model_dump_models)) {
2033 DumpModelProto(model_proto, "model");
2034 }
2035 if (absl::GetFlag(FLAGS_cp_model_export_model)) {
2036 if (model_proto.name().empty()) {
2037 DumpModelProto(model_proto, "unnamed_model");
2038 } else {
2039 DumpModelProto(model_proto, model_proto.name());
2040 }
2041 }
2042
2043 // Override parameters?
2044 if (!absl::GetFlag(FLAGS_cp_model_params).empty()) {
2045 SatParameters params = *model->GetOrCreate<SatParameters>();
2046 SatParameters flag_params;
2047 CHECK(google::protobuf::TextFormat::ParseFromString(
2048 absl::GetFlag(FLAGS_cp_model_params), &flag_params));
2049 params.MergeFrom(flag_params);
2050 *(model->GetOrCreate<SatParameters>()) = params;
2051 }
2052#endif // __PORTABLE_PLATFORM__
2053
2054 // Enable the logging component.
2055 const SatParameters& params = *model->GetOrCreate<SatParameters>();
2056 SolverLogger* logger = model->GetOrCreate<SolverLogger>();
2057 logger->EnableLogging(params.log_search_progress());
2058 logger->SetLogToStdOut(params.log_to_stdout());
2059 std::string log_string;
2060 if (params.log_to_response()) {
2061 logger->AddInfoLoggingCallback([&log_string](absl::string_view message) {
2062 absl::StrAppend(&log_string, message, "\n");
2063 });
2064 }
2065
2066 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
2067 shared_response_manager->set_dump_prefix(
2068 absl::GetFlag(FLAGS_cp_model_dump_prefix));
2069
2070#if !defined(__PORTABLE_PLATFORM__)
2071 // Note that the postprocessors are executed in reverse order, so this
2072 // will always dump the response just before it is returned since it is
2073 // the first one we register.
2074 if (absl::GetFlag(FLAGS_cp_model_dump_response)) {
2075 shared_response_manager->AddFinalResponsePostprocessor(
2076 [](CpSolverResponse* response) {
2077 const std::string file = absl::StrCat(
2078 absl::GetFlag(FLAGS_cp_model_dump_prefix), "response.pb.txt");
2079 LOG(INFO) << "Dumping response proto to '" << file << "'.";
2080 CHECK(WriteModelProtoToFile(*response, file));
2081 });
2082 }
2083#endif // __PORTABLE_PLATFORM__
2084
2085 // Always display the final response stats if requested.
2086 // This also copy the logs to the response if requested.
2087 shared_response_manager->AddFinalResponsePostprocessor(
2088 [logger, &model_proto, &log_string](CpSolverResponse* response) {
2090 *response,
2091 model_proto.has_objective() ||
2092 model_proto.has_floating_point_objective()));
2093 if (!log_string.empty()) {
2094 response->set_solve_log(log_string);
2095 }
2096 });
2097
2098 // Always add the timing information to a response. Note that it is important
2099 // to add this after the log/dump postprocessor since we execute them in
2100 // reverse order.
2101 auto* shared_time_limit = model->GetOrCreate<ModelSharedTimeLimit>();
2102 shared_response_manager->AddResponsePostprocessor(
2103 [&wall_timer, &user_timer,
2104 &shared_time_limit](CpSolverResponse* response) {
2105 response->set_wall_time(wall_timer->Get());
2106 response->set_user_time(user_timer->Get());
2107 response->set_deterministic_time(
2108 shared_time_limit->GetElapsedDeterministicTime());
2109 });
2110
2111 // Validate parameters.
2112 //
2113 // Note that the few parameters we use before that are Booleans and thus
2114 // "safe". We need to delay the validation to return a proper response.
2115 {
2116 const std::string error = ValidateParameters(params);
2117 if (!error.empty()) {
2118 SOLVER_LOG(logger, "Invalid parameters: ", error);
2119
2120 // TODO(user): We currently reuse the MODEL_INVALID status even though it
2121 // is not the best name for this. Maybe we can add a PARAMETERS_INVALID
2122 // when it become needed. Or rename to INVALID_INPUT ?
2123 CpSolverResponse status_response;
2124 status_response.set_status(CpSolverStatus::MODEL_INVALID);
2125 status_response.set_solution_info(error);
2126 FillSolveStatsInResponse(model, &status_response);
2127 shared_response_manager->AppendResponseToBeMerged(status_response);
2128 return shared_response_manager->GetResponse();
2129 }
2130 }
2131
2132 // Initialize the time limit from the parameters.
2133 model->GetOrCreate<TimeLimit>()->ResetLimitFromParameters(params);
2134
2135#if !defined(__PORTABLE_PLATFORM__)
2136 // Register SIGINT handler if requested by the parameters.
2137 if (params.catch_sigint_signal()) {
2138 model->GetOrCreate<SigintHandler>()->Register(
2139 [&shared_time_limit]() { shared_time_limit->Stop(); });
2140 }
2141#endif // __PORTABLE_PLATFORM__
2142
2143 SOLVER_LOG(logger, "");
2144 SOLVER_LOG(logger, "Starting ", CpSatSolverVersion());
2145 SOLVER_LOG(logger, "Parameters: ", ProtobufShortDebugString(params));
2146
2147 // Internally we adapt the parameters so that things are disabled if
2148 // they do not make sense.
2149 AdaptGlobalParameters(model_proto, model);
2150
2151 if (logger->LoggingIsEnabled() && params.use_absl_random()) {
2152 model->GetOrCreate<ModelRandomGenerator>()->LogSalt();
2153 }
2154
2155 // Validate model_proto.
2156 // TODO(user): provide an option to skip this step for speed?
2157 {
2158 const std::string error = ValidateInputCpModel(params, model_proto);
2159 if (!error.empty()) {
2160 SOLVER_LOG(logger, "Invalid model: ", error);
2161 CpSolverResponse status_response;
2162 status_response.set_status(CpSolverStatus::MODEL_INVALID);
2163 status_response.set_solution_info(error);
2164 FillSolveStatsInResponse(model, &status_response);
2165 shared_response_manager->AppendResponseToBeMerged(status_response);
2166 return shared_response_manager->GetResponse();
2167 }
2168 }
2169
2170 SOLVER_LOG(logger, "");
2171 SOLVER_LOG(logger, "Initial ", CpModelStats(model_proto));
2172
2173 // Presolve and expansions.
2174 SOLVER_LOG(logger, "");
2175 SOLVER_LOG(logger,
2176 absl::StrFormat("Starting presolve at %.2fs", wall_timer->Get()));
2177
2178 // Note: Allocating in an arena significantly speed up destruction (free) for
2179 // large messages.
2180 google::protobuf::Arena arena;
2181 CpModelProto* new_cp_model_proto =
2182 google::protobuf::Arena::Create<CpModelProto>(&arena);
2183 CpModelProto* mapping_proto =
2184 google::protobuf::Arena::Create<CpModelProto>(&arena);
2185 auto context = std::make_unique<PresolveContext>(model, new_cp_model_proto,
2186 mapping_proto);
2187
2188 if (absl::GetFlag(FLAGS_debug_model_copy)) {
2189 *new_cp_model_proto = model_proto;
2190 } else if (!ImportModelWithBasicPresolveIntoContext(model_proto,
2191 context.get())) {
2192 const std::string info = "Problem proven infeasible during initial copy.";
2193 SOLVER_LOG(logger, info);
2194 CpSolverResponse status_response;
2195 status_response.set_status(CpSolverStatus::INFEASIBLE);
2196 status_response.set_solution_info(info);
2197 shared_response_manager->AppendResponseToBeMerged(status_response);
2198 return shared_response_manager->GetResponse();
2199 }
2200
2201 if (absl::GetFlag(FLAGS_cp_model_ignore_objective) &&
2202 (context->working_model->has_objective() ||
2203 context->working_model->has_floating_point_objective())) {
2204 SOLVER_LOG(logger, "Ignoring objective");
2205 context->working_model->clear_objective();
2206 context->working_model->clear_floating_point_objective();
2207 }
2208
2209 if (absl::GetFlag(FLAGS_cp_model_ignore_hints) &&
2210 context->working_model->has_solution_hint()) {
2211 SOLVER_LOG(logger, "Ignoring solution hint");
2212 context->working_model->clear_solution_hint();
2213 }
2214
2215 // Checks for hints early in case they are forced to be hard constraints.
2216 if (params.fix_variables_to_their_hinted_value() &&
2217 model_proto.has_solution_hint()) {
2218 SOLVER_LOG(logger, "Fixing ", model_proto.solution_hint().vars().size(),
2219 " variables to their value in the solution hints.");
2220 for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
2221 const int var = model_proto.solution_hint().vars(i);
2222 const int64_t value = model_proto.solution_hint().values(i);
2223 if (!context->IntersectDomainWith(var, Domain(value))) {
2224 const IntegerVariableProto& var_proto =
2225 context->working_model->variables(var);
2226 const std::string var_name = var_proto.name().empty()
2227 ? absl::StrCat("var(", var, ")")
2228 : var_proto.name();
2229
2230 const Domain var_domain = ReadDomainFromProto(var_proto);
2231 SOLVER_LOG(logger, "Hint found infeasible when assigning variable '",
2232 var_name, "' with domain", var_domain.ToString(),
2233 " the value ", value);
2234 break;
2235 }
2236 }
2237 }
2238
2239 // If the hint is complete, we can use the solution checker to do more
2240 // validation. Note that after the model has been validated, we are sure there
2241 // are do duplicate variables in the solution hint, so we can just check the
2242 // size.
2243 if (!context->ModelIsUnsat()) {
2244 TestSolutionHintForFeasibility(model_proto, logger);
2245 }
2246
2247 // If the objective was a floating point one, do some postprocessing on the
2248 // final response.
2249 if (model_proto.has_floating_point_objective()) {
2250 shared_response_manager->AddFinalResponsePostprocessor(
2251 [&params, &model_proto, mapping_proto,
2252 &logger](CpSolverResponse* response) {
2253 if (response->solution().empty()) return;
2254
2255 // Compute the true objective of the best returned solution.
2256 const auto& float_obj = model_proto.floating_point_objective();
2257 double value = float_obj.offset();
2258 const int num_terms = float_obj.vars().size();
2259 for (int i = 0; i < num_terms; ++i) {
2260 value += float_obj.coeffs(i) *
2261 static_cast<double>(response->solution(float_obj.vars(i)));
2262 }
2263 response->set_objective_value(value);
2264
2265 // Also copy the scaled objective which must be in the mapping model.
2266 // This can be useful for some client, like if they want to do
2267 // multi-objective optimization in stages.
2268 if (!mapping_proto->has_objective()) return;
2269 const CpObjectiveProto& integer_obj = mapping_proto->objective();
2270 *response->mutable_integer_objective() = integer_obj;
2271
2272 // If requested, compute a correct lb from the one on the integer
2273 // objective. We only do that if some error were introduced by the
2274 // scaling algorithm.
2275 if (params.mip_compute_true_objective_bound() &&
2276 !integer_obj.scaling_was_exact()) {
2277 const int64_t integer_lb = response->inner_objective_lower_bound();
2278 const double lb = ComputeTrueObjectiveLowerBound(
2279 model_proto, integer_obj, integer_lb);
2280 SOLVER_LOG(logger, "[Scaling] scaled_objective_bound: ",
2281 response->best_objective_bound(),
2282 " corrected_bound: ", lb,
2283 " delta: ", response->best_objective_bound() - lb);
2284
2285 // To avoid small errors that can be confusing, we take the
2286 // min/max with the objective value.
2287 if (float_obj.maximize()) {
2288 response->set_best_objective_bound(
2289 std::max(lb, response->objective_value()));
2290 } else {
2291 response->set_best_objective_bound(
2292 std::min(lb, response->objective_value()));
2293 }
2294 }
2295
2296 // Check the absolute gap, and display warning if needed.
2297 // TODO(user): Change status to IMPRECISE?
2298 if (response->status() == CpSolverStatus::OPTIMAL) {
2299 const double gap = std::abs(response->objective_value() -
2300 response->best_objective_bound());
2301 if (gap > params.absolute_gap_limit()) {
2302 SOLVER_LOG(logger,
2303 "[Scaling] Warning: OPTIMAL was reported, yet the "
2304 "objective gap (",
2305 gap, ") is greater than requested absolute limit (",
2306 params.absolute_gap_limit(), ").");
2307 }
2308 }
2309 });
2310 }
2311
2312 if (!model_proto.assumptions().empty() &&
2313 (params.num_workers() > 1 || model_proto.has_objective() ||
2314 model_proto.has_floating_point_objective() ||
2315 params.enumerate_all_solutions())) {
2316 SOLVER_LOG(
2317 logger,
2318 "Warning: solving with assumptions was requested in a non-fully "
2319 "supported setting.\nWe will assumes these assumptions true while "
2320 "solving, but if the model is infeasible, you will not get a useful "
2321 "'sufficient_assumptions_for_infeasibility' field in the response, it "
2322 "will include all assumptions.");
2323
2324 // For the case where the assumptions are currently not supported, we just
2325 // assume they are fixed, and will always report all of them in the UNSAT
2326 // core if the problem turn out to be UNSAT.
2327 //
2328 // If the mode is not degraded, we will hopefully report a small subset
2329 // in case there is no feasible solution under these assumptions.
2330 shared_response_manager->AddFinalResponsePostprocessor(
2331 [&model_proto](CpSolverResponse* response) {
2332 if (response->status() != CpSolverStatus::INFEASIBLE) return;
2333
2334 // For now, just pass in all assumptions.
2335 *response->mutable_sufficient_assumptions_for_infeasibility() =
2336 model_proto.assumptions();
2337 });
2338
2339 // Clear them from the new proto.
2340 new_cp_model_proto->clear_assumptions();
2341
2342 context->InitializeNewDomains();
2343 for (const int ref : model_proto.assumptions()) {
2344 if (!context->SetLiteralToTrue(ref)) {
2345 CpSolverResponse status_response;
2346 status_response.set_status(CpSolverStatus::INFEASIBLE);
2347 status_response.add_sufficient_assumptions_for_infeasibility(ref);
2348 FillSolveStatsInResponse(model, &status_response);
2349 shared_response_manager->AppendResponseToBeMerged(status_response);
2350 return shared_response_manager->GetResponse();
2351 }
2352 }
2353 }
2354
2355 // Do the actual presolve.
2356 std::vector<int> postsolve_mapping;
2357 const CpSolverStatus presolve_status =
2358 PresolveCpModel(context.get(), &postsolve_mapping);
2359
2360 // Delete the context as soon a the presolve is done. Note that only
2361 // postsolve_mapping and mapping_proto are needed for postsolve.
2362 context.reset(nullptr);
2363
2364 if (presolve_status != CpSolverStatus::UNKNOWN) {
2365 SOLVER_LOG(logger, "Problem closed by presolve.");
2366 CpSolverResponse status_response;
2367 status_response.set_status(presolve_status);
2368 FillSolveStatsInResponse(model, &status_response);
2369 shared_response_manager->AppendResponseToBeMerged(status_response);
2370 return shared_response_manager->GetResponse();
2371 }
2372
2373 SOLVER_LOG(logger, "");
2374 SOLVER_LOG(logger, "Presolved ", CpModelStats(*new_cp_model_proto));
2375
2376 // TODO(user): reduce this function size and find a better place for this?
2377 SharedClasses shared(new_cp_model_proto, model);
2378
2379 if (params.fill_tightened_domains_in_response()) {
2380 shared_response_manager->AddResponsePostprocessor(
2381 [&model_proto, new_cp_model_proto, mapping_proto, &postsolve_mapping,
2382 logger, &shared](CpSolverResponse* response) {
2383 // Collect the info we know about new_cp_model_proto bounds.
2384 // Note that this is not really needed as we should have the same
2385 // information in the mapping_proto.
2386 std::vector<Domain> bounds;
2387 for (const IntegerVariableProto& vars :
2388 new_cp_model_proto->variables()) {
2389 bounds.push_back(ReadDomainFromProto(vars));
2390 }
2391
2392 // Intersect with the SharedBoundsManager if it exist.
2393 if (shared.bounds != nullptr) {
2394 shared.bounds->UpdateDomains(&bounds);
2395 }
2396
2397 // Postsolve and fill the field.
2398 FillTightenedDomainInResponse(model_proto, *mapping_proto,
2399 postsolve_mapping, bounds, response,
2400 logger);
2401 });
2402 }
2403
2404 // Solution checking.
2405 // We either check all solutions, or only the last one.
2406 // Checking all solution might be expensive if we creates many.
2407 auto check_solution = [&model_proto, &params, mapping_proto,
2408 &postsolve_mapping](CpSolverResponse* response) {
2409 if (response->solution().empty()) return;
2410 if (params.cp_model_presolve()) {
2411 // We pass presolve data for more informative message in case the solution
2412 // is not feasible.
2413 CHECK(SolutionIsFeasible(model_proto, response->solution(), mapping_proto,
2414 &postsolve_mapping));
2415 } else {
2416 CHECK(SolutionIsFeasible(model_proto, response->solution()));
2417 }
2418 };
2419 if (DEBUG_MODE ||
2420 absl::GetFlag(FLAGS_cp_model_check_intermediate_solutions)) {
2421 shared_response_manager->AddResponsePostprocessor(
2422 std::move(check_solution));
2423 } else {
2424 shared_response_manager->AddFinalResponsePostprocessor(
2425 std::move(check_solution));
2426 }
2427
2428 // Solution postsolving.
2429 if (params.cp_model_presolve()) {
2430 shared_response_manager->AddSolutionPostprocessor(
2431 [&model_proto, &params, mapping_proto, &model,
2432 &postsolve_mapping](std::vector<int64_t>* solution) {
2433 AddPostsolveClauses(postsolve_mapping, model, mapping_proto);
2434 PostsolveResponseWrapper(params, model_proto.variables_size(),
2435 *mapping_proto, postsolve_mapping, solution);
2436 });
2437 shared_response_manager->AddResponsePostprocessor(
2438 [&postsolve_mapping](CpSolverResponse* response) {
2439 // Map back the sufficient assumptions for infeasibility.
2440 for (int& ref :
2441 *(response
2442 ->mutable_sufficient_assumptions_for_infeasibility())) {
2443 ref = RefIsPositive(ref)
2444 ? postsolve_mapping[ref]
2445 : NegatedRef(postsolve_mapping[PositiveRef(ref)]);
2446 }
2447 });
2448 } else {
2449 shared_response_manager->AddResponsePostprocessor(
2450 [&model_proto](CpSolverResponse* response) {
2451 // Truncate the solution in case model expansion added more variables.
2452 const int initial_size = model_proto.variables_size();
2453 if (!response->solution().empty()) {
2454 response->mutable_solution()->Truncate(initial_size);
2455 }
2456 });
2457 }
2458
2459 // Make sure everything stops when we have a first solution if requested.
2460 if (params.stop_after_first_solution()) {
2461 shared_response_manager->AddSolutionCallback(
2462 [shared_time_limit](const CpSolverResponse&) {
2463 shared_time_limit->Stop();
2464 });
2465 }
2466
2467#if !defined(__PORTABLE_PLATFORM__)
2468 if (absl::GetFlag(FLAGS_cp_model_dump_models)) {
2469 DumpModelProto(*new_cp_model_proto, "presolved_model");
2470 DumpModelProto(*mapping_proto, "mapping_model");
2471
2472 // If the model is convertible to a MIP, we dump it too.
2473 //
2474 // TODO(user): We could try to dump our linear relaxation too.
2475 MPModelProto mip_model;
2476 if (ConvertCpModelProtoToMPModelProto(*new_cp_model_proto, &mip_model)) {
2477 DumpModelProto(mip_model, "presolved_mp_model");
2478 }
2479
2480 // If the model is convertible to a pure SAT one, we dump it too.
2481 std::string cnf_string;
2482 if (ConvertCpModelProtoToCnf(*new_cp_model_proto, &cnf_string)) {
2483 const std::string filename = absl::StrCat(
2484 absl::GetFlag(FLAGS_cp_model_dump_prefix), "presolved_cnf_model.cnf");
2485 LOG(INFO) << "Dumping cnf model to '" << filename << "'.";
2486 const absl::Status status =
2487 file::SetContents(filename, cnf_string, file::Defaults());
2488 if (!status.ok()) LOG(ERROR) << status;
2489 }
2490 }
2491#endif // __PORTABLE_PLATFORM__
2492
2493 if (params.stop_after_presolve() || shared_time_limit->LimitReached()) {
2494 int64_t num_terms = 0;
2495 for (const ConstraintProto& ct : new_cp_model_proto->constraints()) {
2496 num_terms += static_cast<int>(UsedVariables(ct).size());
2497 }
2498 SOLVER_LOG(
2499 logger, "Stopped after presolve.",
2500 "\nPresolvedNumVariables: ", new_cp_model_proto->variables().size(),
2501 "\nPresolvedNumConstraints: ", new_cp_model_proto->constraints().size(),
2502 "\nPresolvedNumTerms: ", num_terms);
2503
2504 CpSolverResponse status_response;
2505 FillSolveStatsInResponse(model, &status_response);
2506 shared_response_manager->AppendResponseToBeMerged(status_response);
2507 return shared_response_manager->GetResponse();
2508 }
2509
2510 SOLVER_LOG(logger, "");
2511 SOLVER_LOG(logger, "Preloading model.");
2512
2513 // If specified, we load the initial objective domain right away in the
2514 // response manager. Note that the presolve will always fill it with the
2515 // trivial min/max value if the user left it empty. This avoids to display
2516 // [-infinity, infinity] for the initial objective search space.
2517 if (new_cp_model_proto->has_objective()) {
2518 shared_response_manager->InitializeObjective(*new_cp_model_proto);
2519 shared_response_manager->SetGapLimitsFromParameters(params);
2520 }
2521
2522 // Start counting the primal integral from the current deterministic time and
2523 // initial objective domain gap that we just filled.
2524 shared_response_manager->UpdateGapIntegral();
2525
2526 // Re-test a complete solution hint to see if it survived the presolve.
2527 // If it is feasible, we load it right away.
2528 //
2529 // Tricky: when we enumerate all solutions, we cannot properly exclude the
2530 // current solution if we didn't find it via full propagation, so we don't
2531 // load it in this case.
2532 //
2533 // TODO(user): Even for an optimization, if we load the solution right away,
2534 // we might not have the same behavior as the initial search that follow the
2535 // hint will be infeasible, so the activities of the variables will be
2536 // different.
2537 if (!params.enumerate_all_solutions()) {
2538 TestSolutionHintForFeasibility(*new_cp_model_proto, logger,
2539 shared_response_manager);
2540 } else {
2541 TestSolutionHintForFeasibility(*new_cp_model_proto, logger, nullptr);
2542 }
2543
2544 if (params.symmetry_level() > 1) {
2545 DetectAndAddSymmetryToProto(params, new_cp_model_proto, logger);
2546 }
2547
2548 LoadDebugSolution(*new_cp_model_proto, model);
2549
2550 if (!model->GetOrCreate<TimeLimit>()->LimitReached()) {
2551#if defined(__PORTABLE_PLATFORM__)
2552 if (/* DISABLES CODE */ (false)) {
2553 // We ignore the multithreading parameter in this case.
2554#else // __PORTABLE_PLATFORM__
2555 if (params.num_workers() > 1 || params.interleave_search() ||
2556 !params.subsolvers().empty() || params.use_ls_only()) {
2557 SolveCpModelParallel(&shared, model);
2558#endif // __PORTABLE_PLATFORM__
2559 } else {
2560 shared_response_manager->SetUpdateGapIntegralOnEachChange(true);
2561
2562 // To avoid duplicating code, the single-thread version reuse most of
2563 // the multi-thread architecture.
2564 std::vector<std::unique_ptr<SubSolver>> subsolvers;
2565 subsolvers.push_back(std::make_unique<FullProblemSolver>(
2566 "main", params, /*split_in_chunks=*/false, &shared));
2567 LaunchSubsolvers(params, &shared, subsolvers, {});
2568 }
2569 }
2570
2571 return shared_response_manager->GetResponse();
2572}
2573
2574CpSolverResponse Solve(const CpModelProto& model_proto) {
2575 Model model;
2576 return SolveCpModel(model_proto, &model);
2577}
2578
2579CpSolverResponse SolveWithParameters(const CpModelProto& model_proto,
2580 const SatParameters& params) {
2582 model.Add(NewSatParameters(params));
2583 return SolveCpModel(model_proto, &model);
2584}
2585
2586#if !defined(__PORTABLE_PLATFORM__)
2587CpSolverResponse SolveWithParameters(const CpModelProto& model_proto,
2588 const std::string& params) {
2590 model.Add(NewSatParameters(params));
2591 return SolveCpModel(model_proto, &model);
2592}
2593#endif // !__PORTABLE_PLATFORM__
2594
2595void LoadAndSolveCpModelForTest(const CpModelProto& model_proto, Model* model) {
2596 model->GetOrCreate<SharedResponseManager>()->InitializeObjective(model_proto);
2597 LoadCpModel(model_proto, model);
2598 SolveLoadedCpModel(model_proto, model);
2599}
2600
2601} // namespace sat
2602} // namespace operations_research
IntegerValue size
int64_t max
int64_t min
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
T Add(std::function< T(Model *)> f)
Definition model.h:89
SatParameters parameters
CpModelProto proto
The output proto.
ABSL_FLAG(bool, cp_model_export_model, false, "DEBUG ONLY. When set to true, SolveCpModel() will dump its input model " "protos in text format to 'FLAGS_cp_model_dump_prefix'{name}.pb.txt.")
const std::string name
A name for logging purposes.
const Constraint * ct
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
GurobiMPCallbackContext * context
MPCallback * callback
int lit
time_limit
Definition solve.cc:22
const bool DEBUG_MODE
Definition macros.h:24
double solution
Definition file.cc:169
absl::Status SetContents(absl::string_view filename, absl::string_view contents, Options options)
Definition file.cc:242
Options Defaults()
Definition file.h:109
CpSolverResponse Solve(const CpModelProto &model_proto)
Solves the given CpModelProto and returns an instance of CpSolverResponse.
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
void SolveLoadedCpModel(const CpModelProto &model_proto, Model *model)
constexpr uint64_t kDefaultFingerprintSeed
Default seed for fingerprints.
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
std::string ValidateInputCpModel(const SatParameters &params, const CpModelProto &model)
void FillTightenedDomainInResponse(const CpModelProto &original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, const std::vector< Domain > &search_domains, CpSolverResponse *response, SolverLogger *logger)
void LoadAndSolveCpModelForTest(const CpModelProto &model_proto, Model *model)
std::string CpModelStats(const CpModelProto &model_proto)
Returns a string with some statistics on the given CpModelProto.
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, absl::Span< const int64_t > solution)
std::vector< SatParameters > GetFullWorkerParameters(const SatParameters &base_params, const CpModelProto &cp_model, int num_already_present, SubsolverNameFilter *filter)
void FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
int CombineSeed(int base_seed, int64_t delta)
We assume delta >= 0 and we only use the low bit of delta.
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
void PostsolveResponseWrapper(const SatParameters &params, int num_variable_in_original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, std::vector< int64_t > *solution)
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
void RegisterClausesExport(int id, SharedClausesManager *shared_clauses_manager, Model *model)
Registers a callback that will export good clauses discovered during search.
void MinimizeL1DistanceWithHint(const CpModelProto &model_proto, Model *model)
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
Scales back a objective value to a double value from the original model.
bool ConvertCpModelProtoToCnf(const CpModelProto &cp_model, std::string *out)
uint64_t FingerprintModel(const CpModelProto &model, uint64_t seed)
Returns a stable fingerprint of a model.
void NonDeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, const int num_threads)
Definition subsolver.cc:187
void DeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, int num_threads, int batch_size, int max_num_batches)
Definition subsolver.cc:120
std::string ValidateParameters(const SatParameters &params)
std::vector< int > UsedVariables(const ConstraintProto &ct)
void LoadFeasibilityPump(const CpModelProto &model_proto, Model *model)
std::string CpSatSolverVersion()
Returns a string that describes the version of the solver.
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Definition util.cc:49
std::vector< SatParameters > GetFirstSolutionBaseParams(const SatParameters &base_params)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
void AdaptGlobalParameters(const CpModelProto &model_proto, Model *model)
std::function< void(Model *)> NewBestBoundCallback(const std::function< void(double)> &callback)
CpSolverResponse SolveWithParameters(const CpModelProto &model_proto, const SatParameters &params)
Solves the given CpModelProto with the given parameters.
void DetectAndAddSymmetryToProto(const SatParameters &params, CpModelProto *proto, SolverLogger *logger)
Detects symmetries and fill the symmetry field.
std::function< void(Model *)> NewFeasibleSolutionLogCallback(const std::function< std::string(const CpSolverResponse &response)> &callback)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
void QuickSolveWithHint(const CpModelProto &model_proto, Model *model)
std::vector< SatParameters > RepeatParameters(absl::Span< const SatParameters > base_params, int num_params_to_generate)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
Copies the non constraint, non variables part of the model.
absl::string_view ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
void LoadDebugSolution(const CpModelProto &model_proto, Model *model)
double ComputeTrueObjectiveLowerBound(const CpModelProto &model_proto_with_floating_point_objective, const CpObjectiveProto &integer_objective, const int64_t inner_integer_objective_lower_bound)
Definition lp_utils.cc:1703
bool ConvertCpModelProtoToMPModelProto(const CpModelProto &input, MPModelProto *output)
Definition lp_utils.cc:1150
void RegisterVariableBoundsLevelZeroExport(const CpModelProto &, SharedBoundsManager *shared_bounds_manager, Model *model)
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &callback)
void RegisterVariableBoundsLevelZeroImport(const CpModelProto &model_proto, SharedBoundsManager *shared_bounds_manager, Model *model)
void LoadCpModel(const CpModelProto &model_proto, Model *model)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
absl::flat_hash_map< std::string, SatParameters > GetNamedParameters(SatParameters base_params)
int RegisterClausesLevelZeroImport(int id, SharedClausesManager *shared_clauses_manager, Model *model)
std::string CpSolverResponseStats(const CpSolverResponse &response, bool has_objective)
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
Definition util.cc:77
In SWIG mode, we don't want anything besides these top-level includes.
std::string OrToolsVersionString()
Definition version.cc:28
std::mt19937_64 random_engine_t
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:50
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:41
absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
STL namespace.
if(!yyg->yy_init)
Definition parser.yy.cc:965
static int input(yyscan_t yyscanner)
IntervalVar * interval
Definition resource.cc:101
std::string message
Definition trace.cc:397
#define SOLVER_LOG(logger,...)
Definition logging.h:109