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