35#if !defined(__PORTABLE_PLATFORM__)
39#include "absl/base/thread_annotations.h"
40#include "absl/container/btree_map.h"
41#include "absl/container/btree_set.h"
42#include "absl/container/flat_hash_map.h"
43#include "absl/flags/flag.h"
44#include "absl/log/check.h"
45#include "absl/random/distributions.h"
46#include "absl/strings/str_cat.h"
47#include "absl/strings/str_format.h"
48#include "absl/strings/str_join.h"
49#include "absl/strings/string_view.h"
50#include "absl/strings/strip.h"
51#include "absl/synchronization/mutex.h"
52#include "absl/types/span.h"
53#include "google/protobuf/arena.h"
54#include "google/protobuf/text_format.h"
58#include "ortools/sat/cp_model.pb.h"
82#include "ortools/sat/sat_parameters.pb.h"
92#if !defined(__PORTABLE_PLATFORM__)
100 bool, cp_model_export_model,
false,
101 "DEBUG ONLY. When set to true, SolveCpModel() will dump its input model "
102 "protos in text format to 'FLAGS_cp_model_dump_prefix'{name}.pb.txt.");
105 "DEBUG ONLY, dump models in text proto instead of binary proto.");
108 bool, cp_model_dump_problematic_lns,
false,
109 "DEBUG ONLY. Similar to --cp_model_dump_submodels, but only dump fragment "
110 "for which we got an issue while validating the postsolved solution. This "
111 "allows to debug presolve issues without dumping all the models.");
114 "DEBUG ONLY. If true, the final response of each solve will be "
115 "dumped to 'FLAGS_cp_model_dump_prefix'response.pb.txt");
118 "This is interpreted as a text SatParameters proto. The "
119 "specified fields will override the normal ones for all solves.");
122 "If true, copy the input model as if with no basic presolve");
125 "If true, ignore the objective.");
127 "If true, ignore any supplied hints.");
128ABSL_FLAG(
bool, cp_model_fingerprint_model,
true,
"Fingerprint the model.");
130ABSL_FLAG(
bool, cp_model_check_intermediate_solutions,
false,
131 "When true, all intermediate solutions found by the solver will be "
132 "checked. This can be expensive, therefore it is off by default.");
144std::string Summarize(absl::string_view
input) {
145 if (
input.size() < 105)
return std::string(
input);
147 return absl::StrCat(
input.substr(0, half),
" ... ",
152void DumpModelProto(
const M& proto, absl::string_view name) {
153 std::string filename;
154 if (absl::GetFlag(FLAGS_cp_model_dump_text_proto)) {
155 filename = absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix), name,
157 LOG(INFO) <<
"Dumping " << name <<
" text proto to '" << filename <<
"'.";
159 const std::string filename =
160 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix), name,
".bin");
161 LOG(INFO) <<
"Dumping " << name <<
" binary proto to '" << filename <<
"'.";
166IntegerValue ExprMin(
const LinearExpressionProto& expr,
167 const CpModelProto& model) {
168 IntegerValue result = expr.offset();
169 for (
int i = 0;
i < expr.vars_size(); ++
i) {
170 const IntegerVariableProto& var_proto = model.variables(expr.vars(
i));
171 if (expr.coeffs(
i) > 0) {
172 result += expr.coeffs(
i) * var_proto.domain(0);
174 result += expr.coeffs(
i) * var_proto.domain(var_proto.domain_size() - 1);
180IntegerValue ExprMax(
const LinearExpressionProto& expr,
181 const CpModelProto& model) {
182 IntegerValue result = expr.offset();
183 for (
int i = 0;
i < expr.vars_size(); ++
i) {
184 const IntegerVariableProto& var_proto = model.variables(expr.vars(
i));
185 if (expr.coeffs(
i) > 0) {
186 result += expr.coeffs(
i) * var_proto.domain(var_proto.domain_size() - 1);
188 result += expr.coeffs(
i) * var_proto.domain(0);
194void DumpNoOverlap2dProblem(
const ConstraintProto& ct,
195 const CpModelProto& model_proto) {
196 std::vector<RectangleInRange> non_fixed_boxes;
197 std::vector<Rectangle> fixed_boxes;
198 for (
int i = 0;
i < ct.no_overlap_2d().x_intervals_size(); ++
i) {
199 const int x_interval = ct.no_overlap_2d().x_intervals(
i);
200 const int y_interval = ct.no_overlap_2d().y_intervals(
i);
202 const ConstraintProto& x_ct = model_proto.constraints(x_interval);
203 const ConstraintProto& y_ct = model_proto.constraints(y_interval);
209 .x_min = ExprMin(x_ct.interval().start(), model_proto),
210 .x_max = ExprMax(x_ct.interval().end(), model_proto),
211 .y_min = ExprMin(y_ct.interval().start(), model_proto),
212 .y_max = ExprMax(y_ct.interval().end(), model_proto),
215 .x_size = ExprMin(x_ct.interval().size(), model_proto),
216 .y_size = ExprMin(y_ct.interval().size(), model_proto)};
217 if (box.x_size == box.bounding_area.SizeX() &&
218 box.y_size == box.bounding_area.SizeY()) {
219 fixed_boxes.push_back(box.bounding_area);
221 non_fixed_boxes.push_back(box);
224 VLOG(2) <<
"NoOverlap2D with " << fixed_boxes.size() <<
" fixed boxes and "
225 << non_fixed_boxes.size() <<
" non-fixed boxes.";
227 Rectangle bounding_box = non_fixed_boxes.front().bounding_area;
229 bounding_box.GrowToInclude(r.bounding_area);
231 VLOG(3) <<
"Fixed boxes: " <<
RenderDot(bounding_box, fixed_boxes);
232 std::vector<Rectangle> non_fixed_boxes_to_render;
233 for (
const auto& r : non_fixed_boxes) {
234 non_fixed_boxes_to_render.push_back(r.bounding_area);
236 VLOG(3) <<
"Non-fixed boxes: "
237 <<
RenderDot(bounding_box, non_fixed_boxes_to_render);
238 VLOG(3) <<
"BB: " << bounding_box
239 <<
" non-fixed boxes: " << absl::StrJoin(non_fixed_boxes,
", ");
240 VLOG(3) <<
"BB size: " << bounding_box.SizeX() <<
"x" << bounding_box.SizeY()
241 <<
" non-fixed boxes sizes: "
242 << absl::StrJoin(non_fixed_boxes,
", ",
244 absl::StrAppend(out, r.bounding_area.SizeX(),
"x",
245 r.bounding_area.SizeY());
247 std::vector<Rectangle> sizes_to_render;
248 IntegerValue
x = bounding_box.x_min;
251 for (
const auto& r : non_fixed_boxes) {
253 .x_min =
x, .x_max =
x + r.x_size, .y_min = y, .y_max = y + r.y_size});
255 if (x > bounding_box.x_max) {
261 VLOG(3) <<
"Sizes: " <<
RenderDot(bounding_box, sizes_to_render);
270std::string
CpModelStats(
const CpModelProto& model_proto) {
273 absl::flat_hash_map<char const*, int> name_to_num_constraints;
274 absl::flat_hash_map<char const*, int> name_to_num_reified;
275 absl::flat_hash_map<char const*, int> name_to_num_multi_reified;
276 absl::flat_hash_map<char const*, int> name_to_num_literals;
277 absl::flat_hash_map<char const*, int> name_to_num_terms;
278 absl::flat_hash_map<char const*, int> name_to_num_complex_domain;
279 absl::flat_hash_map<char const*, int> name_to_num_expressions;
281 int no_overlap_2d_num_rectangles = 0;
282 int no_overlap_2d_num_optional_rectangles = 0;
283 int no_overlap_2d_num_linear_areas = 0;
284 int no_overlap_2d_num_quadratic_areas = 0;
285 int no_overlap_2d_num_fixed_rectangles = 0;
287 int cumulative_num_intervals = 0;
288 int cumulative_num_optional_intervals = 0;
289 int cumulative_num_variable_sizes = 0;
290 int cumulative_num_variable_demands = 0;
291 int cumulative_num_fixed_intervals = 0;
293 int no_overlap_num_intervals = 0;
294 int no_overlap_num_optional_intervals = 0;
295 int no_overlap_num_variable_sizes = 0;
296 int no_overlap_num_fixed_intervals = 0;
298 int num_fixed_intervals = 0;
300 for (
const ConstraintProto& ct : model_proto.constraints()) {
304 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
305 if (ct.linear().vars_size() == 0) name =
"kLinear0";
306 if (ct.linear().vars_size() == 1) name =
"kLinear1";
307 if (ct.linear().vars_size() == 2) name =
"kLinear2";
308 if (ct.linear().vars_size() == 3) name =
"kLinear3";
309 if (ct.linear().vars_size() > 3) name =
"kLinearN";
314 name_to_num_constraints[name]++;
315 if (!ct.enforcement_literal().empty()) {
316 name_to_num_reified[name]++;
317 if (ct.enforcement_literal().size() > 1) {
318 name_to_num_multi_reified[name]++;
322 auto variable_is_fixed = [&model_proto](
int ref) {
323 const IntegerVariableProto& proto =
325 return proto.domain_size() == 2 && proto.domain(0) == proto.domain(1);
328 auto expression_is_fixed =
329 [&variable_is_fixed](
const LinearExpressionProto& expr) {
330 for (
const int ref : expr.vars()) {
331 if (!variable_is_fixed(ref)) {
338 auto interval_has_fixed_size = [&model_proto, &expression_is_fixed](
int c) {
339 return expression_is_fixed(model_proto.constraints(c).interval().size());
342 auto constraint_is_optional = [&model_proto](
int i) {
343 return !model_proto.constraints(
i).enforcement_literal().empty();
346 auto interval_is_fixed = [&variable_is_fixed,
347 expression_is_fixed](
const ConstraintProto& ct) {
348 if (ct.constraint_case() != ConstraintProto::ConstraintCase::kInterval) {
351 for (
const int lit : ct.enforcement_literal()) {
352 if (!variable_is_fixed(lit))
return false;
354 return (expression_is_fixed(ct.interval().start()) &&
355 expression_is_fixed(ct.interval().size()) &&
356 expression_is_fixed(ct.interval().end()));
361 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
362 name_to_num_literals[name] += ct.bool_or().literals().size();
363 }
else if (ct.constraint_case() ==
364 ConstraintProto::ConstraintCase::kBoolAnd) {
365 name_to_num_literals[name] +=
366 ct.enforcement_literal().size() + ct.bool_and().literals().size();
367 }
else if (ct.constraint_case() ==
368 ConstraintProto::ConstraintCase::kAtMostOne) {
369 name_to_num_literals[name] += ct.at_most_one().literals().size();
370 }
else if (ct.constraint_case() ==
371 ConstraintProto::ConstraintCase::kExactlyOne) {
372 name_to_num_literals[name] += ct.exactly_one().literals().size();
373 }
else if (ct.constraint_case() ==
374 ConstraintProto::ConstraintCase::kLinMax) {
375 name_to_num_expressions[name] += ct.lin_max().exprs().size();
376 }
else if (ct.constraint_case() ==
377 ConstraintProto::ConstraintCase::kInterval) {
378 if (interval_is_fixed(ct)) num_fixed_intervals++;
379 }
else if (ct.constraint_case() ==
380 ConstraintProto::ConstraintCase::kNoOverlap2D) {
381 const int num_boxes = ct.no_overlap_2d().x_intervals_size();
382 no_overlap_2d_num_rectangles += num_boxes;
383 for (
int i = 0;
i < num_boxes; ++
i) {
384 const int x_interval = ct.no_overlap_2d().x_intervals(
i);
385 const int y_interval = ct.no_overlap_2d().y_intervals(
i);
386 if (constraint_is_optional(x_interval) ||
387 constraint_is_optional(y_interval)) {
388 no_overlap_2d_num_optional_rectangles++;
390 const int num_fixed = interval_has_fixed_size(x_interval) +
391 interval_has_fixed_size(y_interval);
392 if (num_fixed == 0) {
393 no_overlap_2d_num_quadratic_areas++;
394 }
else if (num_fixed == 1) {
395 no_overlap_2d_num_linear_areas++;
397 if (interval_is_fixed(model_proto.constraints(x_interval)) &&
398 interval_is_fixed(model_proto.constraints(y_interval))) {
399 no_overlap_2d_num_fixed_rectangles++;
403 DumpNoOverlap2dProblem(ct, model_proto);
405 }
else if (ct.constraint_case() ==
406 ConstraintProto::ConstraintCase::kNoOverlap) {
407 const int num_intervals = ct.no_overlap().intervals_size();
408 no_overlap_num_intervals += num_intervals;
409 for (
int i = 0;
i < num_intervals; ++
i) {
410 const int interval = ct.no_overlap().intervals(
i);
411 if (constraint_is_optional(interval)) {
412 no_overlap_num_optional_intervals++;
414 if (!interval_has_fixed_size(interval)) {
415 no_overlap_num_variable_sizes++;
417 if (interval_is_fixed(model_proto.constraints(interval))) {
418 no_overlap_num_fixed_intervals++;
421 }
else if (ct.constraint_case() ==
422 ConstraintProto::ConstraintCase::kCumulative) {
423 const int num_intervals = ct.cumulative().intervals_size();
424 cumulative_num_intervals += num_intervals;
425 for (
int i = 0;
i < num_intervals; ++
i) {
426 const int interval = ct.cumulative().intervals(
i);
427 if (constraint_is_optional(interval)) {
428 cumulative_num_optional_intervals++;
430 if (!interval_has_fixed_size(interval)) {
431 cumulative_num_variable_sizes++;
433 if (!expression_is_fixed(ct.cumulative().demands(
i))) {
434 cumulative_num_variable_demands++;
436 if (interval_is_fixed(model_proto.constraints(interval))) {
437 cumulative_num_fixed_intervals++;
442 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
443 ct.linear().vars_size() > 3) {
444 name_to_num_terms[name] += ct.linear().vars_size();
446 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
447 ct.linear().vars_size() > 1 && ct.linear().domain().size() > 2) {
448 name_to_num_complex_domain[name]++;
452 int num_constants = 0;
453 absl::btree_set<int64_t> constant_values;
454 absl::btree_map<Domain, int> num_vars_per_domains;
455 for (
const IntegerVariableProto& var : model_proto.variables()) {
456 if (var.domain_size() == 2 && var.domain(0) == var.domain(1)) {
458 constant_values.insert(var.domain(0));
465 const std::string model_fingerprint_str =
466 (absl::GetFlag(FLAGS_cp_model_fingerprint_model))
467 ? absl::StrFormat(
" (model_fingerprint: %#x)",
471 if (model_proto.has_objective() ||
472 model_proto.has_floating_point_objective()) {
473 absl::StrAppend(&result,
"optimization model '", model_proto.name(),
474 "':", model_fingerprint_str,
"\n");
476 absl::StrAppend(&result,
"satisfaction model '", model_proto.name(),
477 "':", model_fingerprint_str,
"\n");
480 for (
const DecisionStrategyProto& strategy : model_proto.search_strategy()) {
482 &result,
"Search strategy: on ",
483 strategy.exprs().size() + strategy.variables().size(),
" variables, ",
485 strategy.variable_selection_strategy()),
488 strategy.domain_reduction_strategy()),
492 auto count_variables_by_type =
493 [&model_proto](
const google::protobuf::RepeatedField<int>& vars,
494 int* num_booleans,
int* num_integers) {
495 for (
const int ref : vars) {
496 const auto& var_proto = model_proto.variables(
PositiveRef(ref));
497 if (var_proto.domain_size() == 2 && var_proto.domain(0) == 0 &&
498 var_proto.domain(1) == 1) {
502 *num_integers = vars.size() - *num_booleans;
506 int num_boolean_variables_in_objective = 0;
507 int num_integer_variables_in_objective = 0;
508 if (model_proto.has_objective()) {
509 count_variables_by_type(model_proto.objective().vars(),
510 &num_boolean_variables_in_objective,
511 &num_integer_variables_in_objective);
513 if (model_proto.has_floating_point_objective()) {
514 count_variables_by_type(model_proto.floating_point_objective().vars(),
515 &num_boolean_variables_in_objective,
516 &num_integer_variables_in_objective);
519 std::vector<std::string> obj_vars_strings;
520 if (num_boolean_variables_in_objective > 0) {
521 obj_vars_strings.push_back(absl::StrCat(
522 "#bools: ",
FormatCounter(num_boolean_variables_in_objective)));
524 if (num_integer_variables_in_objective > 0) {
525 obj_vars_strings.push_back(absl::StrCat(
526 "#ints: ",
FormatCounter(num_integer_variables_in_objective)));
529 const std::string objective_string =
530 model_proto.has_objective()
531 ? absl::StrCat(
" (", absl::StrJoin(obj_vars_strings,
" "),
533 : (model_proto.has_floating_point_objective()
534 ? absl::StrCat(
" (", absl::StrJoin(obj_vars_strings,
" "),
535 " in floating point objective)")
537 absl::StrAppend(&result,
539 objective_string,
"\n");
541 if (num_vars_per_domains.contains(Domain(0, 1))) {
543 const int num_bools = num_vars_per_domains[Domain(0, 1)];
544 const std::string temp =
545 absl::StrCat(
" - ",
FormatCounter(num_bools),
" Booleans in ",
547 absl::StrAppend(&result, Summarize(temp));
548 num_vars_per_domains.erase(Domain(0, 1));
550 if (num_vars_per_domains.size() < 100) {
551 for (
const auto& entry : num_vars_per_domains) {
552 const std::string temp =
554 entry.first.ToString(),
"\n");
555 absl::StrAppend(&result, Summarize(temp));
558 int64_t max_complexity = 0;
559 int64_t min = std::numeric_limits<int64_t>::max();
560 int64_t max = std::numeric_limits<int64_t>::min();
561 for (
const auto& entry : num_vars_per_domains) {
562 min = std::min(min, entry.first.Min());
563 max = std::max(max, entry.first.Max());
564 max_complexity = std::max(
565 max_complexity,
static_cast<int64_t
>(entry.first.NumIntervals()));
567 absl::StrAppend(&result,
" - ",
FormatCounter(num_vars_per_domains.size()),
568 " different domains in [", min,
",", max,
569 "] with a largest complexity of ", max_complexity,
".\n");
572 if (num_constants > 0) {
573 const std::string temp =
574 absl::StrCat(
" - ",
FormatCounter(num_constants),
" constants in {",
575 absl::StrJoin(constant_values,
","),
"} \n");
576 absl::StrAppend(&result, Summarize(temp));
579 std::vector<std::string> constraints;
580 constraints.reserve(name_to_num_constraints.size());
581 for (
const auto& [c_name, count] : name_to_num_constraints) {
582 const std::string name(c_name);
583 constraints.push_back(absl::StrCat(
"#", name,
": ",
FormatCounter(count)));
584 if (name_to_num_reified.contains(c_name)) {
585 if (name_to_num_multi_reified.contains(c_name)) {
589 " #multi: ",
FormatCounter(name_to_num_multi_reified[c_name]),
")");
591 absl::StrAppend(&constraints.back(),
" (#enforced: ",
595 if (name_to_num_literals.contains(c_name)) {
596 absl::StrAppend(&constraints.back(),
" (#literals: ",
599 if (name_to_num_terms.contains(c_name)) {
600 absl::StrAppend(&constraints.back(),
604 if (name_to_num_expressions.contains(c_name)) {
605 absl::StrAppend(&constraints.back(),
" (#expressions: ",
608 if (name_to_num_complex_domain.contains(c_name)) {
609 absl::StrAppend(&constraints.back(),
" (#complex_domain: ",
612 if (name ==
"kInterval" && num_fixed_intervals > 0) {
613 absl::StrAppend(&constraints.back(),
615 }
else if (name ==
"kNoOverlap2D") {
616 absl::StrAppend(&constraints.back(),
" (#rectangles: ",
618 if (no_overlap_2d_num_optional_rectangles > 0) {
619 absl::StrAppend(&constraints.back(),
", #optional: ",
622 if (no_overlap_2d_num_linear_areas > 0) {
623 absl::StrAppend(&constraints.back(),
", #linear_areas: ",
626 if (no_overlap_2d_num_quadratic_areas > 0) {
627 absl::StrAppend(&constraints.back(),
", #quadratic_areas: ",
630 if (no_overlap_2d_num_fixed_rectangles > 0) {
631 absl::StrAppend(&constraints.back(),
", #fixed_rectangles: ",
634 absl::StrAppend(&constraints.back(),
")");
635 }
else if (name ==
"kCumulative") {
636 absl::StrAppend(&constraints.back(),
" (#intervals: ",
638 if (cumulative_num_optional_intervals > 0) {
639 absl::StrAppend(&constraints.back(),
", #optional: ",
642 if (cumulative_num_variable_sizes > 0) {
643 absl::StrAppend(&constraints.back(),
", #variable_sizes: ",
646 if (cumulative_num_variable_demands > 0) {
647 absl::StrAppend(&constraints.back(),
", #variable_demands: ",
648 cumulative_num_variable_demands);
650 if (cumulative_num_fixed_intervals > 0) {
651 absl::StrAppend(&constraints.back(),
", #fixed_intervals: ",
654 absl::StrAppend(&constraints.back(),
")");
655 }
else if (name ==
"kNoOverlap") {
656 absl::StrAppend(&constraints.back(),
" (#intervals: ",
658 if (no_overlap_num_optional_intervals > 0) {
659 absl::StrAppend(&constraints.back(),
", #optional: ",
662 if (no_overlap_num_variable_sizes > 0) {
663 absl::StrAppend(&constraints.back(),
", #variable_sizes: ",
666 if (no_overlap_num_fixed_intervals > 0) {
667 absl::StrAppend(&constraints.back(),
", #fixed_intervals: ",
670 absl::StrAppend(&constraints.back(),
")");
673 std::sort(constraints.begin(), constraints.end());
674 absl::StrAppend(&result, absl::StrJoin(constraints,
"\n"));
680 bool has_objective) {
682 absl::StrAppend(&result,
"CpSolverResponse summary:");
683 absl::StrAppend(&result,
"\nstatus: ",
686 if (has_objective && response.status() != CpSolverStatus::INFEASIBLE) {
687 absl::StrAppendFormat(&result,
"\nobjective: %.16g",
688 response.objective_value());
689 absl::StrAppendFormat(&result,
"\nbest_bound: %.16g",
690 response.best_objective_bound());
692 absl::StrAppend(&result,
"\nobjective: NA");
693 absl::StrAppend(&result,
"\nbest_bound: NA");
696 absl::StrAppend(&result,
"\nintegers: ", response.num_integers());
697 absl::StrAppend(&result,
"\nbooleans: ", response.num_booleans());
698 absl::StrAppend(&result,
"\nconflicts: ", response.num_conflicts());
699 absl::StrAppend(&result,
"\nbranches: ", response.num_branches());
703 absl::StrAppend(&result,
704 "\npropagations: ", response.num_binary_propagations());
706 &result,
"\ninteger_propagations: ", response.num_integer_propagations());
708 absl::StrAppend(&result,
"\nrestarts: ", response.num_restarts());
709 absl::StrAppend(&result,
"\nlp_iterations: ", response.num_lp_iterations());
710 absl::StrAppend(&result,
"\nwalltime: ", response.wall_time());
711 absl::StrAppend(&result,
"\nusertime: ", response.user_time());
712 absl::StrAppend(&result,
713 "\ndeterministic_time: ", response.deterministic_time());
714 absl::StrAppend(&result,
"\ngap_integral: ", response.gap_integral());
715 if (!response.solution().empty()) {
716 absl::StrAppendFormat(
717 &result,
"\nsolution_fingerprint: %#x",
720 absl::StrAppend(&result,
"\n");
726void LogSubsolverNames(absl::Span<
const std::unique_ptr<SubSolver>> subsolvers,
727 absl::Span<const std::string> ignored,
728 SolverLogger* logger) {
729 if (!logger->LoggingIsEnabled())
return;
731 std::vector<std::string> full_problem_solver_names;
732 std::vector<std::string> incomplete_solver_names;
733 std::vector<std::string> first_solution_solver_names;
734 std::vector<std::string> helper_solver_names;
735 for (
int i = 0;
i < subsolvers.size(); ++
i) {
736 const auto& subsolver = subsolvers[
i];
737 switch (subsolver->type()) {
739 full_problem_solver_names.push_back(subsolver->name());
742 incomplete_solver_names.push_back(subsolver->name());
745 first_solution_solver_names.push_back(subsolver->name());
748 helper_solver_names.push_back(subsolver->name());
755 auto display_subsolver_list = [logger](absl::Span<const std::string> names,
756 const absl::string_view type_name) {
757 if (!names.empty()) {
758 absl::btree_map<std::string, int> solvers_and_count;
759 for (
const auto& name : names) {
760 solvers_and_count[name]++;
762 std::vector<std::string> counted_names;
763 for (
const auto& [name, count] : solvers_and_count) {
765 counted_names.push_back(name);
767 counted_names.push_back(absl::StrCat(name,
"(", count,
")"));
771 logger, names.size(),
" ",
772 absl::StrCat(type_name, names.size() == 1 ?
"" :
"s"),
": [",
773 absl::StrJoin(counted_names.begin(), counted_names.end(),
", "),
"]");
777 display_subsolver_list(full_problem_solver_names,
"full problem subsolver");
778 display_subsolver_list(first_solution_solver_names,
779 "first solution subsolver");
780 display_subsolver_list(incomplete_solver_names,
"interleaved subsolver");
781 display_subsolver_list(helper_solver_names,
"helper subsolver");
782 if (!ignored.empty()) {
783 display_subsolver_list(ignored,
"ignored subsolver");
790 if (!shared->logger->LoggingIsEnabled())
return;
792 shared->logger->FlushPendingThrottledLogs(
true);
795 shared->stat_tables->Display(shared->logger);
796 shared->response->DisplayImprovementStatistics();
798 std::vector<std::vector<std::string>> table;
799 table.push_back({
"Solution repositories",
"Added",
"Queried",
"Synchro"});
800 table.push_back(shared->response->SolutionsRepository().TableLineStats());
801 table.push_back(shared->ls_hints->TableLineStats());
802 if (shared->lp_solutions !=
nullptr) {
803 table.push_back(shared->lp_solutions->TableLineStats());
805 if (shared->incomplete_solutions !=
nullptr) {
806 table.push_back(shared->incomplete_solutions->TableLineStats());
810 if (shared->bounds) {
811 shared->bounds->LogStatistics(shared->logger);
814 if (shared->clauses) {
815 shared->clauses->LogStatistics(shared->logger);
820 shared->stats->Log(shared->logger);
823void LaunchSubsolvers(
const SatParameters& params,
SharedClasses* shared,
824 std::vector<std::unique_ptr<SubSolver>>& subsolvers,
825 absl::Span<const std::string> ignored) {
828 if (params.interleave_search()) {
830 absl::StrFormat(
"Starting deterministic search at %.2fs with "
831 "%i workers and batch size of %d.",
832 shared->wall_timer->Get(), params.num_workers(),
833 params.interleave_batch_size()));
837 absl::StrFormat(
"Starting search at %.2fs with %i workers.",
838 shared->wall_timer->Get(), params.num_workers()));
840 LogSubsolverNames(subsolvers, ignored, shared->logger);
843 if (params.interleave_search()) {
844 int batch_size = params.interleave_batch_size();
845 if (batch_size == 0) {
846 batch_size = params.num_workers() == 1 ? 1 : params.num_workers() * 3;
849 "Setting number of tasks in each batch of interleaved search to ",
853 params.max_num_deterministic_batches());
861 for (
int i = 0;
i < subsolvers.size(); ++
i) {
862 subsolvers[
i].reset();
864 LogFinalStatistics(shared);
867bool VarIsFixed(
const CpModelProto& model_proto,
int i) {
868 return model_proto.variables(
i).domain_size() == 2 &&
869 model_proto.variables(
i).domain(0) ==
870 model_proto.variables(
i).domain(1);
876void RestrictObjectiveUsingHint(CpModelProto* model_proto) {
877 if (!model_proto->has_objective())
return;
878 if (!model_proto->has_solution_hint())
return;
881 const int num_vars = model_proto->variables().size();
883 std::vector<bool> filled(num_vars,
false);
884 std::vector<int64_t>
solution(num_vars, 0);
885 for (
int var = 0; var < num_vars; ++var) {
886 if (VarIsFixed(*model_proto, var)) {
887 solution[var] = model_proto->variables(var).domain(0);
892 const auto& hint_proto = model_proto->solution_hint();
893 const int num_hinted = hint_proto.vars().size();
894 for (
int i = 0;
i < num_hinted; ++
i) {
895 const int var = hint_proto.vars(
i);
897 if (filled[var])
continue;
899 const int64_t value = hint_proto.values(
i);
904 if (num_filled != num_vars)
return;
906 const int64_t obj_upper_bound =
908 const Domain restriction =
909 Domain(std::numeric_limits<int64_t>::min(), obj_upper_bound);
911 if (model_proto->objective().domain().empty()) {
916 model_proto->mutable_objective());
922bool SolutionHintIsCompleteAndFeasible(
923 const CpModelProto& model_proto, SolverLogger* logger =
nullptr,
925 if (!model_proto.has_solution_hint() && model_proto.variables_size() > 0) {
929 int num_active_variables = 0;
930 int num_hinted_variables = 0;
931 for (
int var = 0; var < model_proto.variables_size(); ++var) {
932 if (VarIsFixed(model_proto, var))
continue;
933 ++num_active_variables;
936 for (
int i = 0;
i < model_proto.solution_hint().vars_size(); ++
i) {
937 const int ref = model_proto.solution_hint().vars(
i);
938 if (VarIsFixed(model_proto,
PositiveRef(ref)))
continue;
939 ++num_hinted_variables;
941 CHECK_LE(num_hinted_variables, num_active_variables);
943 if (num_active_variables != num_hinted_variables) {
944 if (logger !=
nullptr) {
946 logger,
"The solution hint is incomplete: ", num_hinted_variables,
947 " out of ", num_active_variables,
" non fixed variables hinted.");
952 std::vector<int64_t>
solution(model_proto.variables_size(), 0);
954 for (
int var = 0; var < model_proto.variables_size(); ++var) {
955 if (VarIsFixed(model_proto, var)) {
956 solution[var] = model_proto.variables(var).domain(0);
960 for (
int i = 0;
i < model_proto.solution_hint().vars_size(); ++
i) {
961 const int ref = model_proto.solution_hint().vars(
i);
963 const int64_t value = model_proto.solution_hint().values(
i);
964 const int64_t hinted_value =
RefIsPositive(ref) ? value : -value;
966 if (!domain.
Contains(hinted_value)) {
967 if (logger !=
nullptr) {
970 "The solution hint is complete but it contains values outside "
971 "of the domain of the variables.");
979 bool breaks_assumptions =
false;
981 for (
const int literal_ref : model_proto.assumptions()) {
984 breaks_assumptions =
true;
989 if (is_feasible && breaks_assumptions) {
990 if (logger !=
nullptr) {
993 "The solution hint is complete and feasible, but it breaks the "
994 "assumptions of the model.");
999 if (manager !=
nullptr && !
solution.empty()) {
1002 manager->NewSolution(
solution,
"complete_hint",
nullptr);
1003 }
else if (logger !=
nullptr) {
1004 std::string message =
"The solution hint is complete and is feasible.";
1005 if (model_proto.has_objective()) {
1007 &message,
" Its objective value is ",
1011 model_proto.objective(),
1021 if (logger !=
nullptr) {
1023 "The solution hint is complete, but it is infeasible! we "
1024 "will try to repair it.");
1031class FullProblemSolver :
public SubSolver {
1033 FullProblemSolver(absl::string_view name,
1034 const SatParameters& local_parameters,
bool split_in_chunks,
1035 SharedClasses* shared,
bool stop_at_first_solution =
false)
1036 : SubSolver(name, stop_at_first_solution ? FIRST_SOLUTION : FULL_PROBLEM),
1038 split_in_chunks_(split_in_chunks),
1039 stop_at_first_solution_(stop_at_first_solution),
1040 local_model_(SubSolver::name()) {
1042 *(local_model_.GetOrCreate<SatParameters>()) = local_parameters;
1043 shared_->time_limit->UpdateLocalLimit(
1044 local_model_.GetOrCreate<TimeLimit>());
1046 if (stop_at_first_solution) {
1047 local_model_.GetOrCreate<TimeLimit>()
1048 ->RegisterSecondaryExternalBooleanAsLimit(
1049 shared_->response->first_solution_solvers_should_stop());
1054 shared_->RegisterSharedClassesInLocalModel(&local_model_);
1059 auto* logger = local_model_.GetOrCreate<SolverLogger>();
1060 logger->EnableLogging(local_parameters.log_search_progress());
1061 logger->SetLogToStdOut(local_parameters.log_to_stdout());
1064 ~FullProblemSolver()
override {
1065 CpSolverResponse response;
1066 shared_->response->FillSolveStatsInResponse(&local_model_, &response);
1067 shared_->response->AppendResponseToBeMerged(response);
1068 shared_->stat_tables->AddTimingStat(*
this);
1069 shared_->stat_tables->AddLpStat(name(), &local_model_);
1070 shared_->stat_tables->AddSearchStat(name(), &local_model_);
1071 shared_->stat_tables->AddClausesStat(name(), &local_model_);
1074 bool IsDone()
override {
1077 if (shared_->SearchIsDone())
return true;
1079 return stop_at_first_solution_ &&
1080 shared_->response->first_solution_solvers_should_stop()->load();
1083 bool TaskIsAvailable()
override {
1084 if (IsDone())
return false;
1090 if (shared_->SearchIsDone())
return false;
1092 absl::MutexLock mutex_lock(&mutex_);
1093 if (previous_task_is_completed_) {
1094 if (solving_first_chunk_)
return true;
1095 if (split_in_chunks_)
return true;
1100 std::function<void()> GenerateTask(int64_t )
override {
1102 absl::MutexLock mutex_lock(&mutex_);
1103 previous_task_is_completed_ =
false;
1106 if (solving_first_chunk_) {
1113 if (shared_->bounds !=
nullptr) {
1115 shared_->model_proto, shared_->bounds.get(), &local_model_);
1117 shared_->model_proto, shared_->bounds.get(), &local_model_);
1122 if (shared_->clauses !=
nullptr) {
1123 const int id = shared_->clauses->RegisterNewId();
1124 shared_->clauses->SetWorkerNameForId(
id, local_model_.Name());
1131 auto* logger = local_model_.GetOrCreate<SolverLogger>();
1134 "Starting subsolver \'%s\' hint search at %.2fs",
1135 name(), shared_->wall_timer->Get()));
1137 if (local_model_.GetOrCreate<SatParameters>()->repair_hint()) {
1144 absl::StrFormat(
"Starting subsolver \'%s\' search at %.2fs",
1145 name(), shared_->wall_timer->Get()));
1148 solving_first_chunk_ =
false;
1150 if (split_in_chunks_) {
1152 absl::MutexLock mutex_lock(&mutex_);
1153 previous_task_is_completed_ =
true;
1158 auto*
time_limit = local_model_.GetOrCreate<TimeLimit>();
1159 if (split_in_chunks_) {
1162 auto* params = local_model_.GetOrCreate<SatParameters>();
1163 params->set_max_deterministic_time(1);
1164 time_limit->ResetLimitFromParameters(*params);
1165 shared_->time_limit->UpdateLocalLimit(
time_limit);
1168 const double saved_dtime =
time_limit->GetElapsedDeterministicTime();
1171 absl::MutexLock mutex_lock(&mutex_);
1172 previous_task_is_completed_ =
true;
1173 dtime_since_last_sync_ +=
1174 time_limit->GetElapsedDeterministicTime() - saved_dtime;
1181 void Synchronize()
override {
1182 absl::MutexLock mutex_lock(&mutex_);
1183 AddTaskDeterministicDuration(dtime_since_last_sync_);
1184 shared_->time_limit->AdvanceDeterministicTime(dtime_since_last_sync_);
1185 dtime_since_last_sync_ = 0.0;
1189 SharedClasses* shared_;
1190 const bool split_in_chunks_;
1191 const bool stop_at_first_solution_;
1196 bool solving_first_chunk_ =
true;
1199 double dtime_since_last_sync_ ABSL_GUARDED_BY(mutex_) = 0.0;
1200 bool previous_task_is_completed_ ABSL_GUARDED_BY(mutex_) =
true;
1203#if !defined(__PORTABLE_PLATFORM__)
1205class FeasibilityPumpSolver :
public SubSolver {
1207 FeasibilityPumpSolver(
const SatParameters& local_parameters,
1208 SharedClasses* shared)
1209 : SubSolver(
"feasibility_pump", INCOMPLETE),
1211 local_model_(std::make_unique<Model>(name())) {
1213 *(local_model_->GetOrCreate<SatParameters>()) = local_parameters;
1214 shared_->time_limit->UpdateLocalLimit(
1215 local_model_->GetOrCreate<TimeLimit>());
1216 shared_->RegisterSharedClassesInLocalModel(local_model_.get());
1219 ~FeasibilityPumpSolver()
override {
1220 shared_->stat_tables->AddTimingStat(*
this);
1223 bool IsDone()
override {
return shared_->SearchIsDone(); }
1225 bool TaskIsAvailable()
override {
1226 if (shared_->SearchIsDone())
return false;
1227 absl::MutexLock mutex_lock(&mutex_);
1228 return previous_task_is_completed_;
1231 std::function<void()> GenerateTask(int64_t )
override {
1233 absl::MutexLock mutex_lock(&mutex_);
1234 previous_task_is_completed_ =
false;
1238 absl::MutexLock mutex_lock(&mutex_);
1239 if (solving_first_chunk_) {
1243 if (local_model_->Get<FeasibilityPump>() ==
nullptr)
return;
1244 solving_first_chunk_ =
false;
1246 previous_task_is_completed_ =
true;
1251 auto*
time_limit = local_model_->GetOrCreate<TimeLimit>();
1252 const double saved_dtime =
time_limit->GetElapsedDeterministicTime();
1253 auto* feasibility_pump = local_model_->Mutable<FeasibilityPump>();
1254 if (!feasibility_pump->Solve()) {
1255 shared_->response->NotifyThatImprovingProblemIsInfeasible(name());
1259 absl::MutexLock mutex_lock(&mutex_);
1260 dtime_since_last_sync_ +=
1261 time_limit->GetElapsedDeterministicTime() - saved_dtime;
1265 if (shared_->SearchIsDone()) {
1266 shared_->time_limit->Stop();
1270 absl::MutexLock mutex_lock(&mutex_);
1271 previous_task_is_completed_ =
true;
1275 void Synchronize()
override {
1276 absl::MutexLock mutex_lock(&mutex_);
1277 AddTaskDeterministicDuration(dtime_since_last_sync_);
1278 shared_->time_limit->AdvanceDeterministicTime(dtime_since_last_sync_);
1279 dtime_since_last_sync_ = 0.0;
1283 SharedClasses* shared_;
1284 std::unique_ptr<Model> local_model_;
1290 bool solving_first_chunk_ ABSL_GUARDED_BY(mutex_) =
true;
1292 double dtime_since_last_sync_ ABSL_GUARDED_BY(mutex_) = 0.0;
1293 bool previous_task_is_completed_ ABSL_GUARDED_BY(mutex_) =
true;
1299 LnsSolver(std::unique_ptr<NeighborhoodGenerator> generator,
1300 const SatParameters& lns_parameters_base,
1301 const SatParameters& lns_parameters_stalling,
1302 NeighborhoodGeneratorHelper* helper, SharedClasses* shared)
1303 : SubSolver(generator->name(), INCOMPLETE),
1304 generator_(std::move(generator)),
1306 lns_parameters_base_(lns_parameters_base),
1307 lns_parameters_stalling_(lns_parameters_stalling),
1310 ~LnsSolver()
override {
1311 shared_->stat_tables->AddTimingStat(*
this);
1312 shared_->stat_tables->AddLnsStat(
1314 generator_->num_fully_solved_calls(),
1315 generator_->num_calls(),
1316 generator_->num_improving_calls(),
1317 generator_->difficulty(),
1318 generator_->deterministic_limit());
1321 bool TaskIsAvailable()
override {
1322 if (shared_->SearchIsDone())
return false;
1323 return generator_->ReadyToGenerate();
1326 std::function<void()> GenerateTask(int64_t task_id)
override {
1327 return [task_id,
this]() {
1328 if (shared_->SearchIsDone())
return;
1333 const int32_t low =
static_cast<int32_t
>(task_id);
1334 const int32_t high =
static_cast<int32_t
>(task_id >> 32);
1335 std::seed_seq seed{low, high, lns_parameters_base_.random_seed()};
1338 NeighborhoodGenerator::SolveData data;
1339 data.task_id = task_id;
1340 data.difficulty = generator_->difficulty();
1341 data.deterministic_limit = generator_->deterministic_limit();
1344 CpSolverResponse base_response;
1346 const SharedSolutionRepository<int64_t>& repo =
1347 shared_->response->SolutionsRepository();
1348 if (repo.NumSolutions() > 0) {
1349 base_response.set_status(CpSolverStatus::FEASIBLE);
1350 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
1351 solution = repo.GetRandomBiasedSolution(random);
1352 base_response.mutable_solution()->Assign(
1358 data.initial_best_objective = repo.GetSolution(0)->rank;
1359 data.base_objective =
solution->rank;
1361 base_response.set_status(CpSolverStatus::UNKNOWN);
1368 data.initial_best_objective =
1369 shared_->response->GetInnerObjectiveUpperBound();
1370 data.base_objective = data.initial_best_objective;
1374 Neighborhood neighborhood =
1375 generator_->Generate(base_response, data, random);
1377 if (!neighborhood.is_generated)
return;
1379 SatParameters local_params;
1382 const int64_t stall = generator_->num_consecutive_non_improving_calls();
1383 const int search_index = stall < 10 ? 0 : task_id % 2;
1384 switch (search_index) {
1386 local_params = lns_parameters_base_;
1389 local_params = lns_parameters_stalling_;
1392 const std::string_view search_info =
1393 absl::StripPrefix(std::string_view(local_params.name()),
"lns_");
1394 local_params.set_max_deterministic_time(data.deterministic_limit);
1396 std::string source_info =
1397 neighborhood.source_info.empty() ? name() : neighborhood.source_info;
1398 const int64_t num_calls = std::max(int64_t{1}, generator_->num_calls());
1399 const double fully_solved_proportion =
1400 static_cast<double>(generator_->num_fully_solved_calls()) /
1401 static_cast<double>(num_calls);
1402 const std::string lns_info = absl::StrFormat(
1403 "%s (d=%0.2e s=%i t=%0.2f p=%0.2f stall=%d h=%s)", source_info,
1404 data.difficulty, task_id, data.deterministic_limit,
1405 fully_solved_proportion, stall, search_info);
1407 Model local_model(lns_info);
1408 *(local_model.GetOrCreate<SatParameters>()) = local_params;
1409 TimeLimit* local_time_limit = local_model.GetOrCreate<TimeLimit>();
1410 local_time_limit->ResetLimitFromParameters(local_params);
1411 shared_->time_limit->UpdateLocalLimit(local_time_limit);
1416 absl::MutexLock l(&next_arena_size_mutex_);
1417 buffer_size = next_arena_size_;
1419 google::protobuf::Arena arena(
1420 google::protobuf::ArenaOptions({.start_block_size = buffer_size}));
1421 CpModelProto& lns_fragment =
1422 *google::protobuf::Arena::Create<CpModelProto>(&arena);
1423 CpModelProto& mapping_proto =
1424 *google::protobuf::Arena::Create<CpModelProto>(&arena);
1425 auto context = std::make_unique<PresolveContext>(
1426 &local_model, &lns_fragment, &mapping_proto);
1428 *lns_fragment.mutable_variables() = neighborhood.delta.variables();
1430 ModelCopy copier(context.get());
1433 if (!copier.ImportAndSimplifyConstraints(helper_->ModelProto())) {
1438 if (!neighborhood.delta.constraints().empty() &&
1439 !copier.ImportAndSimplifyConstraints(neighborhood.delta)) {
1445 context->WriteVariableDomainsToProto();
1450 helper_->ModelProto(), context.get());
1451 lns_fragment.set_name(absl::StrCat(
"lns_", task_id,
"_", source_info));
1458 lns_fragment.clear_symmetry();
1461 if (neighborhood.delta.has_solution_hint()) {
1462 *lns_fragment.mutable_solution_hint() =
1463 neighborhood.delta.solution_hint();
1465 if (generator_->num_consecutive_non_improving_calls() > 10 &&
1466 absl::Bernoulli(random, 0.5)) {
1470 lns_fragment.clear_solution_hint();
1472 if (neighborhood.is_simple &&
1473 neighborhood.num_relaxed_variables_in_objective == 0) {
1479 if (generator_->num_consecutive_non_improving_calls() > 10) {
1481 lns_fragment.clear_solution_hint();
1488 bool hint_feasible_before_presolve =
false;
1489 if (lns_parameters_base_.debug_crash_if_presolve_breaks_hint()) {
1490 hint_feasible_before_presolve =
1491 SolutionHintIsCompleteAndFeasible(lns_fragment,
nullptr);
1498 RestrictObjectiveUsingHint(&lns_fragment);
1500 CpModelProto debug_copy;
1501 if (absl::GetFlag(FLAGS_cp_model_dump_problematic_lns)) {
1504 debug_copy = lns_fragment;
1507 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
1509 const std::string lns_name =
1510 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
1511 lns_fragment.name(),
".pb.txt");
1512 LOG(INFO) <<
"Dumping LNS model to '" << lns_name <<
"'.";
1516 std::vector<int> postsolve_mapping;
1517 const CpSolverStatus presolve_status =
1522 if (local_time_limit->LimitReached())
return;
1525 context.reset(
nullptr);
1526 neighborhood.delta.Clear();
1528 if (lns_parameters_base_.debug_crash_if_presolve_breaks_hint() &&
1529 hint_feasible_before_presolve &&
1530 !SolutionHintIsCompleteAndFeasible(lns_fragment,
1532 LOG(FATAL) <<
"Presolve broke a feasible LNS hint. The model name is '"
1533 << lns_fragment.name()
1534 <<
"' (use the --cp_model_dump_submodels flag to dump it).";
1540 auto* local_response_manager =
1541 local_model.GetOrCreate<SharedResponseManager>();
1542 local_response_manager->InitializeObjective(lns_fragment);
1543 local_response_manager->SetSynchronizationMode(
true);
1545 CpSolverResponse local_response;
1546 if (presolve_status == CpSolverStatus::UNKNOWN) {
1549 if (shared_->SearchIsDone())
return;
1554 local_response = local_response_manager->GetResponse();
1559 if (local_response.solution_info().empty()) {
1560 local_response.set_solution_info(
1561 absl::StrCat(lns_info,
" [presolve]"));
1567 if (presolve_status == CpSolverStatus::INFEASIBLE) {
1568 local_response_manager->NotifyThatImprovingProblemIsInfeasible(
1571 local_response = local_response_manager->GetResponse();
1572 local_response.set_status(presolve_status);
1574 const std::string solution_info = local_response.solution_info();
1575 std::vector<int64_t> solution_values(local_response.solution().begin(),
1576 local_response.solution().end());
1578 data.status = local_response.status();
1581 if (data.status == CpSolverStatus::OPTIMAL ||
1582 data.status == CpSolverStatus::FEASIBLE) {
1584 local_params, helper_->ModelProto().variables_size(), mapping_proto,
1585 postsolve_mapping, &solution_values);
1586 local_response.mutable_solution()->Assign(solution_values.begin(),
1587 solution_values.end());
1590 data.deterministic_time +=
1591 local_time_limit->GetElapsedDeterministicTime();
1593 bool new_solution =
false;
1594 bool display_lns_info = VLOG_IS_ON(2);
1595 if (!local_response.solution().empty()) {
1601 const bool feasible =
1604 if (absl::GetFlag(FLAGS_cp_model_dump_problematic_lns)) {
1605 const std::string name =
1606 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
1607 debug_copy.name(),
".pb.txt");
1608 LOG(INFO) <<
"Dumping problematic LNS model to '" << name <<
"'.";
1611 LOG(ERROR) <<
"Infeasible LNS solution! " << solution_info
1612 <<
" solved with params " << local_params;
1635 if (data.status == CpSolverStatus::OPTIMAL &&
1636 !shared_->model_proto.has_symmetry() && !solution_values.empty() &&
1637 neighborhood.is_simple && shared_->bounds !=
nullptr &&
1638 !neighborhood.variables_that_can_be_fixed_to_local_optimum
1640 display_lns_info =
true;
1641 shared_->bounds->FixVariablesFromPartialSolution(
1643 neighborhood.variables_that_can_be_fixed_to_local_optimum);
1647 data.new_objective = data.base_objective;
1648 if (data.status == CpSolverStatus::OPTIMAL ||
1649 data.status == CpSolverStatus::FEASIBLE) {
1651 shared_->model_proto.objective(), solution_values));
1656 if (data.status == CpSolverStatus::OPTIMAL ||
1657 data.status == CpSolverStatus::FEASIBLE) {
1658 if (absl::MakeSpan(solution_values) !=
1659 absl::MakeSpan(base_response.solution())) {
1660 new_solution =
true;
1662 shared_->response, shared_->model_proto, solution_values,
1663 solution_info, base_response.solution(),
nullptr);
1666 if (!neighborhood.is_reduced &&
1667 (data.status == CpSolverStatus::OPTIMAL ||
1668 data.status == CpSolverStatus::INFEASIBLE)) {
1669 shared_->response->NotifyThatImprovingProblemIsInfeasible(
1671 shared_->time_limit->Stop();
1675 generator_->AddSolveData(data);
1677 if (VLOG_IS_ON(2) && display_lns_info) {
1678 std::string s = absl::StrCat(
" LNS ", name(),
":");
1681 shared_->model_proto.objective(),
1683 base_response.solution()));
1685 shared_->model_proto.objective(),
1688 absl::StrAppend(&s,
" [new_sol:", base_obj,
" -> ", new_obj,
"]");
1690 if (neighborhood.is_simple) {
1693 "relaxed:",
FormatCounter(neighborhood.num_relaxed_variables),
1695 FormatCounter(neighborhood.num_relaxed_variables_in_objective),
1697 neighborhood.variables_that_can_be_fixed_to_local_optimum.size(),
1702 " [d:", absl::StrFormat(
"%0.2e", data.difficulty),
", id:", task_id,
1703 ", dtime:", absl::StrFormat(
"%0.2f", data.deterministic_time),
"/",
1704 data.deterministic_limit,
1706 ", #calls:", generator_->num_calls(),
1707 ", p:", fully_solved_proportion,
"]");
1710 absl::MutexLock l(&next_arena_size_mutex_);
1711 next_arena_size_ = arena.SpaceUsed();
1716 void Synchronize()
override {
1717 const double dtime = generator_->Synchronize();
1718 AddTaskDeterministicDuration(dtime);
1719 shared_->time_limit->AdvanceDeterministicTime(dtime);
1723 std::unique_ptr<NeighborhoodGenerator> generator_;
1724 NeighborhoodGeneratorHelper* helper_;
1725 const SatParameters lns_parameters_base_;
1726 const SatParameters lns_parameters_stalling_;
1727 SharedClasses* shared_;
1731 absl::Mutex next_arena_size_mutex_;
1732 int64_t next_arena_size_ ABSL_GUARDED_BY(next_arena_size_mutex_) =
1733 helper_->ModelProto().SpaceUsedLong();
1737 const SatParameters& params = *global_model->GetOrCreate<SatParameters>();
1738 CHECK(!params.enumerate_all_solutions())
1739 <<
"Enumerating all solutions in parallel is not supported.";
1740 if (global_model->GetOrCreate<TimeLimit>()->LimitReached())
return;
1749 std::vector<std::unique_ptr<SubSolver>> subsolvers;
1760 std::vector<std::unique_ptr<SubSolver>> full_worker_subsolvers;
1761 std::vector<std::unique_ptr<SubSolver>> first_solution_full_subsolvers;
1762 std::vector<std::unique_ptr<SubSolver>> reentrant_interleaved_subsolvers;
1763 std::vector<std::unique_ptr<SubSolver>> interleaved_subsolvers;
1766 subsolvers.push_back(std::make_unique<SynchronizationPoint>(
1767 "synchronization_agent", [shared]() {
1768 shared->response->Synchronize();
1769 shared->response->MutableSolutionsRepository()->Synchronize();
1770 shared->ls_hints->Synchronize();
1771 if (shared->bounds !=
nullptr) {
1772 shared->bounds->Synchronize();
1774 if (shared->lp_solutions !=
nullptr) {
1775 shared->lp_solutions->Synchronize();
1777 if (shared->clauses !=
nullptr) {
1778 shared->clauses->Synchronize();
1783 const SatParameters& lns_params_base = name_to_params.at(
"lns_base");
1784 const SatParameters& lns_params_stalling = name_to_params.at(
"lns_stalling");
1785 const SatParameters& lns_params_routing = name_to_params.at(
"lns_routing");
1789 auto unique_helper = std::make_unique<NeighborhoodGeneratorHelper>(
1790 &shared->model_proto, ¶ms, shared->response, shared->bounds.get());
1792 subsolvers.push_back(std::move(unique_helper));
1795 const int num_shared_tree_workers = shared->shared_tree_manager->NumWorkers();
1798 if (num_shared_tree_workers >= 2 &&
1799 shared->model_proto.assumptions().empty()) {
1801 name_filter.Filter({name_to_params.at(
"shared_tree")}),
1802 num_shared_tree_workers)) {
1803 full_worker_subsolvers.push_back(std::make_unique<FullProblemSolver>(
1804 local_params.name(), local_params,
1805 params.interleave_search(), shared));
1811 params, shared->model_proto,
1812 full_worker_subsolvers.size(),
1814 if (!name_filter.Keep(local_params.name()))
continue;
1817 if (params.optimize_with_max_hs())
continue;
1820 if (local_params.use_objective_shaving_search()) {
1821 full_worker_subsolvers.push_back(std::make_unique<ObjectiveShavingSolver>(
1822 local_params, helper, shared));
1827 if (local_params.use_variables_shaving_search()) {
1828 full_worker_subsolvers.push_back(
1829 std::make_unique<VariablesShavingSolver>(local_params, shared));
1833 full_worker_subsolvers.push_back(std::make_unique<FullProblemSolver>(
1834 local_params.name(), local_params,
1835 params.interleave_search(), shared));
1839 int num_interleaved_subsolver_that_do_not_need_solution = 0;
1840 if (params.use_feasibility_pump() && name_filter.Keep(
"feasibility_pump")) {
1841 ++num_interleaved_subsolver_that_do_not_need_solution;
1842 interleaved_subsolvers.push_back(
1843 std::make_unique<FeasibilityPumpSolver>(params, shared));
1848 if (params.use_rins_lns() && name_filter.Keep(
"rins/rens")) {
1854 ++num_interleaved_subsolver_that_do_not_need_solution;
1855 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1856 std::make_unique<RelaxationInducedNeighborhoodGenerator>(
1857 helper, shared->response, shared->lp_solutions.get(),
1858 shared->incomplete_solutions.get(), name_filter.LastName()),
1859 lns_params_base, lns_params_stalling, helper, shared));
1867 if (params.use_lns() && shared->model_proto.has_objective() &&
1868 !shared->model_proto.objective().vars().empty()) {
1871 if (name_filter.Keep(
"rnd_var_lns")) {
1872 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1873 std::make_unique<RelaxRandomVariablesGenerator>(
1874 helper, name_filter.LastName()),
1875 lns_params_base, lns_params_stalling, helper, shared));
1877 if (name_filter.Keep(
"rnd_cst_lns")) {
1878 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1879 std::make_unique<RelaxRandomConstraintsGenerator>(
1880 helper, name_filter.LastName()),
1881 lns_params_base, lns_params_stalling, helper, shared));
1883 if (name_filter.Keep(
"graph_var_lns")) {
1884 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1885 std::make_unique<VariableGraphNeighborhoodGenerator>(
1886 helper, name_filter.LastName()),
1887 lns_params_base, lns_params_stalling, helper, shared));
1889 if (name_filter.Keep(
"graph_arc_lns")) {
1890 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1891 std::make_unique<ArcGraphNeighborhoodGenerator>(
1892 helper, name_filter.LastName()),
1893 lns_params_base, lns_params_stalling, helper, shared));
1895 if (name_filter.Keep(
"graph_cst_lns")) {
1896 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1897 std::make_unique<ConstraintGraphNeighborhoodGenerator>(
1898 helper, name_filter.LastName()),
1899 lns_params_base, lns_params_stalling, helper, shared));
1901 if (name_filter.Keep(
"graph_dec_lns")) {
1902 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1903 std::make_unique<DecompositionGraphNeighborhoodGenerator>(
1904 helper, name_filter.LastName()),
1905 lns_params_base, lns_params_stalling, helper, shared));
1907 if (params.use_lb_relax_lns() &&
1908 params.num_workers() >= params.lb_relax_num_workers_threshold() &&
1909 name_filter.Keep(
"lb_relax_lns")) {
1910 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1911 std::make_unique<LocalBranchingLpBasedNeighborhoodGenerator>(
1912 helper, name_filter.LastName(), shared->time_limit, shared),
1913 lns_params_base, lns_params_stalling, helper, shared));
1916 const bool has_no_overlap_or_cumulative =
1917 !helper->TypeToConstraints(ConstraintProto::kNoOverlap).empty() ||
1918 !helper->TypeToConstraints(ConstraintProto::kCumulative).empty();
1921 if (has_no_overlap_or_cumulative) {
1922 if (name_filter.Keep(
"scheduling_intervals_lns")) {
1923 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1924 std::make_unique<RandomIntervalSchedulingNeighborhoodGenerator>(
1925 helper, name_filter.LastName()),
1926 lns_params_base, lns_params_stalling, helper, shared));
1928 if (name_filter.Keep(
"scheduling_time_window_lns")) {
1929 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1930 std::make_unique<SchedulingTimeWindowNeighborhoodGenerator>(
1931 helper, name_filter.LastName()),
1932 lns_params_base, lns_params_stalling, helper, shared));
1934 const std::vector<std::vector<int>> intervals_in_constraints =
1935 helper->GetUniqueIntervalSets();
1936 if (intervals_in_constraints.size() > 2 &&
1937 name_filter.Keep(
"scheduling_resource_windows_lns")) {
1938 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1939 std::make_unique<SchedulingResourceWindowsNeighborhoodGenerator>(
1940 helper, intervals_in_constraints, name_filter.LastName()),
1941 lns_params_base, lns_params_stalling, helper, shared));
1946 const bool has_no_overlap2d =
1947 !helper->TypeToConstraints(ConstraintProto::kNoOverlap2D).empty();
1948 if (has_no_overlap2d) {
1949 if (name_filter.Keep(
"packing_random_lns")) {
1950 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1951 std::make_unique<RandomRectanglesPackingNeighborhoodGenerator>(
1952 helper, name_filter.LastName()),
1953 lns_params_base, lns_params_stalling, helper, shared));
1955 if (name_filter.Keep(
"packing_square_lns")) {
1956 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1957 std::make_unique<RectanglesPackingRelaxOneNeighborhoodGenerator>(
1958 helper, name_filter.LastName()),
1959 lns_params_base, lns_params_stalling, helper, shared));
1961 if (name_filter.Keep(
"packing_swap_lns")) {
1962 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1963 std::make_unique<RectanglesPackingRelaxTwoNeighborhoodsGenerator>(
1964 helper, name_filter.LastName()),
1965 lns_params_base, lns_params_stalling, helper, shared));
1967 if (name_filter.Keep(
"packing_precedences_lns")) {
1968 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1969 std::make_unique<RandomPrecedencesPackingNeighborhoodGenerator>(
1970 helper, name_filter.LastName()),
1971 lns_params_base, lns_params_stalling, helper, shared));
1973 if (name_filter.Keep(
"packing_slice_lns")) {
1974 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1975 std::make_unique<SlicePackingNeighborhoodGenerator>(
1976 helper, name_filter.LastName()),
1977 lns_params_base, lns_params_stalling, helper, shared));
1982 if (has_no_overlap_or_cumulative || has_no_overlap2d) {
1983 if (name_filter.Keep(
"scheduling_precedences_lns")) {
1984 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1985 std::make_unique<RandomPrecedenceSchedulingNeighborhoodGenerator>(
1986 helper, name_filter.LastName()),
1987 lns_params_base, lns_params_stalling, helper, shared));
1991 const int num_circuit =
static_cast<int>(
1992 helper->TypeToConstraints(ConstraintProto::kCircuit).size());
1993 const int num_routes =
static_cast<int>(
1994 helper->TypeToConstraints(ConstraintProto::kRoutes).size());
1995 if (num_circuit + num_routes > 0) {
1996 if (name_filter.Keep(
"routing_random_lns")) {
1997 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
1998 std::make_unique<RoutingRandomNeighborhoodGenerator>(
1999 helper, name_filter.LastName()),
2000 lns_params_routing, lns_params_stalling, helper, shared));
2002 if (name_filter.Keep(
"routing_path_lns")) {
2003 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
2004 std::make_unique<RoutingPathNeighborhoodGenerator>(
2005 helper, name_filter.LastName()),
2006 lns_params_routing, lns_params_stalling, helper, shared));
2009 if (num_routes > 0 || num_circuit > 1) {
2010 if (name_filter.Keep(
"routing_full_path_lns")) {
2011 reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
2012 std::make_unique<RoutingFullPathNeighborhoodGenerator>(
2013 helper, name_filter.LastName()),
2014 lns_params_routing, lns_params_stalling, helper, shared));
2021 const auto get_linear_model = [&]() {
2022 auto* candidate = global_model->Get<
LinearModel>();
2023 if (candidate !=
nullptr)
return candidate;
2027 global_model->TakeOwnership(linear_model);
2028 global_model->Register(linear_model);
2036 if (shared->model_proto.has_objective()) {
2040 const int num_thread_for_interleaved_workers =
2041 params.num_workers() - full_worker_subsolvers.size();
2042 int num_violation_ls = params.has_num_violation_ls()
2043 ? params.num_violation_ls()
2044 : (num_thread_for_interleaved_workers + 2) / 3;
2048 if (reentrant_interleaved_subsolvers.empty()) {
2050 std::max(num_violation_ls,
2051 num_thread_for_interleaved_workers -
2052 static_cast<int>(interleaved_subsolvers.size()));
2055 const absl::string_view ls_name =
"ls";
2056 const absl::string_view lin_ls_name =
"ls_lin";
2058 const int num_ls_lin =
2059 name_filter.Keep(lin_ls_name) ? (num_violation_ls + 1) / 3 : 0;
2060 const int num_ls_default =
2061 name_filter.Keep(ls_name) ? num_violation_ls - num_ls_lin : 0;
2063 if (num_ls_default > 0) {
2064 std::shared_ptr<SharedLsStates> states = std::make_shared<SharedLsStates>(
2065 ls_name, params, shared->stat_tables);
2066 for (
int i = 0;
i < num_ls_default; ++
i) {
2067 SatParameters local_params = params;
2068 local_params.set_random_seed(
2069 CombineSeed(params.random_seed(), interleaved_subsolvers.size()));
2070 local_params.set_feasibility_jump_linearization_level(0);
2071 interleaved_subsolvers.push_back(
2072 std::make_unique<FeasibilityJumpSolver>(
2074 local_params, states, shared->time_limit, shared->response,
2075 shared->bounds.get(), shared->ls_hints, shared->stats,
2076 shared->stat_tables));
2080 if (num_ls_lin > 0) {
2081 std::shared_ptr<SharedLsStates> lin_states =
2082 std::make_shared<SharedLsStates>(lin_ls_name, params,
2083 shared->stat_tables);
2084 for (
int i = 0;
i < num_ls_lin; ++
i) {
2085 SatParameters local_params = params;
2086 local_params.set_random_seed(
2087 CombineSeed(params.random_seed(), interleaved_subsolvers.size()));
2088 local_params.set_feasibility_jump_linearization_level(2);
2089 interleaved_subsolvers.push_back(
2090 std::make_unique<FeasibilityJumpSolver>(
2092 local_params, lin_states, shared->time_limit, shared->response,
2093 shared->bounds.get(), shared->ls_hints, shared->stats,
2094 shared->stat_tables));
2105 int num_thread_available =
2106 params.num_workers() -
static_cast<int>(full_worker_subsolvers.size());
2111 if (!params.use_feasibility_jump() &&
2112 num_interleaved_subsolver_that_do_not_need_solution > 0) {
2113 --num_thread_available;
2115 num_thread_available = std::max(num_thread_available, 0);
2117 const std::vector<SatParameters> all_params =
2119 num_thread_available);
2121 std::shared_ptr<SharedLsStates> fj_states;
2122 std::shared_ptr<SharedLsStates> fj_lin_states;
2125 for (
const SatParameters& local_params : all_params) {
2126 if (local_params.use_feasibility_jump()) {
2128 std::shared_ptr<SharedLsStates> states;
2129 if (local_params.feasibility_jump_linearization_level() == 0) {
2130 if (fj_states ==
nullptr) {
2131 fj_states = std::make_shared<SharedLsStates>(
2132 local_params.name(), params, shared->stat_tables);
2136 if (fj_lin_states ==
nullptr) {
2137 fj_lin_states = std::make_shared<SharedLsStates>(
2138 local_params.name(), params, shared->stat_tables);
2140 states = fj_lin_states;
2143 interleaved_subsolvers.push_back(
2144 std::make_unique<FeasibilityJumpSolver>(
2146 get_linear_model(), local_params, states, shared->time_limit,
2147 shared->response, shared->bounds.get(), shared->ls_hints,
2148 shared->stats, shared->stat_tables));
2150 first_solution_full_subsolvers.push_back(
2151 std::make_unique<FullProblemSolver>(
2152 local_params.name(), local_params,
2153 local_params.interleave_search(), shared,
2162 const auto move_all =
2163 [&subsolvers](std::vector<std::unique_ptr<SubSolver>>& from) {
2164 for (
int i = 0;
i < from.size(); ++
i) {
2165 subsolvers.push_back(std::move(from[
i]));
2169 move_all(full_worker_subsolvers);
2170 move_all(first_solution_full_subsolvers);
2171 move_all(reentrant_interleaved_subsolvers);
2172 move_all(interleaved_subsolvers);
2177 if (shared->model_proto.has_objective() &&
2178 !shared->model_proto.objective().vars().empty()) {
2179 subsolvers.push_back(std::make_unique<SynchronizationPoint>(
2180 "update_gap_integral",
2181 [shared]() { shared->response->UpdateGapIntegral(); }));
2184 LaunchSubsolvers(params, shared, subsolvers, name_filter.AllIgnored());
2192void AddPostsolveClauses(absl::Span<const int> postsolve_mapping,
Model* model,
2193 CpModelProto* mapping_proto) {
2196 for (
const auto& clause : postsolve->clauses) {
2197 auto* ct = mapping_proto->add_constraints()->mutable_bool_or();
2198 for (
const Literal l : clause) {
2199 int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
2201 var = postsolve_mapping[var];
2202 ct->add_literals(l.IsPositive() ? var :
NegatedRef(var));
2211 const std::function<
void(
const CpSolverResponse& response)>& callback) {
2212 return [callback = callback](
Model* model) {
2218 const std::function<std::string(
const CpSolverResponse& response)>&
2220 return [callback = callback](
Model* model) {
2226 const std::function<
void(
double)>& callback) {
2227 return [callback = callback](
Model* model) {
2232#if !defined(__PORTABLE_PLATFORM__)
2235 const std::string& params) {
2236 sat::SatParameters parameters;
2237 if (!params.empty()) {
2238 CHECK(google::protobuf::TextFormat::ParseFromString(params, ¶meters))
2246 const sat::SatParameters& parameters) {
2247 return [parameters = parameters](
Model* model) {
2256 *model->GetOrCreate<SatParameters>() = parameters;
2262void RegisterSearchStatisticCallback(
Model* global_model) {
2263 global_model->GetOrCreate<SharedResponseManager>()
2264 ->AddStatisticsPostprocessor([](
Model* local_model,
2265 CpSolverResponse* response) {
2266 auto* sat_solver = local_model->Get<SatSolver>();
2267 if (sat_solver !=
nullptr) {
2268 response->set_num_booleans(sat_solver->NumVariables());
2269 response->set_num_fixed_booleans(sat_solver->NumFixedVariables());
2270 response->set_num_branches(sat_solver->num_branches());
2271 response->set_num_conflicts(sat_solver->num_failures());
2272 response->set_num_binary_propagations(sat_solver->num_propagations());
2273 response->set_num_restarts(sat_solver->num_restarts());
2275 response->set_num_booleans(0);
2276 response->set_num_fixed_booleans(0);
2277 response->set_num_branches(0);
2278 response->set_num_conflicts(0);
2279 response->set_num_binary_propagations(0);
2280 response->set_num_restarts(0);
2283 auto* integer_trail = local_model->Get<
IntegerTrail>();
2284 response->set_num_integers(
2285 integer_trail ==
nullptr
2287 : integer_trail->NumIntegerVariables().value() / 2);
2288 response->set_num_integer_propagations(
2289 integer_trail ==
nullptr ? 0 : integer_trail->num_enqueues());
2291 int64_t num_lp_iters = 0;
2292 auto* lp_constraints =
2295 num_lp_iters += lp->total_num_simplex_iterations();
2297 response->set_num_lp_iterations(num_lp_iters);
2303 auto* wall_timer = model->GetOrCreate<WallTimer>();
2305 wall_timer->Start();
2306 user_timer->Start();
2308#if !defined(__PORTABLE_PLATFORM__)
2310 if (absl::GetFlag(FLAGS_cp_model_dump_models)) {
2311 DumpModelProto(model_proto,
"model");
2313 if (absl::GetFlag(FLAGS_cp_model_export_model)) {
2314 if (model_proto.name().empty()) {
2315 DumpModelProto(model_proto,
"unnamed_model");
2317 DumpModelProto(model_proto, model_proto.name());
2322 if (!absl::GetFlag(FLAGS_cp_model_params).empty()) {
2323 SatParameters params = *model->GetOrCreate<SatParameters>();
2324 SatParameters flag_params;
2325 CHECK(google::protobuf::TextFormat::ParseFromString(
2326 absl::GetFlag(FLAGS_cp_model_params), &flag_params));
2327 params.MergeFrom(flag_params);
2328 *(model->GetOrCreate<SatParameters>()) = params;
2333 const SatParameters& params = *model->GetOrCreate<SatParameters>();
2334 SolverLogger* logger = model->GetOrCreate<SolverLogger>();
2335 logger->EnableLogging(params.log_search_progress());
2336 logger->SetLogToStdOut(params.log_to_stdout());
2337 std::string log_string;
2338 if (params.log_to_response()) {
2339 logger->AddInfoLoggingCallback([&log_string](absl::string_view message) {
2340 absl::StrAppend(&log_string, message,
"\n");
2346 absl::GetFlag(FLAGS_cp_model_dump_prefix));
2347 RegisterSearchStatisticCallback(model);
2349#if !defined(__PORTABLE_PLATFORM__)
2353 if (absl::GetFlag(FLAGS_cp_model_dump_response)) {
2354 shared_response_manager->AddFinalResponsePostprocessor(
2355 [](CpSolverResponse* response) {
2356 const std::string file = absl::StrCat(
2357 absl::GetFlag(FLAGS_cp_model_dump_prefix),
"response.pb.txt");
2358 LOG(INFO) <<
"Dumping response proto to '" << file <<
"'.";
2366 shared_response_manager->AddFinalResponsePostprocessor(
2367 [logger, &model_proto, &log_string](CpSolverResponse* response) {
2370 model_proto.has_objective() ||
2371 model_proto.has_floating_point_objective()));
2372 if (!log_string.empty()) {
2373 response->set_solve_log(log_string);
2381 shared_response_manager->AddResponsePostprocessor(
2382 [&wall_timer, &user_timer,
2383 &shared_time_limit](CpSolverResponse* response) {
2384 response->set_wall_time(wall_timer->Get());
2385 response->set_user_time(user_timer->Get());
2386 response->set_deterministic_time(
2387 shared_time_limit->GetElapsedDeterministicTime());
2396 if (!error.empty()) {
2397 SOLVER_LOG(logger,
"Invalid parameters: ", error);
2402 CpSolverResponse status_response;
2403 status_response.set_status(CpSolverStatus::MODEL_INVALID);
2404 status_response.set_solution_info(error);
2405 shared_response_manager->FillSolveStatsInResponse(model,
2407 shared_response_manager->AppendResponseToBeMerged(status_response);
2408 return shared_response_manager->GetResponse();
2413 model->GetOrCreate<TimeLimit>()->ResetLimitFromParameters(params);
2415#if !defined(__PORTABLE_PLATFORM__)
2417 if (params.catch_sigint_signal()) {
2418 model->GetOrCreate<SigintHandler>()->Register(
2419 [&shared_time_limit]() { shared_time_limit->Stop(); });
2431 if (logger->LoggingIsEnabled() && params.use_absl_random()) {
2439 if (!error.empty()) {
2440 SOLVER_LOG(logger,
"Invalid model: ", error);
2441 CpSolverResponse status_response;
2442 status_response.set_status(CpSolverStatus::MODEL_INVALID);
2443 status_response.set_solution_info(error);
2444 shared_response_manager->FillSolveStatsInResponse(model,
2446 shared_response_manager->AppendResponseToBeMerged(status_response);
2447 return shared_response_manager->GetResponse();
2457 absl::StrFormat(
"Starting presolve at %.2fs", wall_timer->Get()));
2461 google::protobuf::Arena arena;
2462 CpModelProto* new_cp_model_proto =
2463 google::protobuf::Arena::Create<CpModelProto>(&arena);
2464 CpModelProto* mapping_proto =
2465 google::protobuf::Arena::Create<CpModelProto>(&arena);
2466 auto context = std::make_unique<PresolveContext>(model, new_cp_model_proto,
2469 if (absl::GetFlag(FLAGS_debug_model_copy)) {
2470 *new_cp_model_proto = model_proto;
2473 const std::string info =
"Problem proven infeasible during initial copy.";
2475 CpSolverResponse status_response;
2476 status_response.set_status(CpSolverStatus::INFEASIBLE);
2477 status_response.set_solution_info(info);
2478 shared_response_manager->AppendResponseToBeMerged(status_response);
2479 return shared_response_manager->GetResponse();
2482 if (context->working_model->has_symmetry()) {
2483 SOLVER_LOG(logger,
"Ignoring internal symmetry field");
2484 context->working_model->clear_symmetry();
2486 if (context->working_model->has_objective()) {
2487 CpObjectiveProto* objective = context->working_model->mutable_objective();
2488 if (objective->integer_scaling_factor() != 0 ||
2489 objective->integer_before_offset() != 0 ||
2490 objective->integer_after_offset() != 0) {
2491 SOLVER_LOG(logger,
"Ignoring internal objective.integer_* fields.");
2492 objective->clear_integer_scaling_factor();
2493 objective->clear_integer_before_offset();
2494 objective->clear_integer_after_offset();
2498 if (absl::GetFlag(FLAGS_cp_model_ignore_objective) &&
2499 (context->working_model->has_objective() ||
2500 context->working_model->has_floating_point_objective())) {
2502 context->working_model->clear_objective();
2503 context->working_model->clear_floating_point_objective();
2506 if (absl::GetFlag(FLAGS_cp_model_ignore_hints) &&
2507 context->working_model->has_solution_hint()) {
2508 SOLVER_LOG(logger,
"Ignoring solution hint");
2509 context->working_model->clear_solution_hint();
2513 if (params.fix_variables_to_their_hinted_value() &&
2514 model_proto.has_solution_hint()) {
2515 SOLVER_LOG(logger,
"Fixing ", model_proto.solution_hint().vars().size(),
2516 " variables to their value in the solution hints.");
2517 for (
int i = 0;
i < model_proto.solution_hint().vars_size(); ++
i) {
2518 const int var = model_proto.solution_hint().vars(
i);
2519 const int64_t value = model_proto.solution_hint().values(
i);
2520 if (!context->IntersectDomainWith(var, Domain(value))) {
2521 const IntegerVariableProto& var_proto =
2522 context->working_model->variables(var);
2523 const std::string var_name = var_proto.name().empty()
2524 ? absl::StrCat(
"var(", var,
")")
2528 SOLVER_LOG(logger,
"Hint found infeasible when assigning variable '",
2529 var_name,
"' with domain", var_domain.ToString(),
2530 " the value ", value);
2540 bool hint_feasible_before_presolve =
false;
2541 if (!context->ModelIsUnsat()) {
2542 hint_feasible_before_presolve =
2543 SolutionHintIsCompleteAndFeasible(model_proto, logger);
2548 if (model_proto.has_floating_point_objective()) {
2549 shared_response_manager->AddFinalResponsePostprocessor(
2550 [¶ms, &model_proto, mapping_proto,
2551 &logger](CpSolverResponse* response) {
2552 if (response->solution().empty())
return;
2555 const auto& float_obj = model_proto.floating_point_objective();
2556 double value = float_obj.offset();
2557 const int num_terms = float_obj.vars().size();
2558 for (
int i = 0;
i < num_terms; ++
i) {
2559 value += float_obj.coeffs(
i) *
2560 static_cast<double>(response->solution(float_obj.vars(
i)));
2562 response->set_objective_value(value);
2567 if (!mapping_proto->has_objective())
return;
2568 const CpObjectiveProto& integer_obj = mapping_proto->objective();
2569 *response->mutable_integer_objective() = integer_obj;
2574 if (params.mip_compute_true_objective_bound() &&
2575 !integer_obj.scaling_was_exact()) {
2576 const int64_t integer_lb = response->inner_objective_lower_bound();
2578 model_proto, integer_obj, integer_lb);
2579 SOLVER_LOG(logger,
"[Scaling] scaled_objective_bound: ",
2580 response->best_objective_bound(),
2581 " corrected_bound: ", lb,
2582 " delta: ", response->best_objective_bound() - lb);
2586 if (float_obj.maximize()) {
2587 response->set_best_objective_bound(
2588 std::max(lb, response->objective_value()));
2590 response->set_best_objective_bound(
2591 std::min(lb, response->objective_value()));
2597 if (response->status() == CpSolverStatus::OPTIMAL) {
2598 const double gap = std::abs(response->objective_value() -
2599 response->best_objective_bound());
2600 if (gap > params.absolute_gap_limit()) {
2602 "[Scaling] Warning: OPTIMAL was reported, yet the "
2604 gap,
") is greater than requested absolute limit (",
2605 params.absolute_gap_limit(),
").");
2611 if (!model_proto.assumptions().empty() &&
2612 (params.num_workers() > 1 || model_proto.has_objective() ||
2613 model_proto.has_floating_point_objective() ||
2614 params.enumerate_all_solutions())) {
2617 "Warning: solving with assumptions was requested in a non-fully "
2618 "supported setting.\nWe will assumes these assumptions true while "
2619 "solving, but if the model is infeasible, you will not get a useful "
2620 "'sufficient_assumptions_for_infeasibility' field in the response, it "
2621 "will include all assumptions.");
2629 shared_response_manager->AddFinalResponsePostprocessor(
2630 [&model_proto](CpSolverResponse* response) {
2631 if (response->status() != CpSolverStatus::INFEASIBLE)
return;
2634 *response->mutable_sufficient_assumptions_for_infeasibility() =
2635 model_proto.assumptions();
2639 new_cp_model_proto->clear_assumptions();
2641 context->InitializeNewDomains();
2642 for (
const int ref : model_proto.assumptions()) {
2643 if (!context->SetLiteralToTrue(ref)) {
2644 CpSolverResponse status_response;
2645 status_response.set_status(CpSolverStatus::INFEASIBLE);
2646 status_response.add_sufficient_assumptions_for_infeasibility(ref);
2647 shared_response_manager->FillSolveStatsInResponse(model,
2649 shared_response_manager->AppendResponseToBeMerged(status_response);
2650 return shared_response_manager->GetResponse();
2656 std::vector<int> postsolve_mapping;
2657 const CpSolverStatus presolve_status =
2662 context.reset(
nullptr);
2664 if (presolve_status != CpSolverStatus::UNKNOWN) {
2665 if (presolve_status == CpSolverStatus::INFEASIBLE &&
2666 hint_feasible_before_presolve &&
2667 params.debug_crash_if_presolve_breaks_hint()) {
2668 LOG(FATAL) <<
"Presolve bug: model with feasible hint found UNSAT "
2671 SOLVER_LOG(logger,
"Problem closed by presolve.");
2672 CpSolverResponse status_response;
2673 status_response.set_status(presolve_status);
2674 shared_response_manager->FillSolveStatsInResponse(model, &status_response);
2675 shared_response_manager->AppendResponseToBeMerged(status_response);
2676 return shared_response_manager->GetResponse();
2687 if (params.symmetry_level() > 1 && !params.stop_after_presolve() &&
2688 !shared_time_limit->LimitReached()) {
2689 if (params.keep_symmetry_in_presolve() &&
2690 new_cp_model_proto->has_symmetry()) {
2702 if (params.fill_tightened_domains_in_response()) {
2703 shared_response_manager->AddResponsePostprocessor(
2704 [&model_proto, new_cp_model_proto, mapping_proto, &postsolve_mapping,
2705 logger, &shared](CpSolverResponse* response) {
2709 std::vector<Domain> bounds;
2710 for (
const IntegerVariableProto& vars :
2711 new_cp_model_proto->variables()) {
2716 if (shared.bounds !=
nullptr) {
2717 shared.bounds->UpdateDomains(&bounds);
2722 postsolve_mapping, bounds, response,
2730 auto check_solution = [&model_proto, ¶ms, mapping_proto,
2731 &postsolve_mapping](
const CpSolverResponse& response) {
2732 if (response.solution().empty())
return;
2734 bool solution_is_feasible =
true;
2735 if (params.cp_model_presolve()) {
2739 model_proto, response.solution(), mapping_proto, &postsolve_mapping);
2741 solution_is_feasible =
2746 if (!solution_is_feasible) {
2747 const std::string file = absl::StrCat(
2748 absl::GetFlag(FLAGS_cp_model_dump_prefix),
"wrong_response.pb.txt");
2749 LOG(INFO) <<
"Dumping infeasible response proto to '" << file <<
"'.";
2753 LOG(FATAL) <<
"Infeasible solution!"
2754 <<
" source': " << response.solution_info() <<
"'"
2755 <<
" dumped CpSolverResponse to '" << file <<
"'.";
2759 absl::GetFlag(FLAGS_cp_model_check_intermediate_solutions)) {
2760 shared_response_manager->AddSolutionCallback(std::move(check_solution));
2762 shared_response_manager->AddFinalResponsePostprocessor(
2763 [checker = std::move(check_solution)](CpSolverResponse* response) {
2769 if (params.cp_model_presolve()) {
2770 shared_response_manager->AddSolutionPostprocessor(
2771 [&model_proto, ¶ms, mapping_proto, &model,
2772 &postsolve_mapping](std::vector<int64_t>*
solution) {
2773 AddPostsolveClauses(postsolve_mapping, model, mapping_proto);
2775 *mapping_proto, postsolve_mapping,
solution);
2777 shared_response_manager->AddResponsePostprocessor(
2778 [&postsolve_mapping](CpSolverResponse* response) {
2782 ->mutable_sufficient_assumptions_for_infeasibility())) {
2784 ? postsolve_mapping[ref]
2789 shared_response_manager->AddResponsePostprocessor(
2790 [&model_proto](CpSolverResponse* response) {
2792 const int initial_size = model_proto.variables_size();
2793 if (!response->solution().empty()) {
2794 response->mutable_solution()->Truncate(initial_size);
2800 if (params.stop_after_first_solution()) {
2801 shared_response_manager->AddSolutionCallback(
2802 [shared_time_limit](
const CpSolverResponse&) {
2803 shared_time_limit->Stop();
2807#if !defined(__PORTABLE_PLATFORM__)
2808 if (absl::GetFlag(FLAGS_cp_model_dump_models)) {
2809 DumpModelProto(*new_cp_model_proto,
"presolved_model");
2810 DumpModelProto(*mapping_proto,
"mapping_model");
2815 MPModelProto mip_model;
2817 DumpModelProto(mip_model,
"presolved_mp_model");
2821 std::string cnf_string;
2823 const std::string filename = absl::StrCat(
2824 absl::GetFlag(FLAGS_cp_model_dump_prefix),
"presolved_cnf_model.cnf");
2825 LOG(INFO) <<
"Dumping cnf model to '" << filename <<
"'.";
2826 const absl::Status status =
2828 if (!status.ok()) LOG(ERROR) << status;
2833 if (params.stop_after_presolve() || shared_time_limit->LimitReached()) {
2834 int64_t num_terms = 0;
2835 for (
const ConstraintProto& ct : new_cp_model_proto->constraints()) {
2839 logger,
"Stopped after presolve.",
2840 "\nPresolvedNumVariables: ", new_cp_model_proto->variables().size(),
2841 "\nPresolvedNumConstraints: ", new_cp_model_proto->constraints().size(),
2842 "\nPresolvedNumTerms: ", num_terms);
2844 CpSolverResponse status_response;
2845 shared_response_manager->FillSolveStatsInResponse(model, &status_response);
2846 shared_response_manager->AppendResponseToBeMerged(status_response);
2847 return shared_response_manager->GetResponse();
2857 if (new_cp_model_proto->has_objective()) {
2858 shared_response_manager->InitializeObjective(*new_cp_model_proto);
2859 shared_response_manager->SetGapLimitsFromParameters(params);
2864 shared_response_manager->UpdateGapIntegral();
2877 bool hint_feasible_after_presolve;
2878 if (!params.enumerate_all_solutions()) {
2879 hint_feasible_after_presolve = SolutionHintIsCompleteAndFeasible(
2880 *new_cp_model_proto, logger, shared_response_manager);
2882 hint_feasible_after_presolve =
2883 SolutionHintIsCompleteAndFeasible(*new_cp_model_proto, logger,
nullptr);
2886 if (hint_feasible_before_presolve && !hint_feasible_after_presolve &&
2887 params.debug_crash_if_presolve_breaks_hint()) {
2888 LOG(FATAL) <<
"Presolve broke a feasible hint";
2893 if (!model->GetOrCreate<TimeLimit>()->LimitReached()) {
2894#if defined(__PORTABLE_PLATFORM__)
2898 if (params.num_workers() > 1 || params.interleave_search() ||
2899 !params.subsolvers().empty() || !params.filter_subsolvers().empty() ||
2900 params.use_ls_only()) {
2901 SolveCpModelParallel(&shared, model);
2904 shared_response_manager->SetUpdateGapIntegralOnEachChange(
true);
2908 std::vector<std::unique_ptr<SubSolver>> subsolvers;
2909 subsolvers.push_back(std::make_unique<FullProblemSolver>(
2910 "main", params,
false, &shared));
2911 LaunchSubsolvers(params, &shared, subsolvers, {});
2915 return shared_response_manager->GetResponse();
2918CpSolverResponse
Solve(
const CpModelProto& model_proto) {
2924 const SatParameters& params) {
2930#if !defined(__PORTABLE_PLATFORM__)
2932 const std::string& params) {
2940 model->GetOrCreate<SharedResponseManager>()->InitializeObjective(model_proto);
Domain IntersectionWith(const Domain &domain) const
A class that stores the collection of all LP constraints in a model.
The model "singleton" shared time limit.
T Add(std::function< T(Model *)> f)
void set_dump_prefix(absl::string_view dump_prefix)
Debug only. Set dump prefix for solutions written to file.
Simple class used to filter executed subsolver names.
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.")
absl::Status SetContents(absl::string_view filename, absl::string_view contents, Options options)
CpSolverResponse Solve(const CpModelProto &model_proto)
Solves the given CpModelProto and returns an instance of CpSolverResponse.
std::function< SatParameters(Model *)> NewSatParameters(const std::string ¶ms)
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 ¶ms, const CpModelProto &model)
bool RefIsPositive(int ref)
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)
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)
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 DeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, int num_threads, int batch_size, int max_num_batches)
std::string ValidateParameters(const SatParameters ¶ms)
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).
std::vector< SatParameters > GetFirstSolutionBaseParams(const SatParameters &base_params)
void NonDeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, const int num_threads, ModelSharedTimeLimit *time_limit)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
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 ¶ms)
Solves the given CpModelProto with the given parameters.
void DetectAndAddSymmetryToProto(const SatParameters ¶ms, 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)
bool ConvertCpModelProtoToMPModelProto(const CpModelProto &input, MPModelProto *output)
void RegisterVariableBoundsLevelZeroExport(const CpModelProto &, SharedBoundsManager *shared_bounds_manager, Model *model)
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &callback)
void PostsolveResponseWrapper(const SatParameters ¶ms, int num_variable_in_original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, std::vector< int64_t > *solution)
PushedSolutionPointers PushAndMaybeCombineSolution(SharedResponseManager *response_manager, const CpModelProto &model_proto, absl::Span< const int64_t > new_solution, const std::string &solution_info, absl::Span< const int64_t > base_solution, Model *model)
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)
void FillTightenedDomainInResponse(const CpModelProto &original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, absl::Span< const Domain > search_domains, CpSolverResponse *response, SolverLogger *logger)
std::string CpSolverResponseStats(const CpSolverResponse &response, bool has_objective)
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
std::string RenderDot(std::optional< Rectangle > bb, absl::Span< const Rectangle > solution, std::string_view extra_dot_payload)
In SWIG mode, we don't want anything besides these top-level includes.
std::string OrToolsVersionString()
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
std::mt19937_64 random_engine_t
std::string ProtoEnumToString(ProtoEnumType enum_value)
std::string ProtobufShortDebugString(const P &message)
absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
static int input(yyscan_t yyscanner)
bool Contains(int64_t value) const
Various inclusion tests on a domain.
std::deque< std::vector< Literal > > clauses
#define SOLVER_LOG(logger,...)