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