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