Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_solver_helpers.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <array>
18#include <cmath>
19#include <cstdint>
20#include <cstdlib>
21#include <memory>
22#include <string>
23#include <thread>
24#include <tuple>
25#include <utility>
26#include <vector>
27
29#include "ortools/base/timer.h"
30#if !defined(__PORTABLE_PLATFORM__)
33#endif // __PORTABLE_PLATFORM__
34#include "absl/cleanup/cleanup.h"
35#include "absl/container/flat_hash_set.h"
36#include "absl/flags/flag.h"
37#include "absl/log/check.h"
38#include "absl/strings/escaping.h"
39#include "absl/strings/str_cat.h"
40#include "absl/strings/string_view.h"
41#include "absl/time/time.h"
42#include "absl/types/span.h"
43#include "google/protobuf/arena.h"
48#include "ortools/sat/clause.h"
49#include "ortools/sat/cp_model.pb.h"
56#include "ortools/sat/cuts.h"
59#include "ortools/sat/integer.h"
67#include "ortools/sat/max_hs.h"
68#include "ortools/sat/model.h"
71#include "ortools/sat/probing.h"
73#include "ortools/sat/sat_parameters.pb.h"
76#include "ortools/sat/util.h"
79#if !defined(__PORTABLE_PLATFORM__)
80#endif // __PORTABLE_PLATFORM__
85
86ABSL_FLAG(bool, cp_model_dump_models, false,
87 "DEBUG ONLY. When set to true, SolveCpModel() will dump its model "
88 "protos (original model, presolved model, mapping model) in text "
89 "format to 'FLAGS_cp_model_dump_prefix'{model|presolved_model|"
90 "mapping_model}.pb.txt.");
91
92#if defined(_MSC_VER)
93ABSL_FLAG(std::string, cp_model_dump_prefix, ".\\",
94 "Prefix filename for all dumped files");
95#else
96ABSL_FLAG(std::string, cp_model_dump_prefix, "/tmp/",
97 "Prefix filename for all dumped files");
98#endif
99
100ABSL_FLAG(bool, cp_model_dump_submodels, false,
101 "DEBUG ONLY. When set to true, solve will dump all "
102 "lns or objective_shaving submodels proto in text format to "
103 "'FLAGS_cp_model_dump_prefix'xxx.pb.txt.");
104
106 std::string, cp_model_load_debug_solution, "",
107 "DEBUG ONLY. When this is set to a non-empty file name, "
108 "we will interpret this as an internal solution which can be used for "
109 "debugging. For instance we use it to identify wrong cuts/reasons.");
110
111ABSL_FLAG(bool, cp_model_check_intermediate_solutions, false,
112 "When true, all intermediate solutions found by the solver will be "
113 "checked. This can be expensive, therefore it is off by default.");
114
115namespace operations_research {
116namespace sat {
117
118// This should be called on the presolved model. It will read the file
119// specified by --cp_model_load_debug_solution and properly fill the
120// model->Get<DebugSolution>() proto vector.
121void LoadDebugSolution(const CpModelProto& model_proto, Model* model) {
122#if !defined(__PORTABLE_PLATFORM__)
123 if (absl::GetFlag(FLAGS_cp_model_load_debug_solution).empty()) return;
124
125 CpSolverResponse response;
126 SOLVER_LOG(model->GetOrCreate<SolverLogger>(),
127 "Reading debug solution from '",
128 absl::GetFlag(FLAGS_cp_model_load_debug_solution), "'.");
129 CHECK_OK(file::GetTextProto(absl::GetFlag(FLAGS_cp_model_load_debug_solution),
130 &response, file::Defaults()));
131
132 // Make sure we load a solution with the same number of variable has in the
133 // presolved model.
134 CHECK_EQ(response.solution().size(), model_proto.variables().size());
136 response.solution());
137#endif // __PORTABLE_PLATFORM__
138}
139
140// This both copy the "main" DebugSolution to a local_model and also cache
141// the value of the integer variables in that solution.
142void InitializeDebugSolution(const CpModelProto& model_proto, Model* model) {
143 auto* shared_response = model->Get<SharedResponseManager>();
144 if (shared_response == nullptr) return;
145 if (shared_response->DebugSolution().empty()) return;
146
147 // Copy the proto values.
148 DebugSolution& debug_sol = *model->GetOrCreate<DebugSolution>();
149 debug_sol.proto_values = shared_response->DebugSolution();
150
151 // Fill the values by integer variable.
152 const int num_integers =
153 model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value();
154 debug_sol.ivar_has_value.assign(num_integers, false);
155 debug_sol.ivar_values.assign(num_integers, 0);
156
157 std::vector<Literal> boolean_solution;
158
159 const auto& mapping = *model->GetOrCreate<CpModelMapping>();
160 for (int i = 0; i < debug_sol.proto_values.size(); ++i) {
161 if (mapping.IsBoolean(i)) {
162 Literal l = mapping.Literal(i);
163 if (debug_sol.proto_values[i] == 0) {
164 l = l.Negated();
165 }
166 boolean_solution.push_back(l);
167 }
168
169 if (!mapping.IsInteger(i)) continue;
170 const IntegerVariable var = mapping.Integer(i);
171 debug_sol.ivar_has_value[var] = true;
172 debug_sol.ivar_has_value[NegationOf(var)] = true;
173 debug_sol.ivar_values[var] = debug_sol.proto_values[i];
174 debug_sol.ivar_values[NegationOf(var)] = -debug_sol.proto_values[i];
175 }
176
177 // If the solution is fully boolean (there is no integer variable), and
178 // we have a decision problem (so no new boolean should be created), we load
179 // it in the sat solver for debugging too.
180 if (boolean_solution.size() == debug_sol.proto_values.size() &&
181 !model_proto.has_objective()) {
182 LOG(INFO) << "Loaded pure Boolean debugging solution.";
183 model->GetOrCreate<SatSolver>()->LoadDebugSolution(boolean_solution);
184 }
185
186 // The objective variable is usually not part of the proto, but it is still
187 // nice to have it, so we recompute it here.
188 auto* objective_def = model->Get<ObjectiveDefinition>();
189 if (objective_def != nullptr) {
190 const IntegerVariable objective_var = objective_def->objective_var;
191 const int64_t objective_value =
192 ComputeInnerObjective(model_proto.objective(), debug_sol.proto_values);
193 debug_sol.ivar_has_value[objective_var] = true;
194 debug_sol.ivar_has_value[NegationOf(objective_var)] = true;
195 debug_sol.ivar_values[objective_var] = objective_value;
196 debug_sol.ivar_values[NegationOf(objective_var)] = -objective_value;
197 }
198
199 // We also register a DEBUG callback to check our reasons.
200 auto* encoder = model->GetOrCreate<IntegerEncoder>();
201 const auto checker = [mapping, encoder, debug_sol, model](
202 absl::Span<const Literal> clause,
203 absl::Span<const IntegerLiteral> integers) {
204 bool is_satisfied = false;
205 int num_bools = 0;
206 int num_ints = 0;
207 std::vector<std::tuple<Literal, IntegerLiteral, int>> to_print;
208 for (const Literal l : clause) {
209 // First case, this Boolean is mapped.
210 {
211 const int proto_var =
212 mapping.GetProtoVariableFromBooleanVariable(l.Variable());
213 if (proto_var != -1) {
214 to_print.push_back({l, IntegerLiteral(), proto_var});
215 if (debug_sol.proto_values[proto_var] == (l.IsPositive() ? 1 : 0)) {
216 is_satisfied = true;
217 break;
218 }
219 ++num_bools;
220 continue;
221 }
222 }
223
224 // Second case, it is associated to IntVar >= value.
225 // We can use any of them, so if one is false, we use this one.
226 bool all_true = true;
227 for (const IntegerLiteral associated : encoder->GetIntegerLiterals(l)) {
228 const int proto_var = mapping.GetProtoVariableFromIntegerVariable(
229 PositiveVariable(associated.var));
230 if (proto_var == -1) break;
231 int64_t value = debug_sol.proto_values[proto_var];
232 to_print.push_back({l, associated, proto_var});
233
234 if (!VariableIsPositive(associated.var)) value = -value;
235 if (value < associated.bound) {
236 ++num_ints;
237 all_true = false;
238 break;
239 }
240 }
241 if (all_true) {
242 is_satisfied = true;
243 break;
244 }
245 }
246 for (const IntegerLiteral i_lit : integers) {
247 const int proto_var = mapping.GetProtoVariableFromIntegerVariable(
248 PositiveVariable(i_lit.var));
249 if (proto_var == -1) {
250 is_satisfied = true;
251 break;
252 }
253
254 int64_t value = debug_sol.proto_values[proto_var];
255 to_print.push_back({Literal(kNoLiteralIndex), i_lit, proto_var});
256
257 if (!VariableIsPositive(i_lit.var)) value = -value;
258 // Note the sign is inversed, we cannot have all literal false and all
259 // integer literal true.
260 if (value >= i_lit.bound) {
261 is_satisfied = true;
262 break;
263 }
264 }
265 if (!is_satisfied) {
266 LOG(INFO) << "Reason clause is not satisfied by loaded solution:";
267 LOG(INFO) << "Worker '" << model->Name() << "', level="
268 << model->GetOrCreate<SatSolver>()->CurrentDecisionLevel();
269 LOG(INFO) << "literals (neg): " << clause;
270 LOG(INFO) << "integer literals: " << integers;
271 for (const auto [l, i_lit, proto_var] : to_print) {
272 LOG(INFO) << l << " " << i_lit << " var=" << proto_var
273 << " value_in_sol=" << debug_sol.proto_values[proto_var];
274 }
275 }
276 return is_satisfied;
277 };
278 const auto lit_checker = [checker](absl::Span<const Literal> clause) {
279 return checker(clause, {});
280 };
281
282 model->GetOrCreate<Trail>()->RegisterDebugChecker(lit_checker);
283 model->GetOrCreate<IntegerTrail>()->RegisterDebugChecker(checker);
284}
285
286std::vector<int64_t> GetSolutionValues(const CpModelProto& model_proto,
287 const Model& model) {
288 auto* mapping = model.Get<CpModelMapping>();
289 auto* trail = model.Get<Trail>();
290
291 std::vector<int64_t> solution;
292 for (int i = 0; i < model_proto.variables_size(); ++i) {
293 if (mapping->IsInteger(i)) {
294 const IntegerVariable var = mapping->Integer(i);
295
296 // For ignored or not fully instantiated variable, we just use the
297 // lower bound.
298 solution.push_back(model.Get(LowerBound(var)));
299 } else {
300 DCHECK(mapping->IsBoolean(i));
301 const Literal literal = mapping->Literal(i);
302 if (trail->Assignment().LiteralIsAssigned(literal)) {
303 solution.push_back(model.Get(Value(literal)));
304 } else {
305 // Just use the lower bound if the variable is not fully instantiated.
306 solution.push_back(0);
307 }
308 }
309 }
310
311 if (DEBUG_MODE ||
312 absl::GetFlag(FLAGS_cp_model_check_intermediate_solutions)) {
313 // TODO(user): Checks against initial model.
314 CHECK(SolutionIsFeasible(model_proto, solution));
315 }
316 return solution;
317}
318
319namespace {
320
321IntegerVariable GetOrCreateVariableWithTightBound(
322 const std::vector<std::pair<IntegerVariable, int64_t>>& terms,
323 Model* model) {
324 if (terms.empty()) return model->Add(ConstantIntegerVariable(0));
325 if (terms.size() == 1 && terms.front().second == 1) {
326 return terms.front().first;
327 }
328 if (terms.size() == 1 && terms.front().second == -1) {
329 return NegationOf(terms.front().first);
330 }
331
332 int64_t sum_min = 0;
333 int64_t sum_max = 0;
334 for (const std::pair<IntegerVariable, int64_t>& var_coeff : terms) {
335 const int64_t min_domain = model->Get(LowerBound(var_coeff.first));
336 const int64_t max_domain = model->Get(UpperBound(var_coeff.first));
337 const int64_t coeff = var_coeff.second;
338 const int64_t prod1 = min_domain * coeff;
339 const int64_t prod2 = max_domain * coeff;
340 sum_min += std::min(prod1, prod2);
341 sum_max += std::max(prod1, prod2);
342 }
343 return model->Add(NewIntegerVariable(sum_min, sum_max));
344}
345
346IntegerVariable GetOrCreateVariableLinkedToSumOf(
347 const std::vector<std::pair<IntegerVariable, int64_t>>& terms,
348 bool lb_required, bool ub_required, Model* model) {
349 if (terms.empty()) return model->Add(ConstantIntegerVariable(0));
350 if (terms.size() == 1 && terms.front().second == 1) {
351 return terms.front().first;
352 }
353 if (terms.size() == 1 && terms.front().second == -1) {
354 return NegationOf(terms.front().first);
355 }
356
357 const IntegerVariable new_var =
358 GetOrCreateVariableWithTightBound(terms, model);
359
360 // TODO(user): use the same format, i.e. LinearExpression in both code!
361 std::vector<IntegerVariable> vars;
362 std::vector<int64_t> coeffs;
363 for (const auto [var, coeff] : terms) {
364 vars.push_back(var);
365 coeffs.push_back(coeff);
366 }
367 vars.push_back(new_var);
368 coeffs.push_back(-1);
369
370 // Split if linear is large.
371 if (vars.size() > model->GetOrCreate<SatParameters>()->linear_split_size()) {
372 SplitAndLoadIntermediateConstraints(lb_required, ub_required, &vars,
373 &coeffs, model);
374 }
375
376 // Load the top-level constraint with the required sides.
377 if (lb_required) {
378 model->Add(WeightedSumGreaterOrEqual(vars, coeffs, 0));
379 }
380 if (ub_required) {
381 model->Add(WeightedSumLowerOrEqual(vars, coeffs, 0));
382 }
383
384 return new_var;
385}
386
387} // namespace
388
389// Adds one LinearProgrammingConstraint per connected component of the model.
390IntegerVariable AddLPConstraints(bool objective_need_to_be_tight,
391 const CpModelProto& model_proto, Model* m) {
392 // Non const as we will std::move() stuff out of there.
393 LinearRelaxation relaxation = ComputeLinearRelaxation(model_proto, m);
395
396 // The bipartite graph of LP constraints might be disconnected:
397 // make a partition of the variables into connected components.
398 // Constraint nodes are indexed by [0..num_lp_constraints),
399 // variable nodes by [num_lp_constraints..num_lp_constraints+num_variables).
400 //
401 // TODO(user): look into biconnected components.
402 const int num_lp_constraints =
403 static_cast<int>(relaxation.linear_constraints.size());
404 const int num_lp_cut_generators =
405 static_cast<int>(relaxation.cut_generators.size());
406 const int num_integer_variables =
407 m->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value();
408
410 components.SetNumberOfNodes(num_lp_constraints + num_lp_cut_generators +
411 num_integer_variables);
412 auto get_constraint_index = [](int ct_index) { return ct_index; };
413 auto get_cut_generator_index = [num_lp_constraints](int cut_index) {
414 return num_lp_constraints + cut_index;
415 };
416 auto get_var_index = [num_lp_constraints,
417 num_lp_cut_generators](IntegerVariable var) {
418 return num_lp_constraints + num_lp_cut_generators +
419 PositiveVariable(var).value();
420 };
421 for (int i = 0; i < num_lp_constraints; i++) {
422 for (const IntegerVariable var :
423 relaxation.linear_constraints[i].VarsAsSpan()) {
424 components.AddEdge(get_constraint_index(i), get_var_index(var));
425 }
426 }
427 for (int i = 0; i < num_lp_cut_generators; ++i) {
428 for (const IntegerVariable var : relaxation.cut_generators[i].vars) {
429 components.AddEdge(get_cut_generator_index(i), get_var_index(var));
430 }
431 }
432
433 const int num_components = components.GetNumberOfComponents();
434 std::vector<int> component_sizes(num_components, 0);
435 const std::vector<int> index_to_component = components.GetComponentIds();
436 for (int i = 0; i < num_lp_constraints; i++) {
437 ++component_sizes[index_to_component[get_constraint_index(i)]];
438 }
439 for (int i = 0; i < num_lp_cut_generators; i++) {
440 ++component_sizes[index_to_component[get_cut_generator_index(i)]];
441 }
442
443 // TODO(user): Optimize memory layout.
444 std::vector<std::vector<IntegerVariable>> component_to_var(num_components);
445 for (IntegerVariable var(0); var < num_integer_variables; var += 2) {
446 DCHECK(VariableIsPositive(var));
447 component_to_var[index_to_component[get_var_index(var)]].push_back(var);
448 }
449
450 // Make sure any constraint that touch the objective is not discarded even
451 // if it is the only one in its component. This is important to propagate
452 // as much as possible the objective bound by using any bounds the LP give
453 // us on one of its components. This is critical on the zephyrus problems for
454 // instance.
455 auto* mapping = m->GetOrCreate<CpModelMapping>();
456 for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
457 const IntegerVariable var =
458 mapping->Integer(model_proto.objective().vars(i));
459 ++component_sizes[index_to_component[get_var_index(var)]];
460 }
461
462 // Dispatch every constraint to its LinearProgrammingConstraint.
463 std::vector<LinearProgrammingConstraint*> lp_constraints(num_components,
464 nullptr);
465 for (int i = 0; i < num_lp_constraints; i++) {
466 const int c = index_to_component[get_constraint_index(i)];
467 if (component_sizes[c] <= 1) continue;
468 if (lp_constraints[c] == nullptr) {
469 lp_constraints[c] =
470 new LinearProgrammingConstraint(m, component_to_var[c]);
471 m->TakeOwnership(lp_constraints[c]);
472 }
473 // Load the constraint.
474 lp_constraints[c]->AddLinearConstraint(
475 std::move(relaxation.linear_constraints[i]));
476 }
477
478 // Dispatch every cut generator to its LinearProgrammingConstraint.
479 for (int i = 0; i < num_lp_cut_generators; i++) {
480 const int c = index_to_component[get_cut_generator_index(i)];
481 if (lp_constraints[c] == nullptr) {
482 lp_constraints[c] =
483 new LinearProgrammingConstraint(m, component_to_var[c]);
484 m->TakeOwnership(lp_constraints[c]);
485 }
486 lp_constraints[c]->AddCutGenerator(std::move(relaxation.cut_generators[i]));
487 }
488
489 // Add the objective.
490 std::vector<std::vector<std::pair<IntegerVariable, int64_t>>>
491 component_to_cp_terms(num_components);
492 std::vector<std::pair<IntegerVariable, int64_t>> top_level_cp_terms;
493 int num_components_containing_objective = 0;
494 if (model_proto.has_objective()) {
495 // First pass: set objective coefficients on the lp constraints, and store
496 // the cp terms in one vector per component.
497 for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
498 const IntegerVariable var =
499 mapping->Integer(model_proto.objective().vars(i));
500 const int64_t coeff = model_proto.objective().coeffs(i);
501 const int c = index_to_component[get_var_index(var)];
502 if (lp_constraints[c] != nullptr) {
503 lp_constraints[c]->SetObjectiveCoefficient(var, IntegerValue(coeff));
504 component_to_cp_terms[c].push_back(std::make_pair(var, coeff));
505 } else {
506 // Component is too small. We still need to store the objective term.
507 top_level_cp_terms.push_back(std::make_pair(var, coeff));
508 }
509 }
510 // Second pass: Build the cp sub-objectives per component.
511 for (int c = 0; c < num_components; ++c) {
512 if (component_to_cp_terms[c].empty()) continue;
513 const IntegerVariable sub_obj_var = GetOrCreateVariableLinkedToSumOf(
514 component_to_cp_terms[c], objective_need_to_be_tight, true, m);
515 top_level_cp_terms.push_back(std::make_pair(sub_obj_var, 1));
516 lp_constraints[c]->SetMainObjectiveVariable(sub_obj_var);
517 num_components_containing_objective++;
518 }
519 }
520
521 const IntegerVariable main_objective_var =
522 model_proto.has_objective()
523 ? GetOrCreateVariableLinkedToSumOf(
524 top_level_cp_terms, objective_need_to_be_tight, true, m)
526
527 // Register LP constraints. Note that this needs to be done after all the
528 // constraints have been added.
529 for (LinearProgrammingConstraint* lp_constraint : lp_constraints) {
530 if (lp_constraint == nullptr) continue;
531 lp_constraint->RegisterWith(m);
532 VLOG(3) << "LP constraint: " << lp_constraint->DimensionString() << ".";
533 }
534
535 VLOG(3) << top_level_cp_terms.size()
536 << " terms in the main objective linear equation ("
537 << num_components_containing_objective << " from LP constraints).";
538 return main_objective_var;
539}
540
541// Registers a callback that will export variables bounds fixed at level 0 of
542// the search. This should not be registered to a LNS search.
544 const CpModelProto& /*model_proto*/,
545 SharedBoundsManager* shared_bounds_manager, Model* model) {
546 CHECK(shared_bounds_manager != nullptr);
547
548 auto* mapping = model->GetOrCreate<CpModelMapping>();
549 auto* trail = model->Get<Trail>();
550 auto* integer_trail = model->Get<IntegerTrail>();
551
552 int saved_trail_index = 0;
553 std::vector<int> model_variables;
554 std::vector<int64_t> new_lower_bounds;
555 std::vector<int64_t> new_upper_bounds;
556 absl::flat_hash_set<int> visited_variables;
557 const std::string name = model->Name();
558
559 auto broadcast_level_zero_bounds =
560 [=](const std::vector<IntegerVariable>& modified_vars) mutable {
561 // Inspect the modified IntegerVariables.
562 for (const IntegerVariable& var : modified_vars) {
563 const IntegerVariable positive_var = PositiveVariable(var);
564 const int model_var =
565 mapping->GetProtoVariableFromIntegerVariable(positive_var);
566
567 if (model_var == -1) continue;
568 const auto [_, inserted] = visited_variables.insert(model_var);
569 if (!inserted) continue;
570
571 const int64_t new_lb =
572 integer_trail->LevelZeroLowerBound(positive_var).value();
573 const int64_t new_ub =
574 integer_trail->LevelZeroUpperBound(positive_var).value();
575
576 // TODO(user): We could imagine an API based on atomic<int64_t>
577 // that could preemptively check if this new bounds are improving.
578 model_variables.push_back(model_var);
579 new_lower_bounds.push_back(new_lb);
580 new_upper_bounds.push_back(new_ub);
581 }
582
583 // Inspect the newly modified Booleans.
584 for (; saved_trail_index < trail->Index(); ++saved_trail_index) {
585 const Literal fixed_literal = (*trail)[saved_trail_index];
586 const int model_var = mapping->GetProtoVariableFromBooleanVariable(
587 fixed_literal.Variable());
588
589 if (model_var == -1) continue;
590 const auto [_, inserted] = visited_variables.insert(model_var);
591 if (!inserted) continue;
592
593 model_variables.push_back(model_var);
594 if (fixed_literal.IsPositive()) {
595 new_lower_bounds.push_back(1);
596 new_upper_bounds.push_back(1);
597 } else {
598 new_lower_bounds.push_back(0);
599 new_upper_bounds.push_back(0);
600 }
601 }
602
603 if (!model_variables.empty()) {
604 shared_bounds_manager->ReportPotentialNewBounds(
605 model->Name(), model_variables, new_lower_bounds,
606 new_upper_bounds);
607
608 // Clear for next call.
609 model_variables.clear();
610 new_lower_bounds.clear();
611 new_upper_bounds.clear();
612 visited_variables.clear();
613
614 // If we are not in interleave_search we synchronize right away.
615 if (!model->Get<SatParameters>()->interleave_search()) {
616 shared_bounds_manager->Synchronize();
617 }
618 }
619 };
620
621 // The callback will just be called on NEWLY modified var. So initially,
622 // we do want to read all variables.
623 //
624 // TODO(user): Find a better way? It seems nicer to register this before
625 // any variable is modified. But then we don't want to call it each time
626 // we reach level zero during probing. It should be better to only call
627 // it when a new variable has been fixed.
628 const IntegerVariable num_vars =
629 model->GetOrCreate<IntegerTrail>()->NumIntegerVariables();
630 std::vector<IntegerVariable> all_variables;
631 all_variables.reserve(num_vars.value());
632 for (IntegerVariable var(0); var < num_vars; ++var) {
633 all_variables.push_back(var);
634 }
635 broadcast_level_zero_bounds(all_variables);
636
637 model->GetOrCreate<GenericLiteralWatcher>()
638 ->RegisterLevelZeroModifiedVariablesCallback(broadcast_level_zero_bounds);
639}
640
641// Registers a callback to import new variables bounds stored in the
642// shared_bounds_manager. These bounds are imported at level 0 of the search
643// in the linear scan minimize function.
645 const CpModelProto& model_proto, SharedBoundsManager* shared_bounds_manager,
646 Model* model) {
647 CHECK(shared_bounds_manager != nullptr);
648 const std::string name = model->Name();
649 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
650 auto* trail = model->GetOrCreate<Trail>();
651 auto* sat_solver = model->GetOrCreate<SatSolver>();
652 auto* mapping = model->GetOrCreate<CpModelMapping>();
653 const int id = shared_bounds_manager->RegisterNewId();
654
655 const auto& import_level_zero_bounds = [&model_proto, shared_bounds_manager,
656 name, sat_solver, integer_trail,
657 trail, id, mapping]() {
658 std::vector<int> model_variables;
659 std::vector<int64_t> new_lower_bounds;
660 std::vector<int64_t> new_upper_bounds;
661 shared_bounds_manager->GetChangedBounds(
662 id, &model_variables, &new_lower_bounds, &new_upper_bounds);
663 bool new_bounds_have_been_imported = false;
664 for (int i = 0; i < model_variables.size(); ++i) {
665 const int model_var = model_variables[i];
666
667 // If this is a Boolean, fix it if not already done.
668 // Note that it is important not to use AddUnitClause() as we do not
669 // want to propagate after each addition.
670 if (mapping->IsBoolean(model_var)) {
671 Literal lit = mapping->Literal(model_var);
672 if (new_upper_bounds[i] == 0) lit = lit.Negated();
673 if (trail->Assignment().LiteralIsTrue(lit)) continue;
674 if (trail->Assignment().LiteralIsFalse(lit)) {
675 sat_solver->NotifyThatModelIsUnsat();
676 return false;
677 }
678 new_bounds_have_been_imported = true;
679 trail->EnqueueWithUnitReason(lit);
680 continue;
681 }
682
683 // Deal with integer.
684 if (!mapping->IsInteger(model_var)) continue;
685 const IntegerVariable var = mapping->Integer(model_var);
686 const IntegerValue new_lb(new_lower_bounds[i]);
687 const IntegerValue new_ub(new_upper_bounds[i]);
688 const IntegerValue old_lb = integer_trail->LowerBound(var);
689 const IntegerValue old_ub = integer_trail->UpperBound(var);
690 const bool changed_lb = new_lb > old_lb;
691 const bool changed_ub = new_ub < old_ub;
692 if (!changed_lb && !changed_ub) continue;
693
694 new_bounds_have_been_imported = true;
695 if (VLOG_IS_ON(3)) {
696 const IntegerVariableProto& var_proto =
697 model_proto.variables(model_var);
698 const std::string& var_name =
699 var_proto.name().empty()
700 ? absl::StrCat("anonymous_var(", model_var, ")")
701 : var_proto.name();
702 LOG(INFO) << " '" << name << "' imports new bounds for " << var_name
703 << ": from [" << old_lb << ", " << old_ub << "] to ["
704 << new_lb << ", " << new_ub << "]";
705 }
706
707 if (changed_lb &&
708 !integer_trail->Enqueue(IntegerLiteral::GreaterOrEqual(var, new_lb),
709 {}, {})) {
710 return false;
711 }
712 if (changed_ub &&
713 !integer_trail->Enqueue(IntegerLiteral::LowerOrEqual(var, new_ub), {},
714 {})) {
715 return false;
716 }
717 }
718 if (new_bounds_have_been_imported && !sat_solver->FinishPropagation()) {
719 return false;
720 }
721 return true;
722 };
723 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
724 import_level_zero_bounds);
725}
726
727// Registers a callback that will report improving objective best bound.
728// It will be called each time new objective bound are propagated at level zero.
730 IntegerVariable objective_var,
731 SharedResponseManager* shared_response_manager, Model* model) {
732 auto* integer_trail = model->Get<IntegerTrail>();
733 const auto broadcast_objective_lower_bound =
734 [objective_var, integer_trail, shared_response_manager, model,
735 best_obj_lb =
736 kMinIntegerValue](const std::vector<IntegerVariable>&) mutable {
737 const IntegerValue objective_lb =
738 integer_trail->LevelZeroLowerBound(objective_var);
739 if (objective_lb > best_obj_lb) {
740 best_obj_lb = objective_lb;
741 shared_response_manager->UpdateInnerObjectiveBounds(
742 model->Name(), objective_lb,
743 integer_trail->LevelZeroUpperBound(objective_var));
744 // If we are not in interleave_search we synchronize right away.
745 if (!model->Get<SatParameters>()->interleave_search()) {
746 shared_response_manager->Synchronize();
747 }
748 }
749 };
750 model->GetOrCreate<GenericLiteralWatcher>()
751 ->RegisterLevelZeroModifiedVariablesCallback(
752 broadcast_objective_lower_bound);
753}
754
755// Registers a callback to import new objective bounds. It will be called each
756// time the search main loop is back to level zero. Note that it the presence of
757// assumptions, this will not happen until the set of assumptions is changed.
759 SharedResponseManager* shared_response_manager, Model* model) {
760 auto* solver = model->GetOrCreate<SatSolver>();
761 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
762 auto* objective = model->GetOrCreate<ObjectiveDefinition>();
763 const std::string name = model->Name();
764 const auto import_objective_bounds = [name, solver, integer_trail, objective,
765 shared_response_manager]() {
766 if (solver->AssumptionLevel() != 0) return true;
767 bool propagate = false;
768
769 const IntegerValue external_lb =
770 shared_response_manager->GetInnerObjectiveLowerBound();
771 const IntegerValue current_lb =
772 integer_trail->LowerBound(objective->objective_var);
773 if (external_lb > current_lb) {
774 if (!integer_trail->Enqueue(IntegerLiteral::GreaterOrEqual(
775 objective->objective_var, external_lb),
776 {}, {})) {
777 return false;
778 }
779 propagate = true;
780 }
781
782 const IntegerValue external_ub =
783 shared_response_manager->GetInnerObjectiveUpperBound();
784 const IntegerValue current_ub =
785 integer_trail->UpperBound(objective->objective_var);
786 if (external_ub < current_ub) {
787 if (!integer_trail->Enqueue(IntegerLiteral::LowerOrEqual(
788 objective->objective_var, external_ub),
789 {}, {})) {
790 return false;
791 }
792 propagate = true;
793 }
794
795 if (!propagate) return true;
796
797 VLOG(3) << "'" << name << "' imports objective bounds: external ["
798 << objective->ScaleIntegerObjective(external_lb) << ", "
799 << objective->ScaleIntegerObjective(external_ub) << "], current ["
800 << objective->ScaleIntegerObjective(current_lb) << ", "
801 << objective->ScaleIntegerObjective(current_ub) << "]";
802
803 return solver->FinishPropagation();
804 };
805
806 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
807 import_objective_bounds);
808}
809
810// Registers a callback that will export good clauses discovered during search.
811void RegisterClausesExport(int id, SharedClausesManager* shared_clauses_manager,
812 Model* model) {
813 auto* mapping = model->GetOrCreate<CpModelMapping>();
814 const auto& share_binary_clause = [mapping, id, shared_clauses_manager](
815 Literal l1, Literal l2) {
816 const int var1 =
817 mapping->GetProtoVariableFromBooleanVariable(l1.Variable());
818 if (var1 == -1) return;
819 const int var2 =
820 mapping->GetProtoVariableFromBooleanVariable(l2.Variable());
821 if (var2 == -1) return;
822 const int lit1 = l1.IsPositive() ? var1 : NegatedRef(var1);
823 const int lit2 = l2.IsPositive() ? var2 : NegatedRef(var2);
824 shared_clauses_manager->AddBinaryClause(id, lit1, lit2);
825 };
826 model->GetOrCreate<BinaryImplicationGraph>()->SetAdditionCallback(
827 share_binary_clause);
828 if (!model->GetOrCreate<SatParameters>()->share_glue_clauses()) {
829 return;
830 }
831 auto* clause_stream = shared_clauses_manager->GetClauseStream(id);
832 const int max_lbd =
833 model->GetOrCreate<SatParameters>()->clause_cleanup_lbd_bound();
834 // Note that this callback takes no global locks, everything operates on this
835 // worker's own clause stream, whose lock is only used by this worker, and
836 // briefly when generating a batch in SharedClausesManager::Synchronize().
837 auto share_clause = [mapping, clause_stream, max_lbd,
838 clause = std::vector<int>()](
839 int lbd, absl::Span<const Literal> literals) mutable {
840 if (lbd <= 0 || lbd > max_lbd ||
841 !clause_stream->CanAccept(literals.size(), lbd)) {
842 return;
843 }
844 clause.clear();
845 for (const Literal& lit : literals) {
846 const int var =
847 mapping->GetProtoVariableFromBooleanVariable(lit.Variable());
848 if (var == -1) return;
849 clause.push_back(lit.IsPositive() ? var : NegatedRef(var));
850 }
851 clause_stream->Add(clause);
852 };
853 model->GetOrCreate<ClauseManager>()->SetAddClauseCallback(
854 std::move(share_clause));
855}
856
857// Registers a callback to import new clauses stored in the
858// shared_clausess_manager. These clauses are imported at level 0 of the search
859// in the linear scan minimize function.
860// it returns the id of the worker in the shared clause manager.
861//
862// TODO(user): Can we import them in the core worker ?
864 SharedClausesManager* shared_clauses_manager,
865 Model* model) {
866 CHECK(shared_clauses_manager != nullptr);
867 CpModelMapping* const mapping = model->GetOrCreate<CpModelMapping>();
868 auto* sat_solver = model->GetOrCreate<SatSolver>();
869 auto* implications = model->GetOrCreate<BinaryImplicationGraph>();
870 bool share_glue_clauses =
871 model->GetOrCreate<SatParameters>()->share_glue_clauses();
872 auto* clause_stream = share_glue_clauses
873 ? shared_clauses_manager->GetClauseStream(id)
874 : nullptr;
875 const auto& import_level_zero_clauses = [shared_clauses_manager, id, mapping,
876 sat_solver, implications,
877 clause_stream]() {
878 std::vector<std::pair<int, int>> new_binary_clauses;
879 shared_clauses_manager->GetUnseenBinaryClauses(id, &new_binary_clauses);
880 implications->EnableSharing(false);
881 for (const auto& [ref1, ref2] : new_binary_clauses) {
882 const Literal l1 = mapping->Literal(ref1);
883 const Literal l2 = mapping->Literal(ref2);
884 if (!sat_solver->AddBinaryClause(l1, l2)) {
885 return false;
886 }
887 }
888 implications->EnableSharing(true);
889 if (clause_stream == nullptr) return true;
890
891 std::array<Literal, UniqueClauseStream::kMaxClauseSize> local_clause;
892 for (const absl::Span<const int> shared_clause :
893 shared_clauses_manager->GetUnseenClauses(id)) {
894 // Check this clause was not already learned by this worker.
895 // We can delete the fingerprint because we should not learn an identical
896 // clause, and the global stream will not emit the same clause while any
897 // worker hasn't consumed this clause (and thus also shouldn't relearn the
898 // clause).
899 if (clause_stream->Delete(shared_clause)) continue;
900 for (int i = 0; i < shared_clause.size(); ++i) {
901 local_clause[i] = mapping->Literal(shared_clause[i]);
902 }
903 if (!sat_solver->AddProblemClause(
904 absl::MakeSpan(local_clause).subspan(0, shared_clause.size()))) {
905 return false;
906 }
907 }
908 clause_stream->RemoveWorstClauses();
909 return true;
910 };
911 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
912 import_level_zero_clauses);
913 return id;
914}
915
916void LoadBaseModel(const CpModelProto& model_proto, Model* model) {
917 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
918 CHECK(shared_response_manager != nullptr);
919 auto* sat_solver = model->GetOrCreate<SatSolver>();
920
921 // Simple function for the few places where we do "return unsat()".
922 const auto unsat = [shared_response_manager, sat_solver, model] {
923 sat_solver->NotifyThatModelIsUnsat();
924 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
925 absl::StrCat(model->Name(), " [loading]"));
926 };
927
928 // We will add them all at once after model_proto is loaded.
929 model->GetOrCreate<IntegerEncoder>()->DisableImplicationBetweenLiteral();
930
931 auto* mapping = model->GetOrCreate<CpModelMapping>();
932 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
933 const bool view_all_booleans_as_integers =
934 (parameters.linearization_level() >= 2) ||
935 (parameters.search_branching() == SatParameters::FIXED_SEARCH &&
936 model_proto.search_strategy().empty()) ||
937 parameters.optimize_with_max_hs();
938 LoadVariables(model_proto, view_all_booleans_as_integers, model);
939 DetectOptionalVariables(model_proto, model);
940
941 // TODO(user): The core algo and symmetries seems to be problematic in some
942 // cases. See for instance: neos-691058.mps.gz. This is probably because as
943 // we modify the model, our symmetry might be wrong? investigate.
944 //
945 // TODO(user): More generally, we cannot load the symmetry if we create
946 // new Booleans and constraints that link them to some Booleans of the model.
947 // Creating Booleans related to integer variable is fine since we only deal
948 // with Boolean only symmetry here. It is why we disable this when we have
949 // linear relaxation as some of them create new constraints.
950 if (!parameters.optimize_with_core() && parameters.symmetry_level() > 1 &&
951 !parameters.enumerate_all_solutions() &&
952 parameters.linearization_level() == 0) {
953 LoadBooleanSymmetries(model_proto, model);
954 }
955
956 ExtractEncoding(model_proto, model);
958
959 // Check the model is still feasible before continuing.
960 if (sat_solver->ModelIsUnsat()) return unsat();
961
962 // Fully encode variables as needed by the search strategy.
964 if (sat_solver->ModelIsUnsat()) return unsat();
965
966 // Reserve space for the precedence relations.
967 model->GetOrCreate<PrecedenceRelations>()->Resize(
968 model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value());
969
970 // Load the constraints.
971 int num_ignored_constraints = 0;
972 absl::flat_hash_set<ConstraintProto::ConstraintCase> unsupported_types;
973 for (const ConstraintProto& ct : model_proto.constraints()) {
974 if (mapping->ConstraintIsAlreadyLoaded(&ct)) {
975 ++num_ignored_constraints;
976 continue;
977 }
978
979 if (!LoadConstraint(ct, model)) {
980 unsupported_types.insert(ct.constraint_case());
981 continue;
982 }
983
984 // We propagate after each new Boolean constraint but not the integer
985 // ones. So we call FinishPropagation() manually here.
986 //
987 // Note that we only do that in debug mode as this can be really slow on
988 // certain types of problems with millions of constraints.
989 if (DEBUG_MODE) {
990 if (sat_solver->FinishPropagation()) {
991 Trail* trail = model->GetOrCreate<Trail>();
992 const int old_num_fixed = trail->Index();
993 if (trail->Index() > old_num_fixed) {
994 VLOG(3) << "Constraint fixed " << trail->Index() - old_num_fixed
995 << " Boolean variable(s): " << ProtobufDebugString(ct);
996 }
997 }
998 }
999 if (sat_solver->ModelIsUnsat()) {
1000 VLOG(2) << "UNSAT during extraction (after adding '"
1001 << ConstraintCaseName(ct.constraint_case()) << "'). "
1003 return unsat();
1004 }
1005 }
1006 if (num_ignored_constraints > 0) {
1007 VLOG(3) << num_ignored_constraints << " constraints were skipped.";
1008 }
1009 if (!unsupported_types.empty()) {
1010 VLOG(1) << "There is unsupported constraints types in this model: ";
1011 std::vector<absl::string_view> names;
1012 for (const ConstraintProto::ConstraintCase type : unsupported_types) {
1013 names.push_back(ConstraintCaseName(type));
1014 }
1015 std::sort(names.begin(), names.end());
1016 for (const absl::string_view name : names) {
1017 VLOG(1) << " - " << name;
1018 }
1019 return unsat();
1020 }
1021
1022 model->GetOrCreate<IntegerEncoder>()
1023 ->AddAllImplicationsBetweenAssociatedLiterals();
1024 if (!sat_solver->FinishPropagation()) return unsat();
1025
1026 model->GetOrCreate<ProductDetector>()->ProcessImplicationGraph(
1027 model->GetOrCreate<BinaryImplicationGraph>());
1028 model->GetOrCreate<PrecedenceRelations>()->Build();
1029}
1030
1031void LoadFeasibilityPump(const CpModelProto& model_proto, Model* model) {
1032 LoadBaseModel(model_proto, model);
1033
1034 auto* mapping = model->GetOrCreate<CpModelMapping>();
1035 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1036 if (parameters.linearization_level() == 0) return;
1037
1038 // Add linear constraints to Feasibility Pump.
1039 const LinearRelaxation relaxation =
1040 ComputeLinearRelaxation(model_proto, model);
1041 if (model->GetOrCreate<SatSolver>()->ModelIsUnsat()) return;
1042
1043 const int num_lp_constraints =
1044 static_cast<int>(relaxation.linear_constraints.size());
1045 if (num_lp_constraints == 0) return;
1046 auto* feasibility_pump = model->GetOrCreate<FeasibilityPump>();
1047 for (int i = 0; i < num_lp_constraints; i++) {
1048 feasibility_pump->AddLinearConstraint(relaxation.linear_constraints[i]);
1049 }
1050
1051 if (model_proto.has_objective()) {
1052 for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
1053 const IntegerVariable var =
1054 mapping->Integer(model_proto.objective().vars(i));
1055 const int64_t coeff = model_proto.objective().coeffs(i);
1056 feasibility_pump->SetObjectiveCoefficient(var, IntegerValue(coeff));
1057 }
1058 }
1059}
1060
1061// Loads a CpModelProto inside the given model.
1062// This should only be called once on a given 'Model' class.
1063void LoadCpModel(const CpModelProto& model_proto, Model* model) {
1064 LoadBaseModel(model_proto, model);
1065
1066 // We want to load the debug solution before the initial propag.
1067 // But at this point the objective is not loaded yet, so we will not have
1068 // a value for the objective integer variable, so we do it again later.
1069 InitializeDebugSolution(model_proto, model);
1070
1071 // Simple function for the few places where we do "return unsat()".
1072 auto* sat_solver = model->GetOrCreate<SatSolver>();
1073 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1074 const auto unsat = [shared_response_manager, sat_solver, model] {
1075 sat_solver->NotifyThatModelIsUnsat();
1076 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1077 absl::StrCat(model->Name(), " [loading]"));
1078 };
1079
1080 auto* mapping = model->GetOrCreate<CpModelMapping>();
1081 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1082
1083 // Auto detect "at least one of" constraints in the PrecedencesPropagator.
1084 // Note that we do that before we finish loading the problem (objective and
1085 // LP relaxation), because propagation will be faster at this point and it
1086 // should be enough for the purpose of this auto-detection.
1087 if (parameters.auto_detect_greater_than_at_least_one_of()) {
1089 ->AddGreaterThanAtLeastOneOfConstraints(model);
1090 if (!sat_solver->FinishPropagation()) return unsat();
1091 }
1092
1093 // Note that this is already done in the presolve, but it is important to redo
1094 // it here to collect literal => integer >= bound constraints that are used in
1095 // many places. Without it, we don't detect them if they depends on long chain
1096 // of implications.
1097 //
1098 // TODO(user): We don't have a good deterministic time on all constraints,
1099 // so this might take more time than wanted.
1100 if (parameters.cp_model_probing_level() > 1) {
1101 Prober* prober = model->GetOrCreate<Prober>();
1102 prober->ProbeBooleanVariables(/*deterministic_time_limit=*/1.0);
1103 if (!model->GetOrCreate<BinaryImplicationGraph>()
1105 return unsat();
1106 }
1107 }
1108 if (sat_solver->ModelIsUnsat()) return unsat();
1109
1110 // Note that it is important to do that after the probing.
1111 ExtractElementEncoding(model_proto, model);
1112
1113 // Compute decomposed energies on demands helper.
1114 IntervalsRepository* repository = model->Mutable<IntervalsRepository>();
1115 if (repository != nullptr) {
1116 repository->InitAllDecomposedEnergies();
1117 }
1118
1119 // We need to know beforehand if the objective var can just be >= terms or
1120 // needs to be == terms.
1121 bool objective_need_to_be_tight = false;
1122 if (model_proto.has_objective() &&
1123 !model_proto.objective().domain().empty()) {
1124 int64_t min_value = 0;
1125 int64_t max_value = 0;
1126 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1127 const CpObjectiveProto& obj = model_proto.objective();
1128 for (int i = 0; i < obj.vars_size(); ++i) {
1129 const int64_t coeff = obj.coeffs(i);
1130 const IntegerVariable var = mapping->Integer(obj.vars(i));
1131 if (coeff > 0) {
1132 min_value += coeff * integer_trail->LowerBound(var).value();
1133 max_value += coeff * integer_trail->UpperBound(var).value();
1134 } else {
1135 min_value += coeff * integer_trail->UpperBound(var).value();
1136 max_value += coeff * integer_trail->LowerBound(var).value();
1137 }
1138 }
1139 const Domain user_domain = ReadDomainFromProto(model_proto.objective());
1140 const Domain automatic_domain = Domain(min_value, max_value);
1141 objective_need_to_be_tight = !automatic_domain.IsIncludedIn(user_domain);
1142 }
1143
1144 // Create an objective variable and its associated linear constraint if
1145 // needed.
1146 IntegerVariable objective_var = kNoIntegerVariable;
1147 if (parameters.linearization_level() > 0) {
1148 // Linearize some part of the problem and register LP constraint(s).
1149 objective_var =
1150 AddLPConstraints(objective_need_to_be_tight, model_proto, model);
1151 if (sat_solver->ModelIsUnsat()) return unsat();
1152 } else if (model_proto.has_objective()) {
1153 const CpObjectiveProto& obj = model_proto.objective();
1154 std::vector<std::pair<IntegerVariable, int64_t>> terms;
1155 terms.reserve(obj.vars_size());
1156 for (int i = 0; i < obj.vars_size(); ++i) {
1157 terms.push_back(
1158 std::make_pair(mapping->Integer(obj.vars(i)), obj.coeffs(i)));
1159 }
1160 if (parameters.optimize_with_core()) {
1161 if (objective_need_to_be_tight) {
1162 // We do not care about the <= obj for core, we only need the other side
1163 // to enforce a restriction of the objective lower bound.
1164 //
1165 // TODO(user): This might still create intermediate variables to
1166 // decompose the objective for no reason. Just deal directly with the
1167 // objective domain in the core algo by forbidding bad assumptions?
1168 // Alternatively, just ignore the core solution if it is "too" good and
1169 // rely on other solvers?
1170 objective_var =
1171 GetOrCreateVariableLinkedToSumOf(terms, true, false, model);
1172 } else {
1173 objective_var = GetOrCreateVariableWithTightBound(terms, model);
1174 }
1175 } else {
1176 objective_var = GetOrCreateVariableLinkedToSumOf(
1177 terms, objective_need_to_be_tight, true, model);
1178 }
1179 }
1180
1181 // Create the objective definition inside the Model so that it can be accessed
1182 // by the heuristics than needs it.
1183 if (objective_var != kNoIntegerVariable) {
1184 const CpObjectiveProto& objective_proto = model_proto.objective();
1185 auto* objective_definition = model->GetOrCreate<ObjectiveDefinition>();
1186
1187 objective_definition->scaling_factor = objective_proto.scaling_factor();
1188 if (objective_definition->scaling_factor == 0.0) {
1189 objective_definition->scaling_factor = 1.0;
1190 }
1191 objective_definition->offset = objective_proto.offset();
1192 objective_definition->objective_var = objective_var;
1193
1194 const int size = objective_proto.vars_size();
1195 objective_definition->vars.resize(size);
1196 objective_definition->coeffs.resize(size);
1197 for (int i = 0; i < objective_proto.vars_size(); ++i) {
1198 // Note that if there is no mapping, then the variable will be
1199 // kNoIntegerVariable.
1200 objective_definition->vars[i] = mapping->Integer(objective_proto.vars(i));
1201 objective_definition->coeffs[i] = IntegerValue(objective_proto.coeffs(i));
1202
1203 // Fill the objective heuristics data.
1204 const int ref = objective_proto.vars(i);
1205 if (mapping->IsInteger(ref)) {
1206 const IntegerVariable var = mapping->Integer(objective_proto.vars(i));
1207 objective_definition->objective_impacting_variables.insert(
1208 objective_proto.coeffs(i) > 0 ? var : NegationOf(var));
1209 }
1210 }
1211
1212 // Register an objective special propagator.
1213 model->TakeOwnership(
1214 new LevelZeroEquality(objective_var, objective_definition->vars,
1215 objective_definition->coeffs, model));
1216 }
1217
1218 // Intersect the objective domain with the given one if any.
1219 if (!model_proto.objective().domain().empty()) {
1220 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1221 const Domain user_domain = ReadDomainFromProto(model_proto.objective());
1222 const Domain automatic_domain =
1223 integer_trail->InitialVariableDomain(objective_var);
1224 VLOG(3) << "Objective offset:" << model_proto.objective().offset()
1225 << " scaling_factor:" << model_proto.objective().scaling_factor();
1226 VLOG(3) << "Automatic internal objective domain: " << automatic_domain;
1227 VLOG(3) << "User specified internal objective domain: " << user_domain;
1228 CHECK_NE(objective_var, kNoIntegerVariable);
1229 if (!integer_trail->UpdateInitialDomain(objective_var, user_domain)) {
1230 VLOG(2) << "UNSAT due to the objective domain.";
1231 return unsat();
1232 }
1233 }
1234
1235 // Note that we do one last propagation at level zero once all the
1236 // constraints were added.
1237 SOLVER_LOG(model->GetOrCreate<SolverLogger>(),
1238 "Initial num_bool: ", sat_solver->NumVariables());
1239 if (!sat_solver->FinishPropagation()) return unsat();
1240
1241 if (model_proto.has_objective()) {
1242 // Report the initial objective variable bounds.
1243 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1244 shared_response_manager->UpdateInnerObjectiveBounds(
1245 absl::StrCat(model->Name(), " (initial_propagation)"),
1246 integer_trail->LowerBound(objective_var),
1247 integer_trail->UpperBound(objective_var));
1248
1249 // Watch improved objective best bounds.
1250 RegisterObjectiveBestBoundExport(objective_var, shared_response_manager,
1251 model);
1252
1253 // Import objective bounds.
1254 // TODO(user): Support objective bounds import in LNS and Core based
1255 // search.
1256 if (model->GetOrCreate<SatParameters>()->share_objective_bounds()) {
1257 RegisterObjectiveBoundsImport(shared_response_manager, model);
1258 }
1259 }
1260
1261 // Initialize the search strategies.
1262 auto* search_heuristics = model->GetOrCreate<SearchHeuristics>();
1263 search_heuristics->user_search =
1264 ConstructUserSearchStrategy(model_proto, model);
1265 search_heuristics->heuristic_search =
1267 search_heuristics->integer_completion_search =
1268 ConstructIntegerCompletionSearchStrategy(mapping->GetVariableMapping(),
1269 objective_var, model);
1270 search_heuristics->fixed_search = ConstructFixedSearchStrategy(
1271 search_heuristics->user_search, search_heuristics->heuristic_search,
1272 search_heuristics->integer_completion_search);
1273 if (VLOG_IS_ON(3)) {
1274 search_heuristics->fixed_search =
1275 InstrumentSearchStrategy(model_proto, mapping->GetVariableMapping(),
1276 search_heuristics->fixed_search, model);
1277 }
1278 search_heuristics->hint_search =
1279 ConstructHintSearchStrategy(model_proto, mapping, model);
1280
1281 // Create the CoreBasedOptimizer class if needed.
1282 if (parameters.optimize_with_core()) {
1283 // TODO(user): Remove code duplication with the solution_observer in
1284 // SolveLoadedCpModel().
1285 const auto solution_observer = [&model_proto, model,
1286 shared_response_manager,
1287 best_obj_ub = kMaxIntegerValue]() mutable {
1288 const std::vector<int64_t> solution =
1289 GetSolutionValues(model_proto, *model);
1290 const IntegerValue obj_ub =
1291 ComputeInnerObjective(model_proto.objective(), solution);
1292 if (obj_ub < best_obj_ub) {
1293 best_obj_ub = obj_ub;
1294 shared_response_manager->NewSolution(solution, model->Name(), model);
1295 }
1296 };
1297
1298 const auto& objective = *model->GetOrCreate<ObjectiveDefinition>();
1299 if (parameters.optimize_with_max_hs()) {
1301 model_proto, objective, solution_observer, model);
1302 model->Register<HittingSetOptimizer>(max_hs);
1303 model->TakeOwnership(max_hs);
1304 } else {
1305 CoreBasedOptimizer* core =
1306 new CoreBasedOptimizer(objective_var, objective.vars,
1307 objective.coeffs, solution_observer, model);
1308 model->Register<CoreBasedOptimizer>(core);
1309 model->TakeOwnership(core);
1310 }
1311 }
1312
1313 InitializeDebugSolution(model_proto, model);
1314}
1315
1316// Solves an already loaded cp_model_proto.
1317// The final CpSolverResponse must be read from the shared_response_manager.
1318//
1319// TODO(user): This should be transformed so that it can be called many times
1320// and resume from the last search state as if it wasn't interrupted. That would
1321// allow use to easily interleave different heuristics in the same thread.
1322void SolveLoadedCpModel(const CpModelProto& model_proto, Model* model) {
1323 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1324 if (shared_response_manager->ProblemIsSolved()) return;
1325
1326 const SatParameters& parameters = *model->GetOrCreate<SatParameters>();
1327 if (parameters.stop_after_root_propagation()) return;
1328
1329 auto solution_observer = [&model_proto, model, shared_response_manager,
1330 best_obj_ub = kMaxIntegerValue]() mutable {
1331 const std::vector<int64_t> solution =
1332 GetSolutionValues(model_proto, *model);
1333 if (model_proto.has_objective()) {
1334 const IntegerValue obj_ub =
1335 ComputeInnerObjective(model_proto.objective(), solution);
1336 if (obj_ub < best_obj_ub) {
1337 best_obj_ub = obj_ub;
1338 shared_response_manager->NewSolution(solution, model->Name(), model);
1339 }
1340 } else {
1341 shared_response_manager->NewSolution(solution, model->Name(), model);
1342 }
1343 };
1344
1345 // Make sure we are not at a positive level.
1346 if (!model->GetOrCreate<SatSolver>()->ResetToLevelZero()) {
1347 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1348 model->Name());
1349 return;
1350 }
1351
1352 // Reconfigure search heuristic if it was changed.
1354
1355 const auto& mapping = *model->GetOrCreate<CpModelMapping>();
1357
1358 if (parameters.use_probing_search()) {
1359 ContinuousProber prober(model_proto, model);
1360 while (true) {
1361 status = prober.Probe();
1363 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1364 model->Name());
1365 break;
1366 }
1367 if (status == SatSolver::FEASIBLE) {
1368 solution_observer();
1369 } else {
1370 break;
1371 }
1372 }
1373 } else if (!model_proto.has_objective()) {
1374 while (true) {
1375 if (parameters.use_shared_tree_search()) {
1376 auto* subtree_worker = model->GetOrCreate<SharedTreeWorker>();
1377 status = subtree_worker->Search(solution_observer);
1378 } else {
1380 mapping.Literals(model_proto.assumptions()), model);
1381 }
1382 if (status != SatSolver::Status::FEASIBLE) break;
1383 solution_observer();
1384 if (!parameters.enumerate_all_solutions()) break;
1386 }
1388 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1389 model->Name());
1390 }
1392 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1393 model->Name());
1394
1395 // Extract a good subset of assumptions and add it to the response.
1396 auto* time_limit = model->GetOrCreate<TimeLimit>();
1397 auto* sat_solver = model->GetOrCreate<SatSolver>();
1398 std::vector<Literal> core = sat_solver->GetLastIncompatibleDecisions();
1399 MinimizeCoreWithPropagation(time_limit, sat_solver, &core);
1400 std::vector<int> core_in_proto_format;
1401 for (const Literal l : core) {
1402 core_in_proto_format.push_back(
1403 mapping.GetProtoVariableFromBooleanVariable(l.Variable()));
1404 if (!l.IsPositive()) {
1405 core_in_proto_format.back() = NegatedRef(core_in_proto_format.back());
1406 }
1407 }
1408 shared_response_manager->AddUnsatCore(core_in_proto_format);
1409 }
1410 } else {
1411 // Optimization problem.
1412 const auto& objective = *model->GetOrCreate<ObjectiveDefinition>();
1413 const IntegerVariable objective_var = objective.objective_var;
1414 CHECK_NE(objective_var, kNoIntegerVariable);
1415
1416 if (parameters.optimize_with_lb_tree_search()) {
1417 auto* search = model->GetOrCreate<LbTreeSearch>();
1418 status = search->Search(solution_observer);
1419 } else if (parameters.optimize_with_core()) {
1420 // TODO(user): This doesn't work with splitting in chunk for now. It
1421 // shouldn't be too hard to fix.
1422 if (parameters.optimize_with_max_hs()) {
1423 status = model->Mutable<HittingSetOptimizer>()->Optimize();
1424 } else {
1425 status = model->Mutable<CoreBasedOptimizer>()->Optimize();
1426 }
1427 } else if (parameters.use_shared_tree_search()) {
1428 auto* subtree_worker = model->GetOrCreate<SharedTreeWorker>();
1429 status = subtree_worker->Search(solution_observer);
1430 } else {
1431 // TODO(user): This parameter breaks the splitting in chunk of a Solve().
1432 // It should probably be moved into another SubSolver altogether.
1433 if (parameters.binary_search_num_conflicts() >= 0) {
1435 solution_observer, model);
1436 }
1438 objective_var, solution_observer, model);
1439 }
1440
1441 // The search is done in both case.
1442 //
1443 // TODO(user): Remove the weird translation INFEASIBLE->FEASIBLE in the
1444 // function above?
1446 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1447 model->Name());
1448 }
1449 }
1450}
1451
1452// Try to find a solution by following the hint and using a low conflict limit.
1453// The CpModelProto must already be loaded in the Model.
1454void QuickSolveWithHint(const CpModelProto& model_proto, Model* model) {
1455 if (!model_proto.has_solution_hint()) return;
1456
1457 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1458 if (shared_response_manager->ProblemIsSolved()) return;
1459
1460 // Temporarily change the parameters.
1461 auto* parameters = model->GetOrCreate<SatParameters>();
1462
1463 // If the model was loaded with "optimize_with_core" then the objective
1464 // variable is not linked to its linear expression. Because of that, we can
1465 // return a solution that does not satisfy the objective domain.
1466 //
1467 // TODO(user): This is fixable, but then do we need the hint when optimizing
1468 // with core?
1469 if (parameters->optimize_with_core()) return;
1470
1471 const SatParameters saved_params = *parameters;
1472 parameters->set_max_number_of_conflicts(parameters->hint_conflict_limit());
1473 parameters->set_search_branching(SatParameters::HINT_SEARCH);
1474 parameters->set_optimize_with_core(false);
1475 parameters->set_use_sat_inprocessing(false);
1476 auto cleanup = ::absl::MakeCleanup(
1477 [parameters, saved_params]() { *parameters = saved_params; });
1478
1479 // Solve decision problem.
1481 const auto& mapping = *model->GetOrCreate<CpModelMapping>();
1483 mapping.Literals(model_proto.assumptions()), model);
1484
1485 const std::string& solution_info = model->Name();
1487 const std::vector<int64_t> solution =
1488 GetSolutionValues(model_proto, *model);
1489 shared_response_manager->NewSolution(
1490 solution, absl::StrCat(solution_info, " [hint]"), model);
1491
1492 if (!model_proto.has_objective()) {
1493 if (parameters->enumerate_all_solutions()) {
1495 }
1496 } else {
1497 // Restrict the objective.
1498 const IntegerVariable objective_var =
1499 model->GetOrCreate<ObjectiveDefinition>()->objective_var;
1500 model->GetOrCreate<SatSolver>()->Backtrack(0);
1501 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1502 if (!integer_trail->Enqueue(
1504 objective_var,
1505 shared_response_manager->GetInnerObjectiveUpperBound()),
1506 {}, {})) {
1507 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1508 absl::StrCat(solution_info, " [hint]"));
1509 }
1510 }
1511 return;
1512 }
1513
1514 // This code is here to debug bad presolve during LNS that corrupt the hint.
1515 // Note that sometime the deterministic limit is hit before the hint can be
1516 // completed, so we don't report that has an error.
1517 //
1518 // Tricky: We can only test that if we don't already have a feasible solution
1519 // like we do if the hint is complete.
1520 if (parameters->debug_crash_on_bad_hint() &&
1521 shared_response_manager->SolutionsRepository().NumSolutions() == 0 &&
1522 !model->GetOrCreate<TimeLimit>()->LimitReached() &&
1524 LOG(FATAL) << "QuickSolveWithHint() didn't find a feasible solution."
1525 << " The model name is '" << model_proto.name() << "'."
1526 << " Status: " << status << ".";
1527 }
1528
1530 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1531 absl::StrCat(solution_info, " [hint]"));
1532 return;
1533 }
1534}
1535
1536// Solve a model with a different objective consisting of minimizing the L1
1537// distance with the provided hint. Note that this method creates an in-memory
1538// copy of the model and loads a local Model object from the copied model.
1539void MinimizeL1DistanceWithHint(const CpModelProto& model_proto, Model* model) {
1540 Model local_model;
1541
1542 // Forward some shared class.
1543 local_model.Register<ModelSharedTimeLimit>(
1544 model->GetOrCreate<ModelSharedTimeLimit>());
1545 local_model.Register<WallTimer>(model->GetOrCreate<WallTimer>());
1546
1547 if (!model_proto.has_solution_hint()) return;
1548
1549 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1550 if (shared_response_manager->ProblemIsSolved()) return;
1551
1552 auto* parameters = local_model.GetOrCreate<SatParameters>();
1553 // TODO(user): As of now the repair hint doesn't support when
1554 // enumerate_all_solutions is set since the solution is created on a different
1555 // model.
1556 if (parameters->enumerate_all_solutions()) return;
1557
1558 // Change the parameters.
1559 const SatParameters saved_params = *model->GetOrCreate<SatParameters>();
1560 *parameters = saved_params;
1561 parameters->set_max_number_of_conflicts(parameters->hint_conflict_limit());
1562 parameters->set_optimize_with_core(false);
1563
1564 // Update the model to introduce penalties to go away from hinted values.
1565 CpModelProto updated_model_proto = model_proto;
1566 updated_model_proto.clear_objective();
1567
1568 // TODO(user): For boolean variables we can avoid creating new variables.
1569 for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
1570 const int var = model_proto.solution_hint().vars(i);
1571 const int64_t value = model_proto.solution_hint().values(i);
1572
1573 // Add a new var to represent the difference between var and value.
1574 const int new_var_index = updated_model_proto.variables_size();
1575 IntegerVariableProto* var_proto = updated_model_proto.add_variables();
1576 const int64_t min_domain = model_proto.variables(var).domain(0) - value;
1577 const int64_t max_domain =
1578 model_proto.variables(var).domain(
1579 model_proto.variables(var).domain_size() - 1) -
1580 value;
1581 var_proto->add_domain(min_domain);
1582 var_proto->add_domain(max_domain);
1583
1584 // new_var = var - value.
1585 ConstraintProto* const linear_constraint_proto =
1586 updated_model_proto.add_constraints();
1587 LinearConstraintProto* linear = linear_constraint_proto->mutable_linear();
1588 linear->add_vars(new_var_index);
1589 linear->add_coeffs(1);
1590 linear->add_vars(var);
1591 linear->add_coeffs(-1);
1592 linear->add_domain(-value);
1593 linear->add_domain(-value);
1594
1595 // abs_var = abs(new_var).
1596 const int abs_var_index = updated_model_proto.variables_size();
1597 IntegerVariableProto* abs_var_proto = updated_model_proto.add_variables();
1598 const int64_t abs_min_domain = 0;
1599 const int64_t abs_max_domain =
1600 std::max(std::abs(min_domain), std::abs(max_domain));
1601 abs_var_proto->add_domain(abs_min_domain);
1602 abs_var_proto->add_domain(abs_max_domain);
1603 auto* abs_ct = updated_model_proto.add_constraints()->mutable_lin_max();
1604 abs_ct->mutable_target()->add_vars(abs_var_index);
1605 abs_ct->mutable_target()->add_coeffs(1);
1606 LinearExpressionProto* left = abs_ct->add_exprs();
1607 left->add_vars(new_var_index);
1608 left->add_coeffs(1);
1609 LinearExpressionProto* right = abs_ct->add_exprs();
1610 right->add_vars(new_var_index);
1611 right->add_coeffs(-1);
1612
1613 updated_model_proto.mutable_objective()->add_vars(abs_var_index);
1614 updated_model_proto.mutable_objective()->add_coeffs(1);
1615 }
1616
1617 auto* local_response_manager =
1618 local_model.GetOrCreate<SharedResponseManager>();
1619 local_response_manager->InitializeObjective(updated_model_proto);
1620
1621 // Solve optimization problem.
1622 LoadCpModel(updated_model_proto, &local_model);
1623
1624 ConfigureSearchHeuristics(&local_model);
1625 const auto& mapping = *local_model.GetOrCreate<CpModelMapping>();
1627 mapping.Literals(updated_model_proto.assumptions()), &local_model);
1628
1629 const std::string& solution_info = model->Name();
1631 const std::vector<int64_t> solution =
1632 GetSolutionValues(model_proto, local_model);
1633 if (DEBUG_MODE) {
1634 const std::vector<int64_t> updated_solution =
1635 GetSolutionValues(updated_model_proto, local_model);
1636 LOG(INFO) << "Found solution with repaired hint penalty = "
1637 << ComputeInnerObjective(updated_model_proto.objective(),
1638 updated_solution);
1639 }
1640 shared_response_manager->NewSolution(
1641 solution, absl::StrCat(solution_info, " [repaired]"), &local_model);
1642 }
1643}
1644
1645// TODO(user): If this ever shows up in the profile, we could avoid copying
1646// the mapping_proto if we are careful about how we modify the variable domain
1647// before postsolving it. Note that 'num_variables_in_original_model' refers to
1648// the model before presolve.
1649void PostsolveResponseWithFullSolver(int num_variables_in_original_model,
1650 CpModelProto mapping_proto,
1651 const std::vector<int>& postsolve_mapping,
1652 std::vector<int64_t>* solution) {
1653 WallTimer wall_timer;
1654 wall_timer.Start();
1655
1656 // Fix the correct variable in the mapping_proto.
1657 for (int i = 0; i < solution->size(); ++i) {
1658 auto* var_proto = mapping_proto.mutable_variables(postsolve_mapping[i]);
1659 var_proto->clear_domain();
1660 var_proto->add_domain((*solution)[i]);
1661 var_proto->add_domain((*solution)[i]);
1662 }
1663
1664 // Postosolve parameters.
1665 // TODO(user): this problem is usually trivial, but we may still want to
1666 // impose a time limit or copy some of the parameters passed by the user.
1667 Model postsolve_model;
1668 postsolve_model.Register<WallTimer>(&wall_timer);
1669 {
1670 SatParameters& params = *postsolve_model.GetOrCreate<SatParameters>();
1671 params.set_linearization_level(0);
1672 params.set_cp_model_probing_level(0);
1673 }
1674
1675 auto* response_manager = postsolve_model.GetOrCreate<SharedResponseManager>();
1676 response_manager->InitializeObjective(mapping_proto);
1677
1678 LoadCpModel(mapping_proto, &postsolve_model);
1679 SolveLoadedCpModel(mapping_proto, &postsolve_model);
1680 const CpSolverResponse postsolve_response = response_manager->GetResponse();
1681 CHECK(postsolve_response.status() == CpSolverStatus::FEASIBLE ||
1682 postsolve_response.status() == CpSolverStatus::OPTIMAL)
1683 << postsolve_response.status();
1684
1685 // We only copy the solution from the postsolve_response to the response.
1686 CHECK_LE(num_variables_in_original_model,
1687 postsolve_response.solution().size());
1688 solution->assign(
1689 postsolve_response.solution().begin(),
1690 postsolve_response.solution().begin() + num_variables_in_original_model);
1691}
1692
1693void PostsolveResponseWrapper(const SatParameters& params,
1694 int num_variable_in_original_model,
1695 const CpModelProto& mapping_proto,
1696 const std::vector<int>& postsolve_mapping,
1697 std::vector<int64_t>* solution) {
1698 if (params.debug_postsolve_with_full_solver()) {
1699 PostsolveResponseWithFullSolver(num_variable_in_original_model,
1700 mapping_proto, postsolve_mapping, solution);
1701 } else {
1702 PostsolveResponse(num_variable_in_original_model, mapping_proto,
1703 postsolve_mapping, solution);
1704 }
1705}
1706
1707void AdaptGlobalParameters(const CpModelProto& model_proto, Model* model) {
1708 auto* params = model->GetOrCreate<SatParameters>();
1709 auto* logger = model->GetOrCreate<SolverLogger>();
1710
1711 // Update params.num_workers() if the old field was used.
1712 if (params->num_workers() == 0) {
1713 params->set_num_workers(params->num_search_workers());
1714 }
1715
1716 if (params->enumerate_all_solutions()) {
1717 if (params->num_workers() >= 1) {
1718 SOLVER_LOG(logger,
1719 "Forcing sequential search as enumerating all solutions is "
1720 "not supported in multi-thread.");
1721 }
1722 params->set_num_workers(1);
1723 }
1724
1725 if (!model_proto.assumptions().empty()) {
1726 if (params->num_workers() >= 1) {
1727 SOLVER_LOG(logger,
1728 "Forcing sequential search as assumptions are not supported "
1729 "in multi-thread.");
1730 }
1731 params->set_num_workers(1);
1732 }
1733
1734 if (params->num_workers() == 0) {
1735 // Initialize the number of workers if set to 0.
1736#if !defined(__PORTABLE_PLATFORM__)
1737 // Sometimes, hardware_concurrency will return 0. So always default to 1.
1738 const int num_cores = std::max<int>(std::thread::hardware_concurrency(), 1);
1739#else
1740 const int num_cores = 1;
1741#endif
1742 SOLVER_LOG(logger, "Setting number of workers to ", num_cores);
1743 params->set_num_workers(num_cores);
1744 }
1745
1746 // We currently only use the feasibility pump or rins/rens if it is enabled
1747 // and some other parameters are not on.
1748 //
1749 // TODO(user): for now this is not deterministic so we disable it on
1750 // interleave search. Fix.
1751 if (params->interleave_search() || params->num_workers() == 1 ||
1752 !params->use_lns()) {
1753 params->set_use_rins_lns(false);
1754 params->set_use_feasibility_pump(false);
1755 }
1756
1757 // We disable this if the global param asked for no LP.
1758 if (params->linearization_level() == 0) {
1759 params->set_use_feasibility_pump(false);
1760 }
1761
1762 // Disable shared bounds if we are in single thread and we are not
1763 // tightening the domains.
1764 if (!params->fill_tightened_domains_in_response() &&
1765 params->num_workers() == 1) {
1766 params->set_share_level_zero_bounds(false);
1767 }
1768}
1769
1770SharedClasses::SharedClasses(const CpModelProto* proto, Model* global_model)
1771 : model_proto(*proto),
1772 wall_timer(global_model->GetOrCreate<WallTimer>()),
1773 time_limit(global_model->GetOrCreate<ModelSharedTimeLimit>()),
1774 logger(global_model->GetOrCreate<SolverLogger>()),
1775 stats(global_model->GetOrCreate<SharedStatistics>()),
1776 response(global_model->GetOrCreate<SharedResponseManager>()),
1777 shared_tree_manager(global_model->GetOrCreate<SharedTreeManager>()) {
1778 const SatParameters& params = *global_model->GetOrCreate<SatParameters>();
1779
1780 if (params.share_level_zero_bounds()) {
1781 bounds = std::make_unique<SharedBoundsManager>(*proto);
1782 bounds->set_dump_prefix(absl::GetFlag(FLAGS_cp_model_dump_prefix));
1783 bounds->LoadDebugSolution(response->DebugSolution());
1784 }
1785
1786 // Create extra shared classes if needed. Note that while these parameters
1787 // are true by default, we disable them if we don't have enough workers for
1788 // them in AdaptGlobalParameters().
1789 //
1790 // Registering them to the global model should not really be necessary,
1791 // except if one wants to expect them from outside SolveCpModel().
1792 if (params.use_rins_lns() || params.use_feasibility_pump()) {
1793 lp_solutions = std::make_unique<SharedLPSolutionRepository>(
1794 /*num_solutions_to_keep=*/10);
1795 global_model->Register<SharedLPSolutionRepository>(lp_solutions.get());
1796
1797 incomplete_solutions = std::make_unique<SharedIncompleteSolutionManager>();
1798 global_model->Register<SharedIncompleteSolutionManager>(
1799 incomplete_solutions.get());
1800 }
1801
1802 // Set up synchronization mode in parallel.
1803 const bool always_synchronize =
1804 !params.interleave_search() || params.num_workers() <= 1;
1805 response->SetSynchronizationMode(always_synchronize);
1806 if (params.share_binary_clauses() && params.num_workers() > 1) {
1807 clauses = std::make_unique<SharedClausesManager>(always_synchronize,
1808 absl::Seconds(1));
1809 }
1810}
1811
1813 if (response->ProblemIsSolved()) {
1814 // This is for cases where the time limit is checked more often.
1815 time_limit->Stop();
1816 return true;
1817 }
1818 if (time_limit->LimitReached()) return true;
1819 return false;
1820}
1821
1822} // namespace sat
1823} // namespace operations_research
IntegerValue size
A connected components finder that only works on dense ints.
bool AddEdge(int node1, int node2)
std::vector< int > GetComponentIds()
Returns the same as GetConnectedComponents().
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
bool IsIncludedIn(const Domain &domain) const
bool ComputeTransitiveReduction(bool log_info=false)
Definition clause.cc:1441
IntegerVariable NumIntegerVariables() const
Definition integer.h:806
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
Definition integer.h:972
void InitAllDecomposedEnergies()
Calls InitDecomposedEnergies on all SchedulingDemandHelper created.
Definition intervals.cc:222
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
Explores the search space.
BooleanVariable Variable() const
Definition sat_base.h:87
The model "singleton" shared time limit.
Definition util.h:331
void Register(T *non_owned_class)
Definition model.h:179
bool ProbeBooleanVariables(double deterministic_time_limit)
Definition probing.cc:59
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
void ReportPotentialNewBounds(const std::string &worker_name, const std::vector< int > &variables, const std::vector< int64_t > &new_lower_bounds, const std::vector< int64_t > &new_upper_bounds)
void AddBinaryClause(int id, int lit1, int lit2)
void GetUnseenBinaryClauses(int id, std::vector< std::pair< int, int > > *new_clauses)
std::vector< absl::Span< const int > > GetUnseenClauses(int id)
void SetSynchronizationMode(bool always_synchronize)
void InitializeObjective(const CpModelProto &cp_model)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
Updates the inner objective bounds.
Simple class to add statistics by name and print them at the end.
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
void assign(size_type n, const value_type &val)
SatParameters parameters
CpModelProto proto
The output proto.
ABSL_FLAG(bool, cp_model_dump_models, false, "DEBUG ONLY. When set to true, SolveCpModel() will dump its model " "protos (original model, presolved model, mapping model) in text " "format to 'FLAGS_cp_model_dump_prefix'{model|presolved_model|" "mapping_model}.pb.txt.")
const std::string name
A name for logging purposes.
const Constraint * ct
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
int lit
int literal
int ct_index
time_limit
Definition solve.cc:22
const bool DEBUG_MODE
Definition macros.h:24
double solution
Options Defaults()
Definition file.h:109
absl::Status GetTextProto(absl::string_view filename, google::protobuf::Message *proto, Options options)
Definition file.cc:327
std::function< void(Model *)> WeightedSumGreaterOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t lower_bound)
Weighted sum >= constant.
void ConfigureSearchHeuristics(Model *model)
std::vector< int64_t > GetSolutionValues(const CpModelProto &model_proto, const Model &model)
void SolveLoadedCpModel(const CpModelProto &model_proto, Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void LoadBaseModel(const CpModelProto &model_proto, Model *model)
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, absl::Span< const int64_t > solution)
void SplitAndLoadIntermediateConstraints(bool lb_required, bool ub_required, std::vector< IntegerVariable > *vars, std::vector< int64_t > *coeffs, Model *m)
std::function< void(Model *)> WeightedSumLowerOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t upper_bound)
Weighted sum <= constant.
const LiteralIndex kNoLiteralIndex(-1)
void DetectOptionalVariables(const CpModelProto &model_proto, Model *m)
Automatically detect optional variables.
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
LinearRelaxation ComputeLinearRelaxation(const CpModelProto &model_proto, Model *m)
Builds the linear relaxation of a CpModelProto.
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition integer.h:1955
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
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 LoadVariables(const CpModelProto &model_proto, bool view_all_booleans_as_integers, Model *m)
void PostsolveResponseWrapper(const SatParameters &params, int num_variable_in_original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, std::vector< int64_t > *solution)
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
void RegisterObjectiveBoundsImport(SharedResponseManager *shared_response_manager, Model *model)
std::function< BooleanOrIntegerLiteral()> ConstructFixedSearchStrategy(std::function< BooleanOrIntegerLiteral()> user_search, std::function< BooleanOrIntegerLiteral()> heuristic_search, std::function< BooleanOrIntegerLiteral()> integer_completion)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
std::function< BooleanOrIntegerLiteral()> ConstructHintSearchStrategy(const CpModelProto &cp_model_proto, CpModelMapping *mapping, Model *model)
Constructs a search strategy that follow the hint from the model.
void InitializeDebugSolution(const CpModelProto &model_proto, Model *model)
void PostsolveResponseWithFullSolver(int num_variables_in_original_model, CpModelProto mapping_proto, const std::vector< int > &postsolve_mapping, std::vector< int64_t > *solution)
const IntegerVariable kNoIntegerVariable(-1)
void RegisterClausesExport(int id, SharedClausesManager *shared_clauses_manager, Model *model)
Registers a callback that will export good clauses discovered during search.
void MinimizeL1DistanceWithHint(const CpModelProto &model_proto, Model *model)
std::function< IntegerVariable(Model *)> ConstantIntegerVariable(int64_t value)
Definition integer.h:1899
void LoadBooleanSymmetries(const CpModelProto &model_proto, Model *m)
IntegerVariable AddLPConstraints(bool objective_need_to_be_tight, const CpModelProto &model_proto, Model *m)
Adds one LinearProgrammingConstraint per connected component of the model.
void AddFullEncodingFromSearchBranching(const CpModelProto &model_proto, Model *m)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
std::function< BooleanOrIntegerLiteral()> ConstructHeuristicSearchStrategy(const CpModelProto &cp_model_proto, Model *model)
Constructs a search strategy tailored for the current model.
void LoadFeasibilityPump(const CpModelProto &model_proto, Model *model)
std::function< BooleanOrIntegerLiteral()> InstrumentSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, std::function< BooleanOrIntegerLiteral()> instrumented_strategy, Model *model)
void ExtractElementEncoding(const CpModelProto &model_proto, Model *m)
std::function< BooleanOrIntegerLiteral()> ConstructIntegerCompletionSearchStrategy(const std::vector< IntegerVariable > &variable_mapping, IntegerVariable objective_var, Model *model)
Constructs an integer completion search strategy.
std::function< int64_t(const Model &)> Value(IntegerVariable v)
This checks that the variable is fixed.
Definition integer.h:1975
void AdaptGlobalParameters(const CpModelProto &model_proto, Model *model)
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition integer.h:1961
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
std::function< IntegerVariable(Model *)> NewIntegerVariable(int64_t lb, int64_t ub)
Definition integer.h:1907
SatSolver::Status MinimizeIntegerVariableWithLinearScanAndLazyEncoding(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
void QuickSolveWithHint(const CpModelProto &model_proto, Model *model)
std::function< BooleanOrIntegerLiteral()> ConstructUserSearchStrategy(const CpModelProto &cp_model_proto, Model *model)
Constructs the search strategy specified in the given CpModelProto.
absl::string_view ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
void LoadDebugSolution(const CpModelProto &model_proto, Model *model)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
void RegisterVariableBoundsLevelZeroExport(const CpModelProto &, SharedBoundsManager *shared_bounds_manager, Model *model)
void PropagateEncodingFromEquivalenceRelations(const CpModelProto &model_proto, Model *m)
void RegisterVariableBoundsLevelZeroImport(const CpModelProto &model_proto, SharedBoundsManager *shared_bounds_manager, Model *model)
void LoadCpModel(const CpModelProto &model_proto, Model *model)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
bool VariableIsPositive(IntegerVariable i)
Definition integer.h:189
void RegisterObjectiveBestBoundExport(IntegerVariable objective_var, SharedResponseManager *shared_response_manager, Model *model)
std::function< void(Model *)> ExcludeCurrentSolutionAndBacktrack()
int RegisterClausesLevelZeroImport(int id, SharedClausesManager *shared_clauses_manager, Model *model)
void RestrictObjectiveDomainWithBinarySearch(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
void ExtractEncoding(const CpModelProto &model_proto, Model *m)
In SWIG mode, we don't want anything besides these top-level includes.
std::string ProtobufDebugString(const P &message)
Definition proto_utils.h:32
if(!yyg->yy_init)
Definition parser.yy.cc:965
int model_var
Definition rins.cc:99
std::vector< int64_t > proto_values
Definition integer.h:385
util_intops::StrongVector< IntegerVariable, bool > ivar_has_value
Definition integer.h:394
util_intops::StrongVector< IntegerVariable, IntegerValue > ivar_values
Definition integer.h:395
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1667
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1673
std::vector< LinearConstraint > linear_constraints
std::function< BooleanOrIntegerLiteral()> user_search
Contains the search specified by the user in CpModelProto.
SharedClasses(const CpModelProto *proto, Model *global_model)
double objective_value
The value objective_vector^T * (solution - center_point).
#define SOLVER_LOG(logger,...)
Definition logging.h:109