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