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