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