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