Google OR-Tools v9.15
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-2025 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"
31#if !defined(__PORTABLE_PLATFORM__)
34#endif // __PORTABLE_PLATFORM__
35#include "absl/algorithm/container.h"
36#include "absl/cleanup/cleanup.h"
37#include "absl/container/flat_hash_set.h"
38#include "absl/flags/flag.h"
39#include "absl/log/check.h"
40#include "absl/log/log.h"
41#include "absl/log/vlog_is_on.h"
42#include "absl/strings/escaping.h"
43#include "absl/strings/str_cat.h"
44#include "absl/strings/string_view.h"
45#include "absl/types/span.h"
46#include "google/protobuf/arena.h"
51#include "ortools/sat/clause.h"
60#include "ortools/sat/cuts.h"
63#include "ortools/sat/integer.h"
74#include "ortools/sat/max_hs.h"
75#include "ortools/sat/model.h"
78#include "ortools/sat/probing.h"
85#include "ortools/sat/util.h"
89#if !defined(__PORTABLE_PLATFORM__)
90#endif // __PORTABLE_PLATFORM__
95
97 std::string, cp_model_load_debug_solution, "",
98 "DEBUG ONLY. When this is set to a non-empty file name, "
99 "we will interpret this as an internal solution which can be used for "
100 "debugging. For instance we use it to identify wrong cuts/reasons.");
101
102namespace operations_research {
103namespace sat {
104
105// This should be called on the presolved model. It will read the file
106// specified by --cp_model_load_debug_solution and properly fill the
107// model->Get<DebugSolution>() proto vector.
108void LoadDebugSolution(const CpModelProto& model_proto, Model* model) {
109#if !defined(__PORTABLE_PLATFORM__)
110 if (absl::GetFlag(FLAGS_cp_model_load_debug_solution).empty()) return;
111
112 CpSolverResponse response;
114 "Reading debug solution from '",
115 absl::GetFlag(FLAGS_cp_model_load_debug_solution), "'.");
116 CHECK_OK(file::GetTextProto(absl::GetFlag(FLAGS_cp_model_load_debug_solution),
117 &response, file::Defaults()));
118
119 // Make sure we load a solution with the same number of variable has in the
120 // presolved model.
121 CHECK_EQ(response.solution().size(), model_proto.variables().size());
123 response.solution());
124#endif // __PORTABLE_PLATFORM__
125}
126
127// This both copy the "main" DebugSolution to a local_model and also cache
128// the value of the integer variables in that solution.
129void InitializeDebugSolution(const CpModelProto& model_proto, Model* model) {
130 auto* shared_response = model->Get<SharedResponseManager>();
131 if (shared_response == nullptr) return;
132 if (shared_response->DebugSolution().empty()) return;
133
134 if (!SolutionIsFeasible(model_proto, shared_response->DebugSolution())) {
135 // TODO(user): we should probably CHECK-fail.
137 "Debug solution is not feasible.");
138 return;
139 }
140 SOLVER_LOG(model->GetOrCreate<SolverLogger>(), "Debug solution is feasible.");
141
142 // Copy the proto values.
143 DebugSolution& debug_sol = *model->GetOrCreate<DebugSolution>();
144 debug_sol.proto_values = shared_response->DebugSolution();
145
146 // Fill the values by integer variable.
147 const int num_integers =
148 model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value();
149 debug_sol.ivar_has_value.assign(num_integers, false);
150 debug_sol.ivar_values.assign(num_integers, 0);
151
152 std::vector<Literal> boolean_solution;
153
154 const auto& mapping = *model->GetOrCreate<CpModelMapping>();
155 for (int i = 0; i < debug_sol.proto_values.size(); ++i) {
156 if (mapping.IsBoolean(i)) {
157 Literal l = mapping.Literal(i);
158 if (debug_sol.proto_values[i] == 0) {
159 l = l.Negated();
160 }
161 boolean_solution.push_back(l);
162 }
163
164 if (!mapping.IsInteger(i)) continue;
165 const IntegerVariable var = mapping.Integer(i);
166 debug_sol.ivar_has_value[var] = true;
167 debug_sol.ivar_has_value[NegationOf(var)] = true;
168 debug_sol.ivar_values[var] = debug_sol.proto_values[i];
169 debug_sol.ivar_values[NegationOf(var)] = -debug_sol.proto_values[i];
170 }
171
172 // If the solution is fully boolean (there is no integer variable), and
173 // we have a decision problem (so no new boolean should be created), we load
174 // it in the sat solver for debugging too.
175 if (boolean_solution.size() == debug_sol.proto_values.size() &&
176 !model_proto.has_objective()) {
178 "Loaded pure Boolean debugging solution.");
179 model->GetOrCreate<SatSolver>()->LoadDebugSolution(boolean_solution);
180 }
181
182 // The objective variable is usually not part of the proto, but it is still
183 // nice to have it, so we recompute it here.
184 auto* objective_def = model->Get<ObjectiveDefinition>();
185 if (objective_def != nullptr &&
186 objective_def->objective_var != kNoIntegerVariable) {
187 if (absl::c_all_of(objective_def->vars, [&debug_sol](IntegerVariable var) {
188 return var < debug_sol.ivar_has_value.end_index() &&
189 debug_sol.ivar_has_value[var];
190 })) {
191 const IntegerVariable objective_var = objective_def->objective_var;
192 if (objective_var + 1 >= debug_sol.ivar_has_value.size()) {
193 debug_sol.ivar_has_value.resize(objective_var + 2, false);
194 debug_sol.ivar_values.resize(objective_var + 2, 0);
195 }
196 IntegerValue objective_value = 0;
197 for (int i = 0; i < objective_def->vars.size(); ++i) {
198 objective_value += objective_def->coeffs[i] *
199 debug_sol.ivar_values[objective_def->vars[i]];
200 }
202 model->GetOrCreate<SolverLogger>(),
203 absl::StrCat("Debug solution objective value: ",
204 objective_def->ScaleIntegerObjective(objective_value)));
205 debug_sol.ivar_has_value[objective_var] = true;
206 debug_sol.ivar_has_value[NegationOf(objective_var)] = true;
207 debug_sol.ivar_values[objective_var] = objective_value;
208 debug_sol.ivar_values[NegationOf(objective_var)] = -objective_value;
209 debug_sol.inner_objective_value = objective_value;
210 }
211 }
212
213 // We also register a DEBUG callback to check our reasons.
214 auto* encoder = model->GetOrCreate<IntegerEncoder>();
215 const auto checker = [mapping = &mapping, encoder, model](
216 absl::Span<const Literal> clause,
217 absl::Span<const IntegerLiteral> integers) {
218 const DebugSolution* debug_sol = model->Get<DebugSolution>();
219 if (!debug_sol || debug_sol->proto_values.empty()) return true;
220
221 bool is_satisfied = false;
222 std::vector<std::tuple<Literal, IntegerLiteral, IntegerValue>> to_print;
223 for (const Literal l : clause) {
224 // First case, this Boolean is mapped.
225 {
226 const int proto_var =
227 mapping->GetProtoVariableFromBooleanVariable(l.Variable());
228 if (proto_var != -1) {
229 CHECK_LT(proto_var, debug_sol->proto_values.size());
230 to_print.push_back(
231 {l, IntegerLiteral(), debug_sol->proto_values[proto_var]});
232 if (debug_sol->proto_values[proto_var] == (l.IsPositive() ? 1 : 0)) {
233 is_satisfied = true;
234 break;
235 }
236 continue;
237 }
238 }
239
240 // Second case, it is associated to IntVar >= value.
241 // We can use any of them, so if one is false, we use this one.
242 bool all_true = true;
243 for (const IntegerLiteral associated : encoder->GetIntegerLiterals(l)) {
244 if (associated.var >= debug_sol->ivar_has_value.end_index() ||
245 !debug_sol->ivar_has_value[associated.var]) {
246 continue;
247 }
248 const IntegerValue value = debug_sol->ivar_values[associated.var];
249 to_print.push_back({l, associated, value});
250
251 if (value < associated.bound) {
252 all_true = false;
253 break;
254 }
255 }
256 if (all_true) {
257 is_satisfied = true;
258 break;
259 }
260 }
261 for (const IntegerLiteral i_lit : integers) {
262 DCHECK(!i_lit.IsAlwaysFalse());
263 if (i_lit.IsAlwaysTrue()) continue;
264 if (i_lit.var >= debug_sol->ivar_has_value.end_index() ||
265 !debug_sol->ivar_has_value[i_lit.var]) {
266 is_satisfied = true;
267 break;
268 }
269
270 const IntegerValue value = debug_sol->ivar_values[i_lit.var];
271 to_print.push_back({Literal(kNoLiteralIndex), i_lit, value});
272
273 // This is a bit confusing, but since the i_lit in the reason are
274 // not "negated", we need at least one to be FALSE, for the reason to
275 // be valid.
276 if (value < i_lit.bound) {
277 is_satisfied = true;
278 break;
279 }
280 }
281 if (!is_satisfied) {
282 LOG(INFO) << "Reason clause is not satisfied by loaded solution:";
283 LOG(INFO) << "Worker '" << model->Name() << "', level="
284 << model->GetOrCreate<SatSolver>()->CurrentDecisionLevel();
285 LOG(INFO) << "literals (neg): " << clause;
286 LOG(INFO) << "integer literals: " << integers;
287 for (const auto [l, i_lit, solution_value] : to_print) {
288 if (i_lit.IsAlwaysTrue()) {
289 const int proto_var =
290 mapping->GetProtoVariableFromBooleanVariable(l.Variable());
291 LOG(INFO) << l << " (bool in model) proto_var=" << proto_var
292 << " value_in_sol=" << solution_value;
293 } else {
294 const int proto_var = mapping->GetProtoVariableFromIntegerVariable(
295 PositiveVariable(i_lit.var));
296 LOG(INFO) << l << " " << i_lit << " proto_var="
297 << (proto_var == -1 ? "none" : absl::StrCat(proto_var))
298 << (VariableIsPositive(i_lit.var) ? "" : " (negated)")
299 << " value_in_sol=" << solution_value;
300 }
301 }
302 }
303 return is_satisfied;
304 };
305 const auto lit_checker = [checker =
306 checker](absl::Span<const Literal> clause) {
307 return checker(clause, {});
308 };
309
310 model->GetOrCreate<Trail>()->RegisterDebugChecker(lit_checker);
311 model->GetOrCreate<IntegerTrail>()->RegisterDebugChecker(checker);
312}
313
314std::vector<int64_t> GetSolutionValues(const CpModelProto& model_proto,
315 const Model& model) {
316 auto* mapping = model.Get<CpModelMapping>();
317 auto* trail = model.Get<Trail>();
318
319 std::vector<int64_t> solution;
320 for (int i = 0; i < model_proto.variables_size(); ++i) {
321 if (mapping->IsInteger(i)) {
322 const IntegerVariable var = mapping->Integer(i);
323
324 // For ignored or not fully instantiated variable, we just use the
325 // lower bound.
326 solution.push_back(model.Get(LowerBound(var)));
327 } else {
328 DCHECK(mapping->IsBoolean(i));
329 const Literal literal = mapping->Literal(i);
330 if (trail->Assignment().LiteralIsAssigned(literal)) {
331 solution.push_back(model.Get(Value(literal)));
332 } else {
333 // Just use the lower bound if the variable is not fully instantiated.
334 solution.push_back(0);
335 }
336 }
337 }
338 return solution;
339}
340
341namespace {
342
343IntegerVariable GetOrCreateVariableWithTightBound(
344 absl::Span<const std::pair<IntegerVariable, int64_t>> terms, Model* model) {
345 if (terms.empty()) return model->Add(ConstantIntegerVariable(0));
346 if (terms.size() == 1 && terms.front().second == 1) {
347 return terms.front().first;
348 }
349 if (terms.size() == 1 && terms.front().second == -1) {
350 return NegationOf(terms.front().first);
351 }
352
353 int64_t sum_min = 0;
354 int64_t sum_max = 0;
355 for (const std::pair<IntegerVariable, int64_t>& var_coeff : terms) {
356 const int64_t min_domain = model->Get(LowerBound(var_coeff.first));
357 const int64_t max_domain = model->Get(UpperBound(var_coeff.first));
358 const int64_t coeff = var_coeff.second;
359 const int64_t prod1 = min_domain * coeff;
360 const int64_t prod2 = max_domain * coeff;
361 sum_min += std::min(prod1, prod2);
362 sum_max += std::max(prod1, prod2);
363 }
364 return model->Add(NewIntegerVariable(sum_min, sum_max));
365}
366
367IntegerVariable GetOrCreateVariableLinkedToSumOf(
368 absl::Span<const std::pair<IntegerVariable, int64_t>> terms,
369 bool lb_required, bool ub_required, Model* model) {
370 if (terms.empty()) return model->Add(ConstantIntegerVariable(0));
371 if (terms.size() == 1 && terms.front().second == 1) {
372 return terms.front().first;
373 }
374 if (terms.size() == 1 && terms.front().second == -1) {
375 return NegationOf(terms.front().first);
376 }
377
378 const IntegerVariable new_var =
379 GetOrCreateVariableWithTightBound(terms, model);
380
381 // TODO(user): use the same format, i.e. LinearExpression in both code!
382 std::vector<IntegerVariable> vars;
383 std::vector<IntegerValue> coeffs;
384 for (const auto [var, coeff] : terms) {
385 vars.push_back(var);
386 coeffs.push_back(IntegerValue(coeff));
387 }
388 vars.push_back(new_var);
389 coeffs.push_back(IntegerValue(-1));
390
391 // Split if linear is large.
392 if (vars.size() > model->GetOrCreate<SatParameters>()->linear_split_size()) {
393 SplitAndLoadIntermediateConstraints(lb_required, ub_required, &vars,
394 &coeffs, model);
395 }
396
397 // Load the top-level constraint with the required sides.
398 if (lb_required) {
399 AddWeightedSumGreaterOrEqual({}, vars, coeffs, 0, model);
400 }
401 if (ub_required) {
402 AddWeightedSumLowerOrEqual({}, vars, coeffs, 0, model);
403 }
404
405 return new_var;
406}
407
408// Currently, the LP will exploit symmetry if we load some in the
409// LinearConstraintSymmetrizer. So not loading them disable the feature.
410//
411// TODO(user): We probably want to separate the two as we could still use orbits
412// in other places while not doing so in the LP.
413void InitializeLinearConstraintSymmetrizerIfRequested(
414 const CpModelProto& model_proto, const LinearRelaxation& linear_relaxation,
415 Model* m) {
416 if (!model_proto.has_symmetry()) return;
417
418 auto* params = m->GetOrCreate<SatParameters>();
419 if (params->linearization_level() < 2) return;
420 if (!params->use_symmetry_in_lp()) return;
421
422 // Tricky: while we load the model, we might create new integer-variables, and
423 // in some rare case, these variable can appear in the LP relaxation. This
424 // might happen when we extend an at most one or when we use an integer
425 // encoding.
426 //
427 // The issue with this and having symmetry is that we didn't extend the
428 // problem symmetries to include these new variables, so we can derive wrong
429 // conclusion. When we use symmetry in the LP we cannot have any variable like
430 // this part of a LinearProgrammingConstraint.
431 auto* mapping = m->GetOrCreate<CpModelMapping>();
432 int num_constraints_with_non_proto_variables = 0;
433 for (const auto& lp_constraint : linear_relaxation.linear_constraints) {
434 bool has_non_proto_variable = false;
435 for (const IntegerVariable var : lp_constraint.VarsAsSpan()) {
436 if (mapping->GetProtoVariableFromIntegerVariable(var) == -1) {
437 has_non_proto_variable = true;
438 break;
439 }
440 }
441 if (has_non_proto_variable) {
442 ++num_constraints_with_non_proto_variables;
443 }
444 }
445 if (num_constraints_with_non_proto_variables > 0) {
446 // TODO(user): Logging like this is not visible in multi-thread, so we will
447 // not have a lot of warning if this happens a lot.
448 auto* logger = m->GetOrCreate<SolverLogger>();
449 SOLVER_LOG(logger, num_constraints_with_non_proto_variables,
450 " LP constraints uses new variables not appearing in the "
451 "presolved model. ");
452
453 // TODO(user): We currently disable symmetries in LP completely when this
454 // happen, but we could probably be smarter about this. I am not really
455 // sure we want to create such extra variable in the first place :)
456 return;
457 }
458
459 // Convert to SparsePermutation.
460 const int num_vars = model_proto.variables().size();
461 std::vector<std::unique_ptr<SparsePermutation>> generators;
462 for (const SparsePermutationProto& perm :
463 model_proto.symmetry().permutations()) {
464 generators.emplace_back(CreateSparsePermutationFromProto(num_vars, perm));
465 }
466
467 // Get orbits in term of IntegerVariable.
468 const std::vector<int> var_to_orbit_index = GetOrbits(num_vars, generators);
469 std::vector<bool> orbit_is_ok;
470 std::vector<std::vector<IntegerVariable>> orbits;
471 for (int proto_var = 0; proto_var < num_vars; ++proto_var) {
472 const int orbit_index = var_to_orbit_index[proto_var];
473 if (orbit_index == -1) continue;
474 if (orbit_index >= orbits.size()) {
475 orbits.resize(orbit_index + 1);
476 orbit_is_ok.resize(orbit_index + 1, true);
477 }
478
479 // In linearization level >=2, all variables should have a view.
480 // Otherwise revisit and skip orbit without a full view.
481 const IntegerVariable var = mapping->Integer(proto_var);
482 CHECK_NE(var, kNoIntegerVariable);
483 orbits[orbit_index].push_back(var);
484 }
485
486 // Lets create the orbit sum vars and register each orbit.
487 auto* symmetrizer = m->GetOrCreate<LinearConstraintSymmetrizer>();
488 std::vector<std::pair<IntegerVariable, int64_t>> terms;
489 for (const std::vector<IntegerVariable>& orbit : orbits) {
490 terms.clear();
491 for (const IntegerVariable var : orbit) {
492 terms.push_back({var, 1});
493 }
494 const IntegerVariable sum_var =
495 GetOrCreateVariableLinkedToSumOf(terms, true, true, m);
496 symmetrizer->AddSymmetryOrbit(sum_var, orbit);
497 }
498}
499
500// Adds one LinearProgrammingConstraint per connected component of the model.
501IntegerVariable AddLPConstraints(bool objective_need_to_be_tight,
502 const CpModelProto& model_proto, Model* m) {
503 // Non const as we will std::move() stuff out of there.
504 LinearRelaxation relaxation = ComputeLinearRelaxation(model_proto, m);
505 if (m->GetOrCreate<SatSolver>()->ModelIsUnsat()) return kNoIntegerVariable;
506
507 // Load symmetry?
508 InitializeLinearConstraintSymmetrizerIfRequested(model_proto, relaxation, m);
509
510 // The bipartite graph of LP constraints might be disconnected:
511 // make a partition of the variables into connected components.
512 // Constraint nodes are indexed by [0..num_lp_constraints),
513 // variable nodes by [num_lp_constraints..num_lp_constraints+num_variables).
514 //
515 // TODO(user): look into biconnected components.
516 const int num_lp_constraints =
517 static_cast<int>(relaxation.linear_constraints.size());
518 const int num_lp_cut_generators =
519 static_cast<int>(relaxation.cut_generators.size());
520 const int num_integer_variables =
521 m->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value();
522
523 DenseConnectedComponentsFinder components;
524 components.SetNumberOfNodes(num_lp_constraints + num_lp_cut_generators +
525 num_integer_variables);
526 auto get_constraint_index = [](int ct_index) { return ct_index; };
527 auto get_cut_generator_index = [num_lp_constraints](int cut_index) {
528 return num_lp_constraints + cut_index;
529 };
530 auto get_var_index = [num_lp_constraints,
531 num_lp_cut_generators](IntegerVariable var) {
532 return num_lp_constraints + num_lp_cut_generators +
533 PositiveVariable(var).value();
534 };
535 for (int i = 0; i < num_lp_constraints; i++) {
536 for (const IntegerVariable var :
537 relaxation.linear_constraints[i].VarsAsSpan()) {
538 components.AddEdge(get_constraint_index(i), get_var_index(var));
539 }
540 }
541 for (int i = 0; i < num_lp_cut_generators; ++i) {
542 for (const IntegerVariable var : relaxation.cut_generators[i].vars) {
543 components.AddEdge(get_cut_generator_index(i), get_var_index(var));
544 }
545 }
546
547 // Make sure variables from the same orbit end up in same components.
548 auto* symmetrizer = m->GetOrCreate<LinearConstraintSymmetrizer>();
549 for (int i = 0; i < symmetrizer->NumOrbits(); ++i) {
550 const int representative = get_var_index(symmetrizer->OrbitSumVar(i));
551 for (const IntegerVariable var : symmetrizer->Orbit(i)) {
552 components.AddEdge(representative, get_var_index(var));
553 }
554 }
555
556 const int num_components = components.GetNumberOfComponents();
557 std::vector<int> component_sizes(num_components, 0);
558 const std::vector<int> index_to_component = components.GetComponentIds();
559 for (int i = 0; i < num_lp_constraints; i++) {
560 ++component_sizes[index_to_component[get_constraint_index(i)]];
561 }
562 for (int i = 0; i < num_lp_cut_generators; i++) {
563 ++component_sizes[index_to_component[get_cut_generator_index(i)]];
564 }
565
566 // TODO(user): Optimize memory layout.
567 std::vector<std::vector<IntegerVariable>> component_to_var(num_components);
568 for (IntegerVariable var(0); var < num_integer_variables; var += 2) {
569 DCHECK(VariableIsPositive(var));
570 component_to_var[index_to_component[get_var_index(var)]].push_back(var);
571 }
572
573 // Make sure any constraint that touch the objective is not discarded even
574 // if it is the only one in its component. This is important to propagate
575 // as much as possible the objective bound by using any bounds the LP give
576 // us on one of its components. This is critical on the zephyrus problems for
577 // instance.
578 auto* mapping = m->GetOrCreate<CpModelMapping>();
579 for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
580 const IntegerVariable var =
581 mapping->Integer(model_proto.objective().vars(i));
582 ++component_sizes[index_to_component[get_var_index(var)]];
583 }
584
585 // Dispatch every constraint to its LinearProgrammingConstraint.
586 std::vector<LinearProgrammingConstraint*> lp_constraints(num_components,
587 nullptr);
588 for (int i = 0; i < num_lp_constraints; i++) {
589 const int c = index_to_component[get_constraint_index(i)];
590 if (component_sizes[c] <= 1) continue;
591 if (lp_constraints[c] == nullptr) {
592 lp_constraints[c] =
593 new LinearProgrammingConstraint(m, component_to_var[c]);
594 m->TakeOwnership(lp_constraints[c]);
595 }
596 // Load the constraint.
597 if (!lp_constraints[c]->AddLinearConstraint(
598 std::move(relaxation.linear_constraints[i]))) {
599 m->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
600 return kNoIntegerVariable;
601 }
602 }
603
604 // Dispatch every cut generator to its LinearProgrammingConstraint.
605 for (int i = 0; i < num_lp_cut_generators; i++) {
606 const int c = index_to_component[get_cut_generator_index(i)];
607 if (lp_constraints[c] == nullptr) {
608 lp_constraints[c] =
609 new LinearProgrammingConstraint(m, component_to_var[c]);
610 m->TakeOwnership(lp_constraints[c]);
611 }
612 lp_constraints[c]->AddCutGenerator(std::move(relaxation.cut_generators[i]));
613 }
614
615 // We deal with the clique cut generator here now that the component have
616 // been computed. As we don't want to merge independent component with it.
617 auto* params = m->GetOrCreate<SatParameters>();
618 if (params->linearization_level() > 1 && params->add_clique_cuts() &&
619 params->cut_level() > 0) {
620 for (LinearProgrammingConstraint* lp : lp_constraints) {
621 if (lp == nullptr) continue;
622 lp->AddCutGenerator(CreateCliqueCutGenerator(lp->integer_variables(), m));
623 }
624 }
625
626 // Add the objective.
627 std::vector<std::vector<std::pair<IntegerVariable, int64_t>>>
628 component_to_cp_terms(num_components);
629 std::vector<std::pair<IntegerVariable, int64_t>> top_level_cp_terms;
630 int num_components_containing_objective = 0;
631 if (model_proto.has_objective()) {
632 // First convert the proto objective to an IntegerVariable one. In case of
633 // "use_symmetry_in_lp", we also rewrite it in terms of the sum of the
634 // variables in the orbits.
635 std::vector<std::pair<IntegerVariable, int64_t>> objective;
636 const int num_orbits = symmetrizer->NumOrbits();
637 if (num_orbits > 0) {
638 // We use the orbit_sum var instead.
639 std::vector<int64_t> orbit_obj_coeff(num_orbits, 0);
640 for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
641 const IntegerVariable var =
642 mapping->Integer(model_proto.objective().vars(i));
643 const int64_t coeff = model_proto.objective().coeffs(i);
644 const int orbit_index = symmetrizer->OrbitIndex(var);
645 if (orbit_index != -1) {
646 if (orbit_obj_coeff[orbit_index] == 0) {
647 orbit_obj_coeff[orbit_index] = coeff;
648 } else {
649 CHECK_EQ(orbit_obj_coeff[orbit_index], coeff);
650 }
651 continue;
652 }
653 objective.push_back({var, coeff});
654 }
655 for (int i = 0; i < num_orbits; ++i) {
656 if (orbit_obj_coeff[i] == 0) continue;
657 objective.push_back({symmetrizer->OrbitSumVar(i), orbit_obj_coeff[i]});
658 }
659 } else {
660 for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
661 const IntegerVariable var =
662 mapping->Integer(model_proto.objective().vars(i));
663 const int64_t coeff = model_proto.objective().coeffs(i);
664 objective.push_back({var, coeff});
665 }
666 }
667
668 // First pass: set objective coefficients on the lp constraints, and store
669 // the cp terms in one vector per component.
670 for (const auto [var, coeff] : objective) {
671 const int c = index_to_component[get_var_index(var)];
672 if (lp_constraints[c] != nullptr) {
673 lp_constraints[c]->SetObjectiveCoefficient(var, IntegerValue(coeff));
674 component_to_cp_terms[c].push_back(std::make_pair(var, coeff));
675 } else {
676 // Component is too small. We still need to store the objective term.
677 top_level_cp_terms.push_back(std::make_pair(var, coeff));
678 }
679 }
680 // Second pass: Build the cp sub-objectives per component.
681 for (int c = 0; c < num_components; ++c) {
682 if (component_to_cp_terms[c].empty()) continue;
683 const IntegerVariable sub_obj_var = GetOrCreateVariableLinkedToSumOf(
684 component_to_cp_terms[c], objective_need_to_be_tight, true, m);
685 top_level_cp_terms.push_back(std::make_pair(sub_obj_var, 1));
686 lp_constraints[c]->SetMainObjectiveVariable(sub_obj_var);
687 num_components_containing_objective++;
688 }
689 }
690
691 const IntegerVariable main_objective_var =
692 model_proto.has_objective()
693 ? GetOrCreateVariableLinkedToSumOf(
694 top_level_cp_terms, objective_need_to_be_tight, true, m)
696
697 // Register LP constraints. Note that this needs to be done after all the
698 // constraints have been added.
699 for (LinearProgrammingConstraint* lp_constraint : lp_constraints) {
700 if (lp_constraint == nullptr) continue;
701 lp_constraint->RegisterWith(m);
702 VLOG(3) << "LP constraint: " << lp_constraint->DimensionString() << ".";
703 }
704
705 VLOG(3) << top_level_cp_terms.size()
706 << " terms in the main objective linear equation ("
707 << num_components_containing_objective << " from LP constraints).";
708 return main_objective_var;
709}
710
711} // namespace
712
713// Registers a callback that will export variables bounds fixed at level 0 of
714// the search. This should not be registered to a LNS search.
716 const CpModelProto& /*model_proto*/,
717 SharedBoundsManager* shared_bounds_manager, Model* model) {
718 CHECK(shared_bounds_manager != nullptr);
719
720 auto* mapping = model->GetOrCreate<CpModelMapping>();
721 auto* trail = model->Get<Trail>();
722 auto* integer_trail = model->Get<IntegerTrail>();
723
724 int saved_trail_index = 0;
725 std::vector<int> model_variables;
726 std::vector<int64_t> new_lower_bounds;
727 std::vector<int64_t> new_upper_bounds;
728 absl::flat_hash_set<int> visited_variables;
729 const std::string name = model->Name();
730
731 auto broadcast_level_zero_bounds =
732 [=](absl::Span<const IntegerVariable> modified_vars) mutable {
733 // Inspect the modified IntegerVariables.
734 for (const IntegerVariable& var : modified_vars) {
735 const IntegerVariable positive_var = PositiveVariable(var);
736 const int model_var =
737 mapping->GetProtoVariableFromIntegerVariable(positive_var);
738
739 if (model_var == -1) continue;
740 const auto [_, inserted] = visited_variables.insert(model_var);
741 if (!inserted) continue;
742
743 const int64_t new_lb =
744 integer_trail->LevelZeroLowerBound(positive_var).value();
745 const int64_t new_ub =
746 integer_trail->LevelZeroUpperBound(positive_var).value();
747
748 // TODO(user): We could imagine an API based on atomic<int64_t>
749 // that could preemptively check if this new bounds are improving.
750 model_variables.push_back(model_var);
751 new_lower_bounds.push_back(new_lb);
752 new_upper_bounds.push_back(new_ub);
753 }
754
755 // Inspect the newly modified Booleans.
756 for (; saved_trail_index < trail->Index(); ++saved_trail_index) {
757 const Literal fixed_literal = (*trail)[saved_trail_index];
758 const int model_var = mapping->GetProtoVariableFromBooleanVariable(
759 fixed_literal.Variable());
760
761 if (model_var == -1) continue;
762 const auto [_, inserted] = visited_variables.insert(model_var);
763 if (!inserted) continue;
764
765 model_variables.push_back(model_var);
766 if (fixed_literal.IsPositive()) {
767 new_lower_bounds.push_back(1);
768 new_upper_bounds.push_back(1);
769 } else {
770 new_lower_bounds.push_back(0);
771 new_upper_bounds.push_back(0);
772 }
773 }
774
775 if (!model_variables.empty()) {
776 shared_bounds_manager->ReportPotentialNewBounds(
777 model->Name(), model_variables, new_lower_bounds,
778 new_upper_bounds);
779
780 // Clear for next call.
781 model_variables.clear();
782 new_lower_bounds.clear();
783 new_upper_bounds.clear();
784 visited_variables.clear();
785
786 // If we are not in interleave_search we synchronize right away.
787 if (!model->Get<SatParameters>()->interleave_search()) {
788 shared_bounds_manager->Synchronize();
789 }
790 }
791 };
792
793 // The callback will just be called on NEWLY modified var. So initially,
794 // we do want to read all variables.
795 //
796 // TODO(user): Find a better way? It seems nicer to register this before
797 // any variable is modified. But then we don't want to call it each time
798 // we reach level zero during probing. It should be better to only call
799 // it when a new variable has been fixed.
800 const IntegerVariable num_vars =
801 model->GetOrCreate<IntegerTrail>()->NumIntegerVariables();
802 std::vector<IntegerVariable> all_variables;
803 all_variables.reserve(num_vars.value());
804 for (IntegerVariable var(0); var < num_vars; ++var) {
805 all_variables.push_back(var);
806 }
807 broadcast_level_zero_bounds(all_variables);
808
810 ->RegisterLevelZeroModifiedVariablesCallback(broadcast_level_zero_bounds);
811}
812
813// Registers a callback to import new variables bounds stored in the
814// shared_bounds_manager. These bounds are imported at level 0 of the search
815// in the linear scan minimize function.
817 const CpModelProto& model_proto, SharedBoundsManager* shared_bounds_manager,
818 Model* model) {
819 CHECK(shared_bounds_manager != nullptr);
820 const std::string name = model->Name();
821 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
822 auto* trail = model->GetOrCreate<Trail>();
823 auto* sat_solver = model->GetOrCreate<SatSolver>();
824 auto* mapping = model->GetOrCreate<CpModelMapping>();
825 auto* lrat_proof_handler = model->Mutable<LratProofHandler>();
826 auto* clause_id_generator = model->GetOrCreate<ClauseIdGenerator>();
827 const int id = shared_bounds_manager->RegisterNewId();
828
829 const auto& import_level_zero_bounds =
830 [&model_proto, shared_bounds_manager, name = name, sat_solver,
831 integer_trail, trail, lrat_proof_handler, clause_id_generator, id,
832 mapping]() {
833 std::vector<int> model_variables;
834 std::vector<int64_t> new_lower_bounds;
835 std::vector<int64_t> new_upper_bounds;
836 shared_bounds_manager->GetChangedBounds(
837 id, &model_variables, &new_lower_bounds, &new_upper_bounds);
838 for (int i = 0; i < model_variables.size(); ++i) {
839 const int model_var = model_variables[i];
840
841 // If this is a Boolean, fix it if not already done.
842 // Note that it is important not to use AddUnitClause() as we do not
843 // want to propagate after each addition.
844 if (mapping->IsBoolean(model_var)) {
845 Literal lit = mapping->Literal(model_var);
846 if (new_upper_bounds[i] == 0) lit = lit.Negated();
847 if (trail->Assignment().LiteralIsTrue(lit)) continue;
848 ClauseId clause_id = kNoClauseId;
849 if (lrat_proof_handler != nullptr) {
850 clause_id = clause_id_generator->GetNextId();
851 lrat_proof_handler->AddImportedClause(clause_id, {lit});
852 }
853 if (trail->Assignment().LiteralIsFalse(lit)) {
854 if (lrat_proof_handler != nullptr) {
855 // Add the UNSAT proof.
856 lrat_proof_handler->AddInferredClause(
857 clause_id_generator->GetNextId(), {},
858 {clause_id, trail->GetUnitClauseId(lit.Variable())});
859 }
860 sat_solver->NotifyThatModelIsUnsat();
861 return false;
862 }
863 trail->EnqueueWithUnitReason(clause_id, lit);
864 continue;
865 }
866
867 // Deal with integer.
868 if (!mapping->IsInteger(model_var)) continue;
869 const IntegerVariable var = mapping->Integer(model_var);
870 const IntegerValue new_lb(new_lower_bounds[i]);
871 const IntegerValue new_ub(new_upper_bounds[i]);
872 const IntegerValue old_lb = integer_trail->LowerBound(var);
873 const IntegerValue old_ub = integer_trail->UpperBound(var);
874 const bool changed_lb = new_lb > old_lb;
875 const bool changed_ub = new_ub < old_ub;
876 if (!changed_lb && !changed_ub) continue;
877
878 if (VLOG_IS_ON(3)) {
879 const IntegerVariableProto& var_proto =
880 model_proto.variables(model_var);
881 const std::string& var_name =
882 var_proto.name().empty()
883 ? absl::StrCat("anonymous_var(", model_var, ")")
884 : var_proto.name();
885 LOG(INFO) << " '" << name << "' imports new bounds for "
886 << var_name << ": from [" << old_lb << ", " << old_ub
887 << "] to [" << new_lb << ", " << new_ub << "]";
888 }
889
890 if (changed_lb &&
891 !integer_trail->Enqueue(
892 IntegerLiteral::GreaterOrEqual(var, new_lb), {}, {})) {
893 return false;
894 }
895 if (changed_ub &&
896 !integer_trail->Enqueue(IntegerLiteral::LowerOrEqual(var, new_ub),
897 {}, {})) {
898 return false;
899 }
900 }
901
902 // Note that we will propagate if they are new bounds separately.
903 // See BeforeTakingDecision().
904 return true;
905 };
906 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
907 import_level_zero_bounds);
908}
909
911 Model* model) {
912 CHECK(shared_linear2_bounds != nullptr);
913 auto* cp_model_mapping = model->GetOrCreate<CpModelMapping>();
914 auto* root_linear2 = model->GetOrCreate<RootLevelLinear2Bounds>();
915 auto* sat_solver = model->GetOrCreate<SatSolver>();
916 const int import_id =
917 shared_linear2_bounds->RegisterNewImportId(model->Name());
918 const auto& import_function = [import_id, shared_linear2_bounds, root_linear2,
919 cp_model_mapping, sat_solver, model]() {
920 const auto new_bounds =
921 shared_linear2_bounds->NewlyUpdatedBounds(import_id);
922 int num_imported = 0;
923 for (const auto& [proto_expr, bounds] : new_bounds) {
924 // Lets create the corresponding LinearExpression2.
926 if (!cp_model_mapping->IsInteger(proto_expr.vars[0]) ||
927 !cp_model_mapping->IsInteger(proto_expr.vars[1])) {
928 continue;
929 }
930 for (const int i : {0, 1}) {
931 expr.vars[i] = cp_model_mapping->Integer(proto_expr.vars[i]);
932 expr.coeffs[i] = proto_expr.coeffs[i];
933 }
934 const auto [lb, ub] = bounds;
935 const auto [lb_added, ub_added] = root_linear2->Add(expr, lb, ub);
936 if (!lb_added && !ub_added) continue;
937 ++num_imported;
938
939 // TODO(user): Is it a good idea to add the linear constraint ?
940 // We might have many redundant linear2 relations that don't need
941 // propagation when we have chains of precedences. The root_linear2 should
942 // be up-to-date with transitive closure to avoid adding such relations
943 // (recompute it at level zero before this?).
944 const std::vector<IntegerValue> coeffs = {expr.coeffs[0].value(),
945 expr.coeffs[1].value()};
946 if (lb_added) {
947 AddWeightedSumGreaterOrEqual({}, absl::MakeSpan(expr.vars, 2), coeffs,
948 lb.value(), model);
949 if (sat_solver->ModelIsUnsat()) return false;
950 }
951 if (ub_added) {
952 AddWeightedSumLowerOrEqual({}, absl::MakeSpan(expr.vars, 2), coeffs,
953 ub.value(), model);
954 if (sat_solver->ModelIsUnsat()) return false;
955 }
956 }
957 shared_linear2_bounds->NotifyNumImported(import_id, num_imported);
958 return true;
959 };
960 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
961 import_function);
962}
963
964// Registers a callback that will report improving objective best bound.
965// It will be called each time new objective bound are propagated at level zero.
967 IntegerVariable objective_var,
968 SharedResponseManager* shared_response_manager, Model* model) {
969 auto* integer_trail = model->Get<IntegerTrail>();
970 const auto broadcast_objective_lower_bound =
971 [objective_var, integer_trail, shared_response_manager, model,
972 best_obj_lb =
973 kMinIntegerValue](absl::Span<const IntegerVariable>) mutable {
974 const IntegerValue objective_lb =
975 integer_trail->LevelZeroLowerBound(objective_var);
976 if (objective_lb > best_obj_lb) {
977 best_obj_lb = objective_lb;
978 shared_response_manager->UpdateInnerObjectiveBounds(
979 model->Name(), objective_lb,
980 integer_trail->LevelZeroUpperBound(objective_var));
981 // If we are not in interleave_search we synchronize right away.
982 if (!model->Get<SatParameters>()->interleave_search()) {
983 shared_response_manager->Synchronize();
984 }
985 }
986 };
988 ->RegisterLevelZeroModifiedVariablesCallback(
989 broadcast_objective_lower_bound);
990}
991
992// Registers a callback to import new objective bounds. It will be called each
993// time the search main loop is back to level zero. Note that it the presence of
994// assumptions, this will not happen until the set of assumptions is changed.
996 SharedResponseManager* shared_response_manager, Model* model) {
997 auto* solver = model->GetOrCreate<SatSolver>();
998 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
999 auto* objective = model->GetOrCreate<ObjectiveDefinition>();
1000 const std::string name = model->Name();
1001 DebugSolution* debug_sol = model->GetOrCreate<DebugSolution>();
1002 const auto import_objective_bounds = [name = name, solver, integer_trail,
1003 objective, shared_response_manager,
1004 debug_sol]() {
1005 if (solver->AssumptionLevel() != 0) return true;
1006 bool tighter_bounds = false;
1007
1008 const IntegerValue external_lb =
1009 shared_response_manager->GetInnerObjectiveLowerBound();
1010 const IntegerValue current_lb =
1011 integer_trail->LowerBound(objective->objective_var);
1012 if (external_lb > current_lb) {
1013 if (!integer_trail->Enqueue(IntegerLiteral::GreaterOrEqual(
1014 objective->objective_var, external_lb),
1015 {}, {})) {
1016 return false;
1017 }
1018 tighter_bounds = true;
1019 }
1020
1021 const IntegerValue external_ub =
1022 shared_response_manager->GetInnerObjectiveUpperBound();
1023 const IntegerValue current_ub =
1024 integer_trail->UpperBound(objective->objective_var);
1025 if (external_ub < current_ub) {
1026 if (DEBUG_MODE) {
1027 // If the current solution is as good or better than the debug one,
1028 // the debug solution is not a solution anymore for the improving
1029 // problem.
1030 if (debug_sol && external_ub <= debug_sol->inner_objective_value) {
1031 debug_sol->Clear();
1032 }
1033 }
1034 if (!integer_trail->Enqueue(IntegerLiteral::LowerOrEqual(
1035 objective->objective_var, external_ub),
1036 {}, {})) {
1037 return false;
1038 }
1039 tighter_bounds = true;
1040 }
1041
1042 // Note that we will propagate if they are new bounds separately.
1043 // See BeforeTakingDecision().
1044 if (tighter_bounds) {
1045 VLOG(3) << "'" << name << "' imports objective bounds: external ["
1046 << objective->ScaleIntegerObjective(external_lb) << ", "
1047 << objective->ScaleIntegerObjective(external_ub) << "], current ["
1048 << objective->ScaleIntegerObjective(current_lb) << ", "
1049 << objective->ScaleIntegerObjective(current_ub) << "]";
1050 }
1051
1052 return true;
1053 };
1054
1055 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
1056 import_objective_bounds);
1057}
1058
1059// Registers a callback that will export good clauses discovered during search.
1060void RegisterClausesExport(int id, SharedClausesManager* shared_clauses_manager,
1061 Model* model) {
1062 auto* mapping = model->GetOrCreate<CpModelMapping>();
1063 const auto& share_binary_clause = [mapping, id, shared_clauses_manager](
1064 Literal l1, Literal l2) {
1065 const int var1 =
1066 mapping->GetProtoVariableFromBooleanVariable(l1.Variable());
1067 if (var1 == -1) return;
1068 const int var2 =
1069 mapping->GetProtoVariableFromBooleanVariable(l2.Variable());
1070 if (var2 == -1) return;
1071 const int lit1 = l1.IsPositive() ? var1 : NegatedRef(var1);
1072 const int lit2 = l2.IsPositive() ? var2 : NegatedRef(var2);
1073 shared_clauses_manager->AddBinaryClause(id, lit1, lit2);
1074 };
1075 model->GetOrCreate<BinaryImplicationGraph>()->SetAdditionCallback(
1076 share_binary_clause);
1077 if (!model->GetOrCreate<SatParameters>()->share_glue_clauses()) {
1078 return;
1079 }
1080 const double share_interval =
1081 model->GetOrCreate<SatParameters>()->share_glue_clauses_dtime();
1082 auto* clause_stream = model->GetOrCreate<UniqueClauseStream>();
1083 auto* time_limit = model->GetOrCreate<TimeLimit>();
1084 auto* lrat_proof_handler = model->Mutable<LratProofHandler>();
1085 auto share_clause = [mapping, clause_stream, time_limit, lrat_proof_handler,
1086 id, shared_clauses_manager, share_interval,
1087 next_batch_dtime = -1.0, clause = std::vector<int>()](
1088 int lbd, ClauseId clause_id,
1089 absl::Span<const Literal> literals) mutable {
1090 if (literals.size() >= UniqueClauseStream::kMinClauseSize &&
1091 literals.size() <= UniqueClauseStream::kMaxClauseSize) {
1092 clause.clear();
1093 for (const Literal& lit : literals) {
1094 const int var =
1095 mapping->GetProtoVariableFromBooleanVariable(lit.Variable());
1096 if (var == -1) return;
1097 clause.push_back(lit.IsPositive() ? var : NegatedRef(var));
1098 }
1099 if (clause_stream->Add(clause, lbd) && lrat_proof_handler != nullptr) {
1100 lrat_proof_handler->ExportClause(clause_id, literals);
1101 }
1102 }
1103 const double elapsed_dtime = time_limit->GetElapsedDeterministicTime();
1104 if (next_batch_dtime < 0) next_batch_dtime = elapsed_dtime + share_interval;
1105 if (elapsed_dtime >= next_batch_dtime) {
1106 shared_clauses_manager->AddBatch(id, clause_stream->NextBatch());
1107 next_batch_dtime = elapsed_dtime + share_interval;
1108 }
1109 };
1110 model->GetOrCreate<ClauseManager>()->SetAddClauseCallback(
1111 std::move(share_clause));
1112}
1113
1114// Registers a callback to import new clauses stored in the
1115// shared_clausess_manager. These clauses are imported at level 0 of the search
1116// in the linear scan minimize function.
1117// it returns the id of the worker in the shared clause manager.
1118//
1119// TODO(user): Can we import them in the core worker ?
1121 SharedClausesManager* shared_clauses_manager,
1122 Model* model) {
1123 CHECK(shared_clauses_manager != nullptr);
1124 CpModelMapping* const mapping = model->GetOrCreate<CpModelMapping>();
1125 auto* sat_solver = model->GetOrCreate<SatSolver>();
1126 auto* vivifier = model->GetOrCreate<Vivifier>();
1127 auto* implications = model->GetOrCreate<BinaryImplicationGraph>();
1128 const bool share_glue_clauses =
1129 model->GetOrCreate<SatParameters>()->share_glue_clauses();
1130 auto* clause_stream =
1131 share_glue_clauses ? model->GetOrCreate<UniqueClauseStream>() : nullptr;
1132 const bool minimize_shared_clauses =
1133 model->GetOrCreate<SatParameters>()->minimize_shared_clauses();
1134 auto* clause_manager = model->GetOrCreate<ClauseManager>();
1135 const auto& import_level_zero_clauses = [shared_clauses_manager, id, mapping,
1136 sat_solver, vivifier, implications,
1137 minimize_shared_clauses,
1138 clause_stream,
1139 clause_manager]() mutable {
1140 std::vector<std::pair<int, int>> new_binary_clauses;
1141 shared_clauses_manager->GetUnseenBinaryClauses(id, &new_binary_clauses);
1142 implications->EnableSharing(false);
1143 for (const auto& [ref1, ref2] : new_binary_clauses) {
1144 const Literal l1 = mapping->Literal(ref1);
1145 const Literal l2 = mapping->Literal(ref2);
1146 if (!sat_solver->AddProblemClause({l1, l2}, /*shared=*/true)) {
1147 return false;
1148 }
1149 }
1150 implications->EnableSharing(true);
1151 if (clause_stream == nullptr) return true;
1152
1153 int new_clauses = 0;
1154 std::array<Literal, UniqueClauseStream::kMaxClauseSize> local_clause;
1155 sat_solver->EnsureNewClauseIndexInitialized();
1156 // Temporarily disable clause sharing.
1157 auto callback = clause_manager->TakeAddClauseCallback();
1158 while (true) {
1159 auto batch = shared_clauses_manager->GetUnseenClauses(id);
1160 if (batch.empty()) break;
1161 for (int clause_index = 0; clause_index < batch.size(); ++clause_index) {
1162 const absl::Span<const int>& shared_clause = batch[clause_index];
1163 // Check this clause was not already learned by this worker.
1164 if (!clause_stream->BlockClause(shared_clause)) continue;
1165 ++new_clauses;
1166 for (int i = 0; i < shared_clause.size(); ++i) {
1167 local_clause[i] = mapping->Literal(shared_clause[i]);
1168 }
1169 if (!sat_solver->AddProblemClause(
1170 absl::MakeSpan(local_clause).subspan(0, shared_clause.size()),
1171 /*shared=*/true)) {
1172 return false;
1173 }
1174 }
1175 }
1176 clause_manager->SetAddClauseCallback(std::move(callback));
1177 if (new_clauses > 0) {
1178 shared_clauses_manager->NotifyNumImported(id, new_clauses);
1179 }
1180
1181 if (new_clauses > 0 && !sat_solver->FinishPropagation()) return false;
1182 if (minimize_shared_clauses && new_clauses > 0) {
1183 // The new clauses may be subsumed, so try to minimize them to reduce
1184 // overhead of sharing.
1185 // We only share up to 1024 literals worth of new clauses per second, so
1186 // at most 1024 decisions to vivify all new clauses, so this should be
1187 // relatively cheap, *if* regular vivification is keeping up with new
1188 // clauses. Use a tight dtime limit in case it isn't.
1189 return vivifier->MinimizeByPropagation(
1190 /*log_info=*/false, /*dtime_budget=*/0.01,
1191 /*minimize_new_clauses_only=*/true);
1192 }
1193 return true;
1194 };
1195 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
1196 import_level_zero_clauses);
1197 return id;
1198}
1199
1200namespace {
1201
1202// Fills several repositories of bounds of linear2 (RootLevelLinear2Bounds,
1203// ConditionalLinear2Bounds and ReifiedLinear2Bounds) using the linear
1204// constraints of size 2 and the linear constraints of size 3 with domain of
1205// size 1. Also expands linear constraints of size 1 enforced by two literals
1206// into (up to) 4 binary relations enforced by only one literal.
1207void FillConditionalLinear2Bounds(const CpModelProto& model_proto,
1208 Model* model) {
1209 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1210 auto* encoder = model->GetOrCreate<IntegerEncoder>();
1211 auto* mapping = model->GetOrCreate<CpModelMapping>();
1212 auto* repository = model->GetOrCreate<ConditionalLinear2Bounds>();
1213 auto* root_level_lin2_bounds = model->GetOrCreate<RootLevelLinear2Bounds>();
1214 auto* reified_lin2_bounds = model->GetOrCreate<ReifiedLinear2Bounds>();
1215
1216 for (const ConstraintProto& ct : model_proto.constraints()) {
1217 // Load conditional precedences and always true binary relations.
1218 if (ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
1219 continue;
1220 }
1221 if (ct.enforcement_literal().size() == 2 && ct.linear().vars_size() == 1) {
1222 // Add an enforced binary relation ensuring var1 \in var1_domain, as well
1223 // as var1 >= implied_lb if lit2 is true.
1224 auto process = [&](Literal enforcement_literal, IntegerVariable var1,
1225 const Domain& var1_domain, Literal lit2,
1226 int64_t implied_lb) {
1227 const int64_t delta = implied_lb - var1_domain.Min();
1228 if (delta <= 0) return;
1229 const IntegerVariable var2 = encoder->GetLiteralView(lit2);
1230 const IntegerVariable negated_var2 =
1231 encoder->GetLiteralView(lit2.Negated());
1232 if (var2 != kNoIntegerVariable) {
1233 // var1_min <= var1 - delta.var2 <= var1_max, which is equivalent to
1234 // the default bounds if var2 = 0, and gives implied_lb <= var1 <=
1235 // var1_max + delta otherwise.
1236 repository->Add(enforcement_literal,
1237 LinearExpression2(var1, var2, 1, -delta),
1238 var1_domain.Min(), var1_domain.Max());
1239 } else if (negated_var2 != kNoIntegerVariable) {
1240 // var1_min + delta <= var1 + delta.neg_var2 <= var1_max + delta,
1241 // which is equivalent to the default bounds if neg_var2 = 1, and
1242 // gives implied_lb <= var1 <= var1_max + delta otherwise.
1243 repository->Add(enforcement_literal,
1244 LinearExpression2(var1, negated_var2, 1, delta),
1245 var1_domain.Min() + delta, var1_domain.Max() + delta);
1246 }
1247 };
1248 const IntegerVariable var = mapping->Integer(ct.linear().vars(0));
1249 const IntegerVariableProto& var_proto =
1250 model_proto.variables(ct.linear().vars(0));
1251 const Domain var_domain = ReadDomainFromProto(var_proto);
1252 const Domain implied_var_domain =
1253 ReadDomainFromProto(ct.linear())
1254 .InverseMultiplicationBy(ct.linear().coeffs(0));
1255 for (int i = 0; i < 2; ++i) {
1256 const Literal lit1 = mapping->Literal(ct.enforcement_literal(i));
1257 const Literal lit2 = mapping->Literal(ct.enforcement_literal(1 - i));
1258 process(lit1, var, var_domain, lit2, implied_var_domain.Min());
1259 process(lit1, NegationOf(var), var_domain.Negation(), lit2,
1260 -implied_var_domain.Max());
1261 }
1262 continue;
1263 } else if (ct.enforcement_literal().size() > 1 ||
1264 ct.linear().vars_size() > 2) {
1265 continue;
1266 }
1267 const std::vector<IntegerVariable> vars =
1268 mapping->Integers(ct.linear().vars());
1269 absl::Span<const int64_t> coeffs = ct.linear().coeffs();
1270
1271 const auto [min_sum, max_sum] =
1272 mapping->ComputeMinMaxActivity(ct.linear(), integer_trail);
1273 // Tighten the bounds to avoid overflows in the code using the repository.
1274 const int64_t rhs_min = std::max(ct.linear().domain(0), min_sum);
1275 const int64_t rhs_max =
1276 std::min(ct.linear().domain(ct.linear().domain().size() - 1), max_sum);
1277
1278 if (ct.enforcement_literal().empty()) {
1279 if (vars.size() == 2) {
1280 const LinearExpression2 expr(vars[0], vars[1], coeffs[0], coeffs[1]);
1281 root_level_lin2_bounds->Add(expr, rhs_min, rhs_max);
1282 } else if (vars.size() == 3 && rhs_min == rhs_max) {
1283 reified_lin2_bounds->AddLinear3(vars, coeffs, rhs_min);
1284 }
1285 } else {
1286 if (vars.size() == 2) {
1287 const Literal lit = mapping->Literal(ct.enforcement_literal(0));
1288 repository->Add(
1289 lit, LinearExpression2(vars[0], vars[1], coeffs[0], coeffs[1]),
1290 rhs_min, rhs_max);
1291 }
1292 }
1293 }
1294 repository->Build();
1295}
1296
1297} // namespace
1298
1299void LoadBaseModel(const CpModelProto& model_proto, Model* model) {
1300 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1301 CHECK(shared_response_manager != nullptr);
1302 auto* sat_solver = model->GetOrCreate<SatSolver>();
1303
1304 // Simple function for the few places where we do "return unsat()".
1305 const auto unsat = [shared_response_manager, sat_solver, model] {
1306 sat_solver->NotifyThatModelIsUnsat();
1307 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1308 absl::StrCat(model->Name(), " [loading]"));
1309 };
1310
1311 // We will add them all at once after model_proto is loaded.
1312 model->GetOrCreate<IntegerEncoder>()->DisableImplicationBetweenLiteral();
1313
1314 auto* mapping = model->GetOrCreate<CpModelMapping>();
1315 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1316 const bool view_all_booleans_as_integers =
1317 (parameters.linearization_level() >= 2) ||
1318 (parameters.search_branching() == SatParameters::FIXED_SEARCH &&
1319 model_proto.search_strategy().empty()) ||
1320 parameters.optimize_with_max_hs();
1321 LoadVariables(model_proto, view_all_booleans_as_integers, model);
1322 DetectOptionalVariables(model_proto, model);
1323
1324 // TODO(user): The core algo and symmetries seems to be problematic in some
1325 // cases. See for instance: neos-691058.mps.gz. This is probably because as
1326 // we modify the model, our symmetry might be wrong? investigate.
1327 //
1328 // TODO(user): More generally, we cannot load the symmetry if we create
1329 // new Booleans and constraints that link them to some Booleans of the model.
1330 // Creating Booleans related to integer variable is fine since we only deal
1331 // with Boolean only symmetry here. It is why we disable this when we have
1332 // linear relaxation as some of them create new constraints.
1333 if (!parameters.optimize_with_core() && parameters.symmetry_level() > 1 &&
1334 !parameters.enumerate_all_solutions() &&
1335 parameters.linearization_level() == 0) {
1336 LoadBooleanSymmetries(model_proto, model);
1337 }
1338
1339 TimeLimit* time_limit = model->GetOrCreate<TimeLimit>();
1340 if (time_limit->LimitReached()) return;
1341
1342 ExtractEncoding(model_proto, model);
1344
1345 if (time_limit->LimitReached()) return;
1346 // Check the model is still feasible before continuing.
1347 if (sat_solver->ModelIsUnsat()) return unsat();
1348
1349 // Fully encode variables as needed by the search strategy.
1350 AddFullEncodingFromSearchBranching(model_proto, model);
1351 if (sat_solver->ModelIsUnsat()) return unsat();
1352
1353 FillConditionalLinear2Bounds(model_proto, model);
1354
1355 if (time_limit->LimitReached()) return;
1356
1357 // Load the constraints.
1358 int num_ignored_constraints = 0;
1359
1360 TimeLimitCheckEveryNCalls time_limit_check(1000, time_limit);
1361 absl::flat_hash_set<ConstraintProto::ConstraintCase> unsupported_types;
1362 for (const ConstraintProto& ct : model_proto.constraints()) {
1363 if (mapping->ConstraintIsAlreadyLoaded(&ct)) {
1364 ++num_ignored_constraints;
1365 continue;
1366 }
1367
1368 if (!LoadConstraint(ct, model)) {
1369 unsupported_types.insert(ct.constraint_case());
1370 continue;
1371 }
1372
1373 if (time_limit_check.LimitReached()) return;
1374
1375 // We propagate after each new Boolean constraint but not the integer
1376 // ones. So we call FinishPropagation() manually here.
1377 //
1378 // Note that we only do that in debug mode as this can be really slow on
1379 // certain types of problems with millions of constraints.
1380 if (DEBUG_MODE) {
1381 if (sat_solver->FinishPropagation()) {
1382 Trail* trail = model->GetOrCreate<Trail>();
1383 const int old_num_fixed = trail->Index();
1384 if (trail->Index() > old_num_fixed) {
1385 VLOG(3) << "Constraint fixed " << trail->Index() - old_num_fixed
1386 << " Boolean variable(s): " << ProtobufDebugString(ct);
1387 }
1388 }
1389 }
1390 if (sat_solver->ModelIsUnsat()) {
1391 VLOG(2) << "UNSAT during extraction (after adding '"
1392 << ConstraintCaseName(ct.constraint_case()) << "'). "
1393 << ProtobufDebugString(ct);
1394 return unsat();
1395 }
1396 }
1397 if (num_ignored_constraints > 0) {
1398 VLOG(3) << num_ignored_constraints << " constraints were skipped.";
1399 }
1400 if (!unsupported_types.empty()) {
1401 auto* logger = model->GetOrCreate<SolverLogger>();
1402 SOLVER_LOG(logger,
1403 "There is unsupported constraints types in this model: ");
1404 std::vector<absl::string_view> names;
1405 for (const ConstraintProto::ConstraintCase type : unsupported_types) {
1406 names.push_back(ConstraintCaseName(type));
1407 }
1408 std::sort(names.begin(), names.end());
1409 for (const absl::string_view name : names) {
1410 SOLVER_LOG(logger, " - ", name);
1411 }
1412
1413 // TODO(user): This is wrong. We should support a MODEL_INVALID end of solve
1414 // in the SharedResponseManager.
1415 SOLVER_LOG(logger, "BUG: We will wrongly report INFEASIBLE now.");
1416 return unsat();
1417 }
1418 if (model->Mutable<LratProofHandler>() != nullptr) {
1419 model->Mutable<LratProofHandler>()->EndProblemClauses();
1420 }
1421
1422 model->GetOrCreate<IntegerEncoder>()
1423 ->AddAllImplicationsBetweenAssociatedLiterals();
1424 if (!sat_solver->FinishPropagation()) return unsat();
1425
1426 model->GetOrCreate<ProductDetector>()->ProcessImplicationGraph(
1429}
1430
1431void LoadFeasibilityPump(const CpModelProto& model_proto, Model* model) {
1432 LoadBaseModel(model_proto, model);
1433
1434 if (model->GetOrCreate<TimeLimit>()->LimitReached()) return;
1435
1436 auto* mapping = model->GetOrCreate<CpModelMapping>();
1437 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1438 if (parameters.linearization_level() == 0) return;
1439
1440 // Add linear constraints to Feasibility Pump.
1441 const LinearRelaxation relaxation =
1442 ComputeLinearRelaxation(model_proto, model);
1443 if (model->GetOrCreate<SatSolver>()->ModelIsUnsat()) return;
1444
1445 const int num_lp_constraints =
1446 static_cast<int>(relaxation.linear_constraints.size());
1447 if (num_lp_constraints == 0) return;
1448 auto* feasibility_pump = model->GetOrCreate<FeasibilityPump>();
1449 for (int i = 0; i < num_lp_constraints; i++) {
1450 feasibility_pump->AddLinearConstraint(relaxation.linear_constraints[i]);
1451 }
1452
1453 if (model_proto.has_objective()) {
1454 for (int i = 0; i < model_proto.objective().coeffs_size(); ++i) {
1455 const IntegerVariable var =
1456 mapping->Integer(model_proto.objective().vars(i));
1457 const int64_t coeff = model_proto.objective().coeffs(i);
1458 feasibility_pump->SetObjectiveCoefficient(var, IntegerValue(coeff));
1459 }
1460 }
1461}
1462
1463// Loads a CpModelProto inside the given model.
1464// This should only be called once on a given 'Model' class.
1465void LoadCpModel(const CpModelProto& model_proto, Model* model) {
1466 LoadBaseModel(model_proto, model);
1467
1468 if (model->GetOrCreate<TimeLimit>()->LimitReached()) return;
1469
1470 // We want to load the debug solution before the initial propag.
1471 // But at this point the objective is not loaded yet, so we will not have
1472 // a value for the objective integer variable, so we do it again later.
1473 InitializeDebugSolution(model_proto, model);
1474
1475 // Simple function for the few places where we do "return unsat()".
1476 auto* sat_solver = model->GetOrCreate<SatSolver>();
1477 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1478 const auto unsat = [shared_response_manager, sat_solver, model] {
1479 sat_solver->NotifyThatModelIsUnsat();
1480 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1481 absl::StrCat(model->Name(), " [loading]"));
1482 };
1483
1484 // Auto detect "at least one of" constraints in the PrecedencesPropagator.
1485 // Note that we do that before we finish loading the problem (objective and
1486 // LP relaxation), because propagation will be faster at this point and it
1487 // should be enough for the purpose of this auto-detection.
1488 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1491 ->AddGreaterThanAtLeastOneOfConstraints(model);
1492 if (!sat_solver->FinishPropagation()) return unsat();
1493 }
1494
1495 // Note that this is already done in the presolve, but it is important to redo
1496 // it here to collect literal => integer >= bound constraints that are used in
1497 // many places. Without it, we don't detect them if they depends on long chain
1498 // of implications.
1499 //
1500 // TODO(user): We don't have a good deterministic time on all constraints,
1501 // so this might take more time than wanted.
1502 if (parameters.cp_model_probing_level() > 1) {
1503 Prober* prober = model->GetOrCreate<Prober>();
1504
1505 // TODO(user): This always add new binary clauses ! there can be a lot
1506 // of them. We get away because of the time limit, but it might not be
1507 // good to just have more binary for the first few variables we where able
1508 // to probe on large problems !
1509 if (!prober->ProbeBooleanVariables(/*deterministic_time_limit=*/1.0)) {
1510 return unsat();
1511 }
1512 if (!model->GetOrCreate<BinaryImplicationGraph>()
1514 return unsat();
1515 }
1516 }
1517 if (sat_solver->ModelIsUnsat()) return unsat();
1518
1519 // Note that it is important to do that after the probing.
1520 ExtractElementEncoding(model_proto, model);
1521
1522 // Compute decomposed energies on demands helper.
1523 IntervalsRepository* repository = model->Mutable<IntervalsRepository>();
1524 if (repository != nullptr) {
1525 repository->InitAllDecomposedEnergies();
1526 }
1527
1528 // We need to know beforehand if the objective var can just be >= terms or
1529 // needs to be == terms.
1530 bool objective_need_to_be_tight = false;
1531 auto* mapping = model->GetOrCreate<CpModelMapping>();
1532 if (model_proto.has_objective() &&
1533 !model_proto.objective().domain().empty()) {
1534 int64_t min_value = 0;
1535 int64_t max_value = 0;
1536 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1537 const CpObjectiveProto& obj = model_proto.objective();
1538 for (int i = 0; i < obj.vars_size(); ++i) {
1539 const int64_t coeff = obj.coeffs(i);
1540 const IntegerVariable var = mapping->Integer(obj.vars(i));
1541 if (coeff > 0) {
1542 min_value += coeff * integer_trail->LowerBound(var).value();
1543 max_value += coeff * integer_trail->UpperBound(var).value();
1544 } else {
1545 min_value += coeff * integer_trail->UpperBound(var).value();
1546 max_value += coeff * integer_trail->LowerBound(var).value();
1547 }
1548 }
1549 const Domain user_domain = ReadDomainFromProto(model_proto.objective());
1550 const Domain automatic_domain = Domain(min_value, max_value);
1551 objective_need_to_be_tight = !automatic_domain.IsIncludedIn(user_domain);
1552 }
1553
1554 // Create an objective variable and its associated linear constraint if
1555 // needed.
1556 IntegerVariable objective_var = kNoIntegerVariable;
1557 if (parameters.linearization_level() > 0) {
1558 // Linearize some part of the problem and register LP constraint(s).
1559 objective_var =
1560 AddLPConstraints(objective_need_to_be_tight, model_proto, model);
1561 if (sat_solver->ModelIsUnsat()) return unsat();
1562 } else if (model_proto.has_objective()) {
1563 const CpObjectiveProto& obj = model_proto.objective();
1564 std::vector<std::pair<IntegerVariable, int64_t>> terms;
1565 terms.reserve(obj.vars_size());
1566 for (int i = 0; i < obj.vars_size(); ++i) {
1567 terms.push_back(
1568 std::make_pair(mapping->Integer(obj.vars(i)), obj.coeffs(i)));
1569 }
1570 if (parameters.optimize_with_core()) {
1571 if (objective_need_to_be_tight) {
1572 // We do not care about the <= obj for core, we only need the other side
1573 // to enforce a restriction of the objective lower bound.
1574 //
1575 // TODO(user): This might still create intermediate variables to
1576 // decompose the objective for no reason. Just deal directly with the
1577 // objective domain in the core algo by forbidding bad assumptions?
1578 // Alternatively, just ignore the core solution if it is "too" good and
1579 // rely on other solvers?
1580 objective_var =
1581 GetOrCreateVariableLinkedToSumOf(terms, true, false, model);
1582 } else {
1583 objective_var = GetOrCreateVariableWithTightBound(terms, model);
1584 }
1585 } else {
1586 objective_var = GetOrCreateVariableLinkedToSumOf(
1587 terms, objective_need_to_be_tight, true, model);
1588 }
1589 }
1590
1591 // Create the objective definition inside the Model so that it can be accessed
1592 // by the heuristics than needs it.
1593 if (objective_var != kNoIntegerVariable) {
1594 const CpObjectiveProto& objective_proto = model_proto.objective();
1595 auto* objective_definition = model->GetOrCreate<ObjectiveDefinition>();
1596
1597 objective_definition->scaling_factor = objective_proto.scaling_factor();
1598 if (objective_definition->scaling_factor == 0.0) {
1599 objective_definition->scaling_factor = 1.0;
1600 }
1601 objective_definition->offset = objective_proto.offset();
1602 objective_definition->objective_var = objective_var;
1603
1604 const int size = objective_proto.vars_size();
1605 objective_definition->vars.resize(size);
1606 objective_definition->coeffs.resize(size);
1607 for (int i = 0; i < objective_proto.vars_size(); ++i) {
1608 // Note that if there is no mapping, then the variable will be
1609 // kNoIntegerVariable.
1610 objective_definition->vars[i] = mapping->Integer(objective_proto.vars(i));
1611 objective_definition->coeffs[i] = IntegerValue(objective_proto.coeffs(i));
1612
1613 // Fill the objective heuristics data.
1614 const int ref = objective_proto.vars(i);
1615 if (mapping->IsInteger(ref)) {
1616 const IntegerVariable var = mapping->Integer(objective_proto.vars(i));
1617 objective_definition->objective_impacting_variables.insert(
1618 objective_proto.coeffs(i) > 0 ? var : NegationOf(var));
1619 }
1620 }
1621
1622 // Register an objective special propagator.
1623 model->TakeOwnership(
1624 new LevelZeroEquality(objective_var, objective_definition->vars,
1625 objective_definition->coeffs, model));
1626 }
1627
1628 // Intersect the objective domain with the given one if any.
1629 if (!model_proto.objective().domain().empty()) {
1630 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1631 const Domain user_domain = ReadDomainFromProto(model_proto.objective());
1632 const Domain automatic_domain =
1633 integer_trail->InitialVariableDomain(objective_var);
1634 VLOG(3) << "Objective offset:" << model_proto.objective().offset()
1635 << " scaling_factor:" << model_proto.objective().scaling_factor();
1636 VLOG(3) << "Automatic internal objective domain: " << automatic_domain;
1637 VLOG(3) << "User specified internal objective domain: " << user_domain;
1638 CHECK_NE(objective_var, kNoIntegerVariable);
1639 if (!integer_trail->UpdateInitialDomain(objective_var, user_domain)) {
1640 VLOG(2) << "UNSAT due to the objective domain.";
1641 return unsat();
1642 }
1643 }
1644
1645 // Note that we do one last propagation at level zero once all the
1646 // constraints were added.
1648 "Initial num_bool: ", sat_solver->NumVariables());
1649 if (!sat_solver->FinishPropagation()) return unsat();
1650
1651 if (model_proto.has_objective()) {
1652 // Report the initial objective variable bounds.
1653 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1654 shared_response_manager->UpdateInnerObjectiveBounds(
1655 absl::StrCat(model->Name(), " (initial_propagation)"),
1656 integer_trail->LowerBound(objective_var),
1657 integer_trail->UpperBound(objective_var));
1658
1659 // Watch improved objective best bounds.
1660 RegisterObjectiveBestBoundExport(objective_var, shared_response_manager,
1661 model);
1662
1663 // Import objective bounds.
1664 // TODO(user): Support objective bounds import in LNS and Core based
1665 // search.
1667 RegisterObjectiveBoundsImport(shared_response_manager, model);
1668 }
1669 }
1670
1671 // Initialize the search strategies.
1672 auto* search_heuristics = model->GetOrCreate<SearchHeuristics>();
1673 search_heuristics->user_search =
1674 ConstructUserSearchStrategy(model_proto, model);
1675 search_heuristics->heuristic_search =
1676 ConstructHeuristicSearchStrategy(model_proto, model);
1677 search_heuristics->integer_completion_search =
1678 ConstructIntegerCompletionSearchStrategy(mapping->GetVariableMapping(),
1679 objective_var, model);
1680 ConstructFixedSearchStrategy(search_heuristics, model);
1681 if (VLOG_IS_ON(3)) {
1682 search_heuristics->fixed_search =
1683 InstrumentSearchStrategy(model_proto, mapping->GetVariableMapping(),
1684 search_heuristics->fixed_search, model);
1685 }
1686 search_heuristics->hint_search =
1687 ConstructHintSearchStrategy(model_proto, mapping, model);
1688
1689 // Create the CoreBasedOptimizer class if needed.
1690 if (parameters.optimize_with_core()) {
1691 // TODO(user): Remove code duplication with the solution_observer in
1692 // SolveLoadedCpModel().
1693 const auto solution_observer = [&model_proto, model,
1694 shared_response_manager,
1695 best_obj_ub = kMaxIntegerValue]() mutable {
1696 const std::vector<int64_t> solution =
1697 GetSolutionValues(model_proto, *model);
1698 const IntegerValue obj_ub =
1700 if (obj_ub < best_obj_ub) {
1701 best_obj_ub = obj_ub;
1702 shared_response_manager->NewSolution(solution, model->Name(), model);
1703 }
1704 };
1705
1706 const auto& objective = *model->GetOrCreate<ObjectiveDefinition>();
1707 if (parameters.optimize_with_max_hs()) {
1709 model_proto, objective, solution_observer, model);
1710 model->Register<HittingSetOptimizer>(max_hs);
1711 model->TakeOwnership(max_hs);
1712 } else {
1713 CoreBasedOptimizer* core =
1714 new CoreBasedOptimizer(objective_var, objective.vars,
1715 objective.coeffs, solution_observer, model);
1716 model->Register<CoreBasedOptimizer>(core);
1717 model->TakeOwnership(core);
1718 }
1719 }
1720
1721 InitializeDebugSolution(model_proto, model);
1722}
1723
1724// Solves an already loaded cp_model_proto.
1725// The final CpSolverResponse must be read from the shared_response_manager.
1726//
1727// TODO(user): This should be transformed so that it can be called many times
1728// and resume from the last search state as if it wasn't interrupted. That would
1729// allow use to easily interleave different heuristics in the same thread.
1730void SolveLoadedCpModel(const CpModelProto& model_proto, Model* model) {
1731 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1732 if (shared_response_manager->ProblemIsSolved()) return;
1733
1734 if (model->GetOrCreate<TimeLimit>()->LimitReached()) return;
1735 const SatParameters& parameters = *model->GetOrCreate<SatParameters>();
1736 if (parameters.stop_after_root_propagation()) return;
1737
1738 // This will activate an integer based conflict resolution.
1739 //
1740 // TODO(user): right now this is not used for probing since we register
1741 // it afterwards... find a better way. Note that we need to handle creation
1742 // of variable in the conflict resolution.
1743 if (parameters.use_new_integer_conflict_resolution()) {
1745 }
1746
1747 auto solution_observer = [&model_proto, model, shared_response_manager,
1748 best_obj_ub = kMaxIntegerValue]() mutable {
1749 const std::vector<int64_t> solution =
1750 GetSolutionValues(model_proto, *model);
1751 if (model_proto.has_objective()) {
1752 const IntegerValue obj_ub =
1754 if (obj_ub < best_obj_ub) {
1755 best_obj_ub = obj_ub;
1756 shared_response_manager->NewSolution(solution, model->Name(), model);
1757 }
1758 } else {
1759 shared_response_manager->NewSolution(solution, model->Name(), model);
1760 }
1761 };
1762
1763 // Make sure we are not at a positive level.
1764 if (!model->GetOrCreate<SatSolver>()->ResetToLevelZero()) {
1765 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1766 model->Name());
1767 return;
1768 }
1769
1770 // Reconfigure search heuristic if it was changed.
1772
1773 const auto& mapping = *model->GetOrCreate<CpModelMapping>();
1774 SatSolver::Status status;
1775
1776 if (parameters.use_probing_search()) {
1777 ContinuousProber prober(model_proto, model);
1778 while (true) {
1779 status = prober.Probe();
1780 if (status == SatSolver::INFEASIBLE) {
1781 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1782 model->Name());
1783 break;
1784 }
1785 if (status == SatSolver::FEASIBLE) {
1786 solution_observer();
1787 } else {
1788 break;
1789 }
1790 }
1791 } else if (!model_proto.has_objective()) {
1792 while (true) {
1793 if (parameters.use_shared_tree_search()) {
1794 auto* subtree_worker = model->GetOrCreate<SharedTreeWorker>();
1795 status = subtree_worker->Search(solution_observer);
1796 } else {
1798 mapping.Literals(model_proto.assumptions()), model);
1799 }
1800 if (status != SatSolver::Status::FEASIBLE) break;
1801 solution_observer();
1802 if (!parameters.enumerate_all_solutions()) break;
1804 }
1805 if (status == SatSolver::INFEASIBLE) {
1806 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1807 model->Name());
1808 }
1809 if (status == SatSolver::ASSUMPTIONS_UNSAT) {
1810 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1811 model->Name());
1812
1813 // Extract a good subset of assumptions and add it to the response.
1814 auto* time_limit = model->GetOrCreate<TimeLimit>();
1815 auto* sat_solver = model->GetOrCreate<SatSolver>();
1816 std::vector<Literal> core = sat_solver->GetLastIncompatibleDecisions();
1817 MinimizeCoreWithPropagation(time_limit, sat_solver, &core);
1818 std::vector<int> core_in_proto_format;
1819 for (const Literal l : core) {
1820 core_in_proto_format.push_back(
1821 mapping.GetProtoVariableFromBooleanVariable(l.Variable()));
1822 if (!l.IsPositive()) {
1823 core_in_proto_format.back() = NegatedRef(core_in_proto_format.back());
1824 }
1825 }
1826 shared_response_manager->AddUnsatCore(core_in_proto_format);
1827 }
1828 } else {
1829 // Optimization problem.
1830 const auto& objective = *model->GetOrCreate<ObjectiveDefinition>();
1831 const IntegerVariable objective_var = objective.objective_var;
1832 CHECK_NE(objective_var, kNoIntegerVariable);
1833
1834 if (parameters.optimize_with_lb_tree_search()) {
1835 auto* search = model->GetOrCreate<LbTreeSearch>();
1836 status = search->Search(solution_observer);
1837 } else if (parameters.optimize_with_core()) {
1838 // TODO(user): This doesn't work with splitting in chunk for now. It
1839 // shouldn't be too hard to fix.
1840 if (parameters.optimize_with_max_hs()) {
1841 status = model->Mutable<HittingSetOptimizer>()->Optimize();
1842 } else {
1843 status = model->Mutable<CoreBasedOptimizer>()->Optimize();
1844 }
1845 } else if (parameters.use_shared_tree_search()) {
1846 auto* subtree_worker = model->GetOrCreate<SharedTreeWorker>();
1847 status = subtree_worker->Search(solution_observer);
1848 } else {
1849 // TODO(user): This parameter breaks the splitting in chunk of a Solve().
1850 // It should probably be moved into another SubSolver altogether.
1851 if (parameters.binary_search_num_conflicts() >= 0) {
1853 solution_observer, model);
1854 }
1856 objective_var, solution_observer, model);
1857 }
1858
1859 // The search is done in both case.
1860 //
1861 // TODO(user): Remove the weird translation INFEASIBLE->FEASIBLE in the
1862 // function above?
1863 if (status == SatSolver::INFEASIBLE || status == SatSolver::FEASIBLE) {
1864 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1865 model->Name());
1866 }
1867 }
1868}
1869
1870// Try to find a solution by following the hint and using a low conflict limit.
1871// The CpModelProto must already be loaded in the Model.
1872void QuickSolveWithHint(const CpModelProto& model_proto, Model* model) {
1873 if (!model_proto.has_solution_hint()) return;
1874
1875 if (model->GetOrCreate<TimeLimit>()->LimitReached()) return;
1876
1877 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1878 if (shared_response_manager->ProblemIsSolved()) return;
1879
1880 // Temporarily change the parameters.
1881 auto* parameters = model->GetOrCreate<SatParameters>();
1882
1883 // If the model was loaded with "optimize_with_core" then the objective
1884 // variable is not linked to its linear expression. Because of that, we can
1885 // return a solution that does not satisfy the objective domain.
1886 //
1887 // TODO(user): This is fixable, but then do we need the hint when optimizing
1888 // with core?
1889 if (parameters->optimize_with_core()) return;
1890
1891 const SatParameters saved_params = *parameters;
1892 parameters->set_max_number_of_conflicts(parameters->hint_conflict_limit());
1893 parameters->set_search_branching(SatParameters::HINT_SEARCH);
1894 parameters->set_optimize_with_core(false);
1895 parameters->set_use_sat_inprocessing(false);
1896 auto cleanup =
1897 ::absl::MakeCleanup([parameters, saved_params = saved_params]() {
1898 *parameters = saved_params;
1899 });
1900
1901 // Solve decision problem.
1903 const auto& mapping = *model->GetOrCreate<CpModelMapping>();
1905 mapping.Literals(model_proto.assumptions()), model);
1906
1907 const std::string& solution_info = model->Name();
1908 if (status == SatSolver::Status::FEASIBLE) {
1909 const std::vector<int64_t> solution =
1910 GetSolutionValues(model_proto, *model);
1911 shared_response_manager->NewSolution(
1912 solution, absl::StrCat(solution_info, " [hint]"), model);
1913
1914 if (!model_proto.has_objective()) {
1915 if (parameters->enumerate_all_solutions()) {
1917 }
1918 } else {
1919 // Restrict the objective.
1920 const IntegerVariable objective_var =
1921 model->GetOrCreate<ObjectiveDefinition>()->objective_var;
1922 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1923 if (DEBUG_MODE) {
1924 // If we try to improve the hint but the hint is already as good as the
1925 // debug solution, we are trying to solve a problem for which the debug
1926 // solution is not a solution anymore.
1927 const DebugSolution* debug_sol = model->Get<DebugSolution>();
1928 if (debug_sol &&
1929 shared_response_manager->GetInnerObjectiveUpperBound() <=
1930 debug_sol->inner_objective_value) {
1931 model->GetOrCreate<DebugSolution>()->Clear();
1932 }
1933 }
1934 model->GetOrCreate<SatSolver>()->Backtrack(0);
1935 if (!integer_trail->Enqueue(
1937 objective_var,
1938 shared_response_manager->GetInnerObjectiveUpperBound()),
1939 {}, {})) {
1940 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1941 absl::StrCat(solution_info, " [hint]"));
1942 }
1943 }
1944 return;
1945 }
1946
1947 // This code is here to debug bad presolve during LNS that corrupt the hint.
1948 // Note that sometime the deterministic limit is hit before the hint can be
1949 // completed, so we don't report that has an error.
1950 //
1951 // Tricky: We can only test that if we don't already have a feasible solution
1952 // like we do if the hint is complete.
1953 if (parameters->debug_crash_on_bad_hint() &&
1954 shared_response_manager->HasFeasibleSolution() &&
1955 !model->GetOrCreate<TimeLimit>()->LimitReached() &&
1956 status != SatSolver::Status::FEASIBLE) {
1957 LOG(FATAL) << "QuickSolveWithHint() didn't find a feasible solution."
1958 << " The model name is '" << model_proto.name() << "'."
1959 << " Status: " << status << ".";
1960 }
1961
1962 if (status == SatSolver::INFEASIBLE) {
1963 shared_response_manager->NotifyThatImprovingProblemIsInfeasible(
1964 absl::StrCat(solution_info, " [hint]"));
1965 return;
1966 }
1967}
1968
1969// Solve a model with a different objective consisting of minimizing the L1
1970// distance with the provided hint. Note that this method creates an in-memory
1971// copy of the model and loads a local Model object from the copied model.
1972void MinimizeL1DistanceWithHint(const CpModelProto& model_proto, Model* model) {
1973 Model local_model;
1974
1975 // Pass the time limit and stop boolean to local limit.
1976 model->GetOrCreate<ModelSharedTimeLimit>()->UpdateLocalLimit(
1977 local_model.GetOrCreate<TimeLimit>());
1978
1979 if (!model_proto.has_solution_hint()) return;
1980
1981 auto* shared_response_manager = model->GetOrCreate<SharedResponseManager>();
1982 if (shared_response_manager->ProblemIsSolved()) return;
1983
1984 auto* parameters = local_model.GetOrCreate<SatParameters>();
1985 // TODO(user): As of now the repair hint doesn't support when
1986 // enumerate_all_solutions is set since the solution is created on a different
1987 // model.
1988 if (parameters->enumerate_all_solutions()) return;
1989
1990 // Change the parameters.
1991 const SatParameters saved_params = *model->GetOrCreate<SatParameters>();
1992 *parameters = saved_params;
1993 parameters->set_max_number_of_conflicts(parameters->hint_conflict_limit());
1994 parameters->set_optimize_with_core(false);
1995
1996 // Update the model to introduce penalties to go away from hinted values.
1997 CpModelProto updated_model_proto = model_proto;
1998 updated_model_proto.clear_objective();
1999
2000 // TODO(user): For boolean variables we can avoid creating new variables.
2001 for (int i = 0; i < model_proto.solution_hint().vars_size(); ++i) {
2002 const int var = model_proto.solution_hint().vars(i);
2003 const int64_t value = model_proto.solution_hint().values(i);
2004
2005 // Add a new var to represent the difference between var and value.
2006 const int new_var_index = updated_model_proto.variables_size();
2007 IntegerVariableProto* var_proto = updated_model_proto.add_variables();
2008 const int64_t min_domain = model_proto.variables(var).domain(0) - value;
2009 const int64_t max_domain =
2010 model_proto.variables(var).domain(
2011 model_proto.variables(var).domain_size() - 1) -
2012 value;
2013 var_proto->add_domain(min_domain);
2014 var_proto->add_domain(max_domain);
2015
2016 // new_var = var - value.
2017 ConstraintProto* const linear_constraint_proto =
2018 updated_model_proto.add_constraints();
2019 LinearConstraintProto* linear = linear_constraint_proto->mutable_linear();
2020 linear->add_vars(new_var_index);
2021 linear->add_coeffs(1);
2022 linear->add_vars(var);
2023 linear->add_coeffs(-1);
2024 linear->add_domain(-value);
2025 linear->add_domain(-value);
2026
2027 // abs_var = abs(new_var).
2028 const int abs_var_index = updated_model_proto.variables_size();
2029 IntegerVariableProto* abs_var_proto = updated_model_proto.add_variables();
2030 const int64_t abs_min_domain = 0;
2031 const int64_t abs_max_domain =
2032 std::max(std::abs(min_domain), std::abs(max_domain));
2033 abs_var_proto->add_domain(abs_min_domain);
2034 abs_var_proto->add_domain(abs_max_domain);
2035 auto* abs_ct = updated_model_proto.add_constraints()->mutable_lin_max();
2036 abs_ct->mutable_target()->add_vars(abs_var_index);
2037 abs_ct->mutable_target()->add_coeffs(1);
2038 LinearExpressionProto* left = abs_ct->add_exprs();
2039 left->add_vars(new_var_index);
2040 left->add_coeffs(1);
2041 LinearExpressionProto* right = abs_ct->add_exprs();
2042 right->add_vars(new_var_index);
2043 right->add_coeffs(-1);
2044
2045 updated_model_proto.mutable_objective()->add_vars(abs_var_index);
2046 updated_model_proto.mutable_objective()->add_coeffs(1);
2047 }
2048
2049 auto* local_response_manager =
2050 local_model.GetOrCreate<SharedResponseManager>();
2051 local_response_manager->InitializeObjective(updated_model_proto);
2052
2053 // Solve optimization problem.
2054 LoadCpModel(updated_model_proto, &local_model);
2055
2056 if (local_model.GetOrCreate<SatSolver>()->ModelIsUnsat()) {
2057 // TODO(user): This should mean the full model is also unsat.
2058 // Exploit that ?
2059 return;
2060 }
2061
2062 ConfigureSearchHeuristics(&local_model);
2063 const auto& mapping = *local_model.GetOrCreate<CpModelMapping>();
2065 mapping.Literals(updated_model_proto.assumptions()), &local_model);
2066
2067 const std::string& solution_info = model->Name();
2068 if (status == SatSolver::Status::FEASIBLE) {
2069 const std::vector<int64_t> solution =
2070 GetSolutionValues(model_proto, local_model);
2071 if (DEBUG_MODE) {
2072 const std::vector<int64_t> updated_solution =
2073 GetSolutionValues(updated_model_proto, local_model);
2074 LOG(INFO) << "Found solution with repaired hint penalty = "
2075 << ComputeInnerObjective(updated_model_proto.objective(),
2076 updated_solution);
2077 }
2078 shared_response_manager->NewSolution(
2079 solution, absl::StrCat(solution_info, " [repaired]"), &local_model);
2080 }
2081
2082 // Make sure we update the higher model with the timing info.
2083 model->GetOrCreate<TimeLimit>()->AdvanceDeterministicTime(
2085}
2086
2087// TODO(user): If this ever shows up in the profile, we could avoid copying
2088// the mapping_proto if we are careful about how we modify the variable domain
2089// before postsolving it. Note that 'num_variables_in_original_model' refers to
2090// the model before presolve.
2091void PostsolveResponseWithFullSolver(int num_variables_in_original_model,
2092 CpModelProto mapping_proto,
2093 absl::Span<const int> postsolve_mapping,
2094 std::vector<int64_t>* solution) {
2095 WallTimer wall_timer;
2096 wall_timer.Start();
2097
2098 // Fix the correct variable in the mapping_proto.
2099 for (int i = 0; i < solution->size(); ++i) {
2100 auto* var_proto = mapping_proto.mutable_variables(postsolve_mapping[i]);
2101 var_proto->clear_domain();
2102 var_proto->add_domain((*solution)[i]);
2103 var_proto->add_domain((*solution)[i]);
2104 }
2105
2106 // Postosolve parameters.
2107 // TODO(user): this problem is usually trivial, but we may still want to
2108 // impose a time limit or copy some of the parameters passed by the user.
2109 Model postsolve_model;
2110 postsolve_model.Register<WallTimer>(&wall_timer);
2111 {
2112 SatParameters& params = *postsolve_model.GetOrCreate<SatParameters>();
2113 params.set_linearization_level(0);
2115 }
2116
2117 auto* response_manager = postsolve_model.GetOrCreate<SharedResponseManager>();
2118 response_manager->InitializeObjective(mapping_proto);
2119
2120 LoadCpModel(mapping_proto, &postsolve_model);
2121 SolveLoadedCpModel(mapping_proto, &postsolve_model);
2122 const CpSolverResponse postsolve_response = response_manager->GetResponse();
2123 CHECK(postsolve_response.status() == CpSolverStatus::FEASIBLE ||
2124 postsolve_response.status() == CpSolverStatus::OPTIMAL)
2125 << postsolve_response.status();
2126
2127 // We only copy the solution from the postsolve_response to the response.
2128 CHECK_LE(num_variables_in_original_model,
2129 postsolve_response.solution().size());
2130 solution->assign(
2131 postsolve_response.solution().begin(),
2132 postsolve_response.solution().begin() + num_variables_in_original_model);
2133}
2134
2136 int num_variable_in_original_model,
2137 const CpModelProto& mapping_proto,
2138 absl::Span<const int> postsolve_mapping,
2139 std::vector<int64_t>* solution) {
2140 if (params.debug_postsolve_with_full_solver()) {
2141 PostsolveResponseWithFullSolver(num_variable_in_original_model,
2142 mapping_proto, postsolve_mapping, solution);
2143 } else {
2144 PostsolveResponse(num_variable_in_original_model, mapping_proto,
2145 postsolve_mapping, solution);
2146 }
2147}
2148
2149void AdaptGlobalParameters(const CpModelProto& model_proto, Model* model) {
2150 auto* params = model->GetOrCreate<SatParameters>();
2151 auto* logger = model->GetOrCreate<SolverLogger>();
2152
2153 // Update params.num_workers() if the old field was used.
2154 if (params->num_workers() == 0) {
2155 params->set_num_workers(params->num_search_workers());
2156 }
2157
2158 if (params->enumerate_all_solutions()) {
2159 if (params->num_workers() == 0) {
2160 SOLVER_LOG(logger,
2161 "Setting num_workers to 1 since it is not specified and "
2162 "enumerate_all_solutions is true.");
2163 params->set_num_workers(1);
2164 } else if (params->num_workers() > 1) {
2165 SOLVER_LOG(
2166 logger,
2167 "WARNING: enumerating all solutions in multi-thread works but might "
2168 "lead to the same solution being found up to num_workers times.");
2169 }
2170
2171 if (!params->has_keep_all_feasible_solutions_in_presolve()) {
2172 SOLVER_LOG(logger,
2173 "Forcing presolve to keep all feasible solution given that "
2174 "enumerate_all_solutions is true and that option is unset.");
2175 params->set_keep_all_feasible_solutions_in_presolve(true);
2176 }
2177 }
2178
2179 if (!model_proto.assumptions().empty()) {
2180 if (params->num_workers() >= 1) {
2181 SOLVER_LOG(logger,
2182 "Forcing sequential search as assumptions are not supported "
2183 "in multi-thread.");
2184 }
2185 if (!params->keep_all_feasible_solutions_in_presolve()) {
2186 SOLVER_LOG(logger,
2187 "Forcing presolve to keep all feasible solutions in the "
2188 "presence of assumptions.");
2189 params->set_keep_all_feasible_solutions_in_presolve(true);
2190 }
2191 params->set_num_workers(1);
2192 }
2193
2194 if (params->num_workers() == 0) {
2195 // Initialize the number of workers if set to 0.
2196#if !defined(__PORTABLE_PLATFORM__)
2197 // Sometimes, hardware_concurrency will return 0. So always default to 1.
2198 const int num_cores = std::max<int>(std::thread::hardware_concurrency(), 1);
2199#else
2200 const int num_cores = 1;
2201#endif
2202 SOLVER_LOG(logger, "Setting number of workers to ", num_cores);
2203 params->set_num_workers(num_cores);
2204 }
2205
2206 if (params->shared_tree_num_workers() == -1) {
2207 int num_shared_tree_workers = 0;
2208 if (model_proto.has_objective() ||
2209 model_proto.has_floating_point_objective()) {
2210 num_shared_tree_workers = (params->num_workers() - 16) / 2;
2211 } else {
2212 num_shared_tree_workers = (params->num_workers() - 8) * 3 / 4;
2213 }
2214 if (num_shared_tree_workers > 4) {
2215 SOLVER_LOG(logger, "Setting number of shared tree workers to ",
2216 num_shared_tree_workers);
2217 params->set_shared_tree_num_workers(num_shared_tree_workers);
2218 }
2219 }
2220
2221 // We currently only use the feasibility pump or rins/rens if it is enabled
2222 // and some other parameters are not on.
2223 //
2224 // TODO(user): for now this is not deterministic so we disable it on
2225 // interleave search. Fix.
2226 if (params->interleave_search() || params->num_workers() == 1 ||
2227 !params->use_lns()) {
2228 params->set_use_rins_lns(false);
2229 params->set_use_feasibility_pump(false);
2230 }
2231
2232 // We disable this if the global param asked for no LP.
2233 if (params->linearization_level() == 0) {
2234 params->set_use_feasibility_pump(false);
2235 }
2236
2237 // Disable shared bounds if we are in single thread and we are not
2238 // tightening the domains.
2239 if (!params->fill_tightened_domains_in_response() &&
2240 params->num_workers() == 1) {
2241 params->set_share_level_zero_bounds(false);
2242 }
2243}
2244
2246 : model_proto(*proto),
2247 wall_timer(global_model->GetOrCreate<WallTimer>()),
2248 time_limit(global_model->GetOrCreate<ModelSharedTimeLimit>()),
2249 logger(global_model->GetOrCreate<SolverLogger>()),
2250 stats(global_model->GetOrCreate<SharedStatistics>()),
2251 stat_tables(global_model->GetOrCreate<SharedStatTables>()),
2252 response(global_model->GetOrCreate<SharedResponseManager>()),
2253 shared_tree_manager(global_model->GetOrCreate<SharedTreeManager>()),
2254 ls_hints(global_model->GetOrCreate<SharedLsSolutionRepository>()),
2255 progress_logger(global_model->GetOrCreate<SolverProgressLogger>()),
2256 lrat_proof_status(global_model->GetOrCreate<SharedLratProofStatus>()) {
2257 const SatParameters& params = *global_model->GetOrCreate<SatParameters>();
2258
2259 if (params.share_level_zero_bounds()) {
2260 bounds = std::make_unique<SharedBoundsManager>(*proto);
2261 bounds->set_dump_prefix(absl::GetFlag(FLAGS_cp_model_dump_prefix));
2262 bounds->LoadDebugSolution(response->DebugSolution());
2263 }
2264
2265 if (params.share_linear2_bounds()) {
2266 linear2_bounds = std::make_unique<SharedLinear2Bounds>();
2267 }
2268
2269 // Create extra shared classes if needed. Note that while these parameters
2270 // are true by default, we disable them if we don't have enough workers for
2271 // them in AdaptGlobalParameters().
2272 //
2273 // Registering them to the global model should not really be necessary,
2274 // except if one wants to expect them from outside SolveCpModel().
2275 if (params.use_rins_lns() || params.use_feasibility_pump()) {
2276 lp_solutions = std::make_unique<SharedLPSolutionRepository>(
2277 /*num_solutions_to_keep=*/10);
2278 global_model->Register<SharedLPSolutionRepository>(lp_solutions.get());
2279
2280 incomplete_solutions = std::make_unique<SharedIncompleteSolutionManager>();
2281 global_model->Register<SharedIncompleteSolutionManager>(
2282 incomplete_solutions.get());
2283 }
2284
2285 // Set up synchronization mode in parallel.
2286 const bool always_synchronize =
2287 !params.interleave_search() || params.num_workers() <= 1;
2288 response->SetSynchronizationMode(always_synchronize);
2289 if (params.share_binary_clauses() && params.num_workers() > 1) {
2290 clauses = std::make_unique<SharedClausesManager>(always_synchronize);
2291 }
2292}
2293
2295 // Note that we do not register the logger which is not a shared class.
2299 local_model->Register<SharedStatistics>(stats);
2300 local_model->Register<SharedStatTables>(stat_tables);
2302
2303 // TODO(user): Use parameters and not the presence/absence of these class
2304 // to decide when to use them? this is not clear.
2305 if (lp_solutions != nullptr) {
2307 }
2308 if (incomplete_solutions != nullptr) {
2310 incomplete_solutions.get());
2311 }
2312 if (bounds != nullptr) {
2313 local_model->Register<SharedBoundsManager>(bounds.get());
2314 }
2315 if (clauses != nullptr) {
2316 local_model->Register<SharedClausesManager>(clauses.get());
2317 }
2318 if (linear2_bounds != nullptr) {
2319 local_model->Register<SharedLinear2Bounds>(linear2_bounds.get());
2320 }
2321}
2322
2324 if (response->ProblemIsSolved()) {
2325 // This is for cases where the time limit is checked more often.
2326 time_limit->Stop();
2327 return true;
2328 }
2329 if (time_limit->LimitReached()) return true;
2330 return false;
2331}
2332
2334 if (!logger->LoggingIsEnabled()) return;
2335
2336 logger->FlushPendingThrottledLogs(/*ignore_rates=*/true);
2337 SOLVER_LOG(logger, "");
2338
2339 stat_tables->Display(logger);
2340 progress_logger->DisplayImprovementStatistics(logger);
2341
2342 std::vector<std::vector<std::string>> table;
2343 table.push_back({"Solution repositories", "Added", "Queried", "Synchro"});
2344 response->SolutionPool().AddTableStats(&table);
2345 table.push_back(ls_hints->TableLineStats());
2346 if (lp_solutions != nullptr) {
2347 table.push_back(lp_solutions->TableLineStats());
2348 }
2349 if (incomplete_solutions != nullptr) {
2350 table.push_back(incomplete_solutions->TableLineStats());
2351 }
2352 SOLVER_LOG(logger, FormatTable(table));
2353
2354 // TODO(user): we can combine the "bounds table" into one for shorter logs.
2355 if (bounds != nullptr) bounds->LogStatistics(logger);
2356 if (linear2_bounds != nullptr) linear2_bounds->LogStatistics(logger);
2357
2358 if (clauses != nullptr) clauses->LogStatistics(logger);
2359
2360 // Extra logging if needed. Note that these are mainly activated on
2361 // --vmodule *some_file*=1 and are here for development.
2362 stats->Log(logger);
2363
2365}
2366
2367} // namespace sat
2368} // namespace operations_research
bool AddEdge(int node1, int node2)
Definition model.h:345
void Start()
Definition timer.h:30
bool IsIncludedIn(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
double GetElapsedDeterministicTime() const
Definition time_limit.h:220
bool ComputeTransitiveReduction(bool log_info=false)
Definition clause.cc:2214
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_lin_max()
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
::int32_t assumptions(int index) const
::operations_research::sat::IntegerVariableProto *PROTOBUF_NONNULL add_variables()
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL add_constraints()
::operations_research::sat::CpObjectiveProto *PROTOBUF_NONNULL mutable_objective()
::operations_research::sat::IntegerVariableProto *PROTOBUF_NONNULL mutable_variables(int index)
const ::std::string & name() const
const ::operations_research::sat::PartialVariableAssignment & solution_hint() const
const ::operations_research::sat::CpObjectiveProto & objective() const
::operations_research::sat::CpSolverStatus status() const
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
::operations_research::sat::LinearExpressionProto *PROTOBUF_NONNULL mutable_target()
BooleanVariable Variable() const
Definition sat_base.h:88
T Add(std::function< T(Model *)> f)
Definition model.h:104
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition model.h:110
const std::string & Name() const
Definition model.h:200
void Register(T *non_owned_class)
Definition model.h:194
bool ProbeBooleanVariables(double deterministic_time_limit)
Definition probing.cc:284
static constexpr SearchBranching FIXED_SEARCH
static constexpr SearchBranching HINT_SEARCH
bool MinimizeByPropagation(double dtime, bool minimize_new_clauses_only=false)
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
void ReportPotentialNewBounds(const std::string &worker_name, absl::Span< const int > variables, absl::Span< const int64_t > new_lower_bounds, absl::Span< const int64_t > new_upper_bounds)
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
void AddBinaryClause(int id, int lit1, int lit2)
void AddBatch(int id, CompactVectorVector< int > batch)
void GetUnseenBinaryClauses(int id, std::vector< std::pair< int, int > > *new_clauses)
const CompactVectorVector< int > & GetUnseenClauses(int id)
void NotifyNumImported(int id, int64_t num_imported)
std::vector< std::pair< Key, std::pair< IntegerValue, IntegerValue > > > NewlyUpdatedBounds(int import_id)
void NotifyNumImported(int import_id, int num)
void InitializeObjective(const CpModelProto &cp_model)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
ABSL_FLAG(std::string, cp_model_load_debug_solution, "", "DEBUG ONLY. When this is set to a non-empty file name, " "we will interpret this as an internal solution which can be used for " "debugging. For instance we use it to identify wrong cuts/reasons.")
absl::Status GetTextProto(absl::string_view file_name, google::protobuf::Message *proto, Options options)
Definition file.cc:409
Options Defaults()
Definition file.h:86
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 AddWeightedSumLowerOrEqual(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coefficients, int64_t upper_bound, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
void DetectOptionalVariables(const CpModelProto &model_proto, Model *m)
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)
void AddWeightedSumGreaterOrEqual(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coefficients, int64_t lower_bound, Model *model)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition integer.h:1819
std::function< BooleanOrIntegerLiteral()> ConstructIntegerCompletionSearchStrategy(absl::Span< const IntegerVariable > variable_mapping, IntegerVariable objective_var, Model *model)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
void LoadVariables(const CpModelProto &model_proto, bool view_all_booleans_as_integers, Model *m)
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
void RegisterObjectiveBoundsImport(SharedResponseManager *shared_response_manager, Model *model)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
std::function< BooleanOrIntegerLiteral()> ConstructHintSearchStrategy(const CpModelProto &cp_model_proto, CpModelMapping *mapping, Model *model)
void InitializeDebugSolution(const CpModelProto &model_proto, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
void PostsolveResponseWithFullSolver(int num_variables_in_original_model, CpModelProto mapping_proto, absl::Span< const int > postsolve_mapping, std::vector< int64_t > *solution)
void RegisterClausesExport(int id, SharedClausesManager *shared_clauses_manager, Model *model)
void MinimizeL1DistanceWithHint(const CpModelProto &model_proto, Model *model)
std::function< IntegerVariable(Model *)> ConstantIntegerVariable(int64_t value)
Definition integer.h:1763
void LoadBooleanSymmetries(const CpModelProto &model_proto, Model *m)
void AddFullEncodingFromSearchBranching(const CpModelProto &model_proto, Model *m)
CutGenerator CreateCliqueCutGenerator(absl::Span< const IntegerVariable > base_variables, Model *model)
Definition cuts.cc:2971
IntegerVariable PositiveVariable(IntegerVariable i)
std::function< BooleanOrIntegerLiteral()> ConstructHeuristicSearchStrategy(const CpModelProto &cp_model_proto, Model *model)
void LoadFeasibilityPump(const CpModelProto &model_proto, Model *model)
void ExtractElementEncoding(const CpModelProto &model_proto, Model *m)
constexpr ClauseId kNoClauseId(0)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition integer.h:1839
void ConstructFixedSearchStrategy(SearchHeuristics *h, Model *model)
void AdaptGlobalParameters(const CpModelProto &model_proto, Model *model)
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition integer.h:1825
std::vector< int > GetOrbits(int n, absl::Span< const std::unique_ptr< SparsePermutation > > generators)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
std::function< IntegerVariable(Model *)> NewIntegerVariable(int64_t lb, int64_t ub)
Definition integer.h:1771
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)
absl::string_view ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
void PostsolveResponse(const int64_t num_variables_in_original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, std::vector< int64_t > *solution)
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 PostsolveResponseWrapper(const SatParameters &params, int num_variable_in_original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, std::vector< int64_t > *solution)
void RegisterVariableBoundsLevelZeroImport(const CpModelProto &model_proto, SharedBoundsManager *shared_bounds_manager, Model *model)
void LoadCpModel(const CpModelProto &model_proto, Model *model)
std::unique_ptr< SparsePermutation > CreateSparsePermutationFromProto(int n, const SparsePermutationProto &proto)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
bool VariableIsPositive(IntegerVariable i)
void RegisterObjectiveBestBoundExport(IntegerVariable objective_var, SharedResponseManager *shared_response_manager, Model *model)
std::function< void(Model *)> ExcludeCurrentSolutionAndBacktrack()
void RegisterLinear2BoundsImport(SharedLinear2Bounds *shared_linear2_bounds, Model *model)
int RegisterClausesLevelZeroImport(int id, SharedClausesManager *shared_clauses_manager, Model *model)
void RestrictObjectiveDomainWithBinarySearch(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
std::function< BooleanOrIntegerLiteral()> InstrumentSearchStrategy(const CpModelProto &cp_model_proto, absl::Span< const IntegerVariable > variable_mapping, std::function< BooleanOrIntegerLiteral()> instrumented_strategy, Model *model)
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
Definition util.cc:61
void ExtractEncoding(const CpModelProto &model_proto, Model *m)
void SplitAndLoadIntermediateConstraints(bool lb_required, bool ub_required, std::vector< IntegerVariable > *vars, std::vector< IntegerValue > *coeffs, Model *m)
OR-Tools root namespace.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
const bool DEBUG_MODE
Definition radix_sort.h:266
std::string ProtobufDebugString(const P &message)
Definition proto_utils.h:31
if(!yyg->yy_init)
Definition parser.yy.cc:966
Definition model.h:50
int64_t Max() const
Definition model.cc:346
int64_t Min() const
Definition model.cc:340
util_intops::StrongVector< IntegerVariable, bool > ivar_has_value
util_intops::StrongVector< IntegerVariable, IntegerValue > ivar_values
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
std::vector< LinearConstraint > linear_constraints
std::function< BooleanOrIntegerLiteral()> user_search
SharedLsSolutionRepository *const ls_hints
std::unique_ptr< SharedIncompleteSolutionManager > incomplete_solutions
std::unique_ptr< SharedClausesManager > clauses
SharedClasses(const CpModelProto *proto, Model *global_model)
std::unique_ptr< SharedLPSolutionRepository > lp_solutions
std::unique_ptr< SharedLinear2Bounds > linear2_bounds
std::unique_ptr< SharedBoundsManager > bounds
#define SOLVER_LOG(logger,...)
Definition logging.h:114