Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
integer_search.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 <cmath>
18#include <cstdint>
19#include <functional>
20#include <random>
21#include <string>
22#include <tuple>
23#include <vector>
24
25#include "absl/container/flat_hash_set.h"
26#include "absl/log/check.h"
27#include "absl/log/log.h"
28#include "absl/log/vlog_is_on.h"
29#include "absl/random/distributions.h"
30#include "absl/strings/str_cat.h"
31#include "absl/types/span.h"
33#include "ortools/sat/clause.h"
37#include "ortools/sat/integer.h"
42#include "ortools/sat/model.h"
43#include "ortools/sat/probing.h"
45#include "ortools/sat/restart.h"
46#include "ortools/sat/rins.h"
54#include "ortools/sat/util.h"
57
58namespace operations_research {
59namespace sat {
60
61IntegerLiteral AtMinValue(IntegerVariable var, IntegerTrail* integer_trail) {
62 const IntegerValue lb = integer_trail->LowerBound(var);
63 DCHECK_LE(lb, integer_trail->UpperBound(var));
64 if (lb == integer_trail->UpperBound(var)) return IntegerLiteral();
65 return IntegerLiteral::LowerOrEqual(var, lb);
66}
67
69 IntegerVariable var, IntegerTrail* integer_trail,
70 ObjectiveDefinition* objective_definition) {
71 const auto& variables = objective_definition->objective_impacting_variables;
72 if (variables.contains(var)) {
73 return AtMinValue(var, integer_trail);
74 } else if (variables.contains(NegationOf(var))) {
75 return AtMinValue(NegationOf(var), integer_trail);
76 }
77 return IntegerLiteral();
78}
79
81 IntegerTrail* integer_trail) {
82 const IntegerValue var_lb = integer_trail->LowerBound(var);
83 const IntegerValue var_ub = integer_trail->UpperBound(var);
84 CHECK_LT(var_lb, var_ub);
85
86 const IntegerValue chosen_value =
87 var_lb + std::max(IntegerValue(1), (var_ub - var_lb) / IntegerValue(2));
88 return IntegerLiteral::GreaterOrEqual(var, chosen_value);
89}
90
91IntegerLiteral SplitAroundGivenValue(IntegerVariable var, IntegerValue value,
92 Model* model) {
93 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
94 const IntegerValue lb = integer_trail->LowerBound(var);
95 const IntegerValue ub = integer_trail->UpperBound(var);
96
97 const absl::flat_hash_set<IntegerVariable>& variables =
98 model->GetOrCreate<ObjectiveDefinition>()->objective_impacting_variables;
99
100 // Heuristic: Prefer the objective direction first. Reference: Conflict-Driven
101 // Heuristics for Mixed Integer Programming (2019) by Jakob Witzig and Ambros
102 // Gleixner.
103 // NOTE: The value might be out of bounds. In that case we return
104 // kNoLiteralIndex.
105 const bool branch_down_feasible = value >= lb && value < ub;
106 const bool branch_up_feasible = value > lb && value <= ub;
107 if (variables.contains(var) && branch_down_feasible) {
108 return IntegerLiteral::LowerOrEqual(var, value);
109 } else if (variables.contains(NegationOf(var)) && branch_up_feasible) {
110 return IntegerLiteral::GreaterOrEqual(var, value);
111 } else if (branch_down_feasible) {
112 return IntegerLiteral::LowerOrEqual(var, value);
113 } else if (branch_up_feasible) {
114 return IntegerLiteral::GreaterOrEqual(var, value);
115 }
116 return IntegerLiteral();
117}
118
119IntegerLiteral SplitAroundLpValue(IntegerVariable var, Model* model) {
120 auto* parameters = model->GetOrCreate<SatParameters>();
121 auto* lp_dispatcher = model->GetOrCreate<LinearProgrammingDispatcher>();
122
123 const IntegerVariable positive_var = PositiveVariable(var);
124 const auto& it = lp_dispatcher->find(positive_var);
126 it == lp_dispatcher->end() ? nullptr : it->second;
127
128 // We only use this if the sub-lp has a solution, and depending on the value
129 // of exploit_all_lp_solution() if it is a pure-integer solution.
130 if (lp == nullptr || !lp->HasSolution()) return IntegerLiteral();
131 if (!parameters->exploit_all_lp_solution() && !lp->SolutionIsInteger()) {
132 return IntegerLiteral();
133 }
134
135 // TODO(user): Depending if we branch up or down, this might not exclude the
136 // LP value, which is potentially a bad thing.
137 //
138 // TODO(user): Why is the reduced cost doing things differently?
139 const IntegerValue value = IntegerValue(
140 static_cast<int64_t>(std::round(lp->GetSolutionValue(positive_var))));
141
142 // Because our lp solution might be from higher up in the tree, it
143 // is possible that value is now outside the domain of positive_var.
144 // In this case, this function will return an invalid literal.
145 return SplitAroundGivenValue(positive_var, value, model);
146}
147
149 IntegerVariable var, const SharedSolutionRepository<int64_t>& solution_repo,
150 Model* model) {
151 if (solution_repo.NumSolutions() == 0) {
152 return IntegerLiteral();
153 }
154
155 const IntegerVariable positive_var = PositiveVariable(var);
156 const int proto_var =
157 model->Get<CpModelMapping>()->GetProtoVariableFromIntegerVariable(
158 positive_var);
159
160 if (proto_var < 0) {
161 return IntegerLiteral();
162 }
163
164 const IntegerValue value(solution_repo.GetVariableValueInSolution(
165 proto_var, /*solution_index=*/0));
166 return SplitAroundGivenValue(positive_var, value, model);
167}
168
169// TODO(user): the complexity caused by the linear scan in this heuristic and
170// the one below is ok when search_branching is set to SAT_SEARCH because it is
171// not executed often, but otherwise it is done for each search decision,
172// which seems expensive. Improve.
174 absl::Span<const IntegerVariable> vars, Model* model) {
175 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
176 return
177 [/*copy*/ vars = std::vector<IntegerVariable>(vars.begin(), vars.end()),
178 integer_trail]() {
179 for (const IntegerVariable var : vars) {
180 const IntegerLiteral decision = AtMinValue(var, integer_trail);
181 if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
182 }
184 };
185}
186
188 auto* lp_values = model->GetOrCreate<ModelLpValues>();
189 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
190 return [lp_values, integer_trail, model]() {
191 double best_fractionality = 0.0;
193 for (IntegerVariable var(0); var < lp_values->size(); var += 2) {
194 if (integer_trail->IsFixed(var)) continue;
195 const double lp_value = (*lp_values)[var];
196 const double fractionality = std::abs(lp_value - std::round(lp_value));
197 if (fractionality > best_fractionality) {
198 best_fractionality = fractionality;
199
200 // This choose <= value if possible.
202 var, IntegerValue(std::floor(lp_value)), model));
203 }
204 }
205 return decision;
206 };
207}
208
210 auto* lp_values = model->GetOrCreate<ModelLpValues>();
211 auto* encoder = model->GetOrCreate<IntegerEncoder>();
212 auto* pseudo_costs = model->GetOrCreate<PseudoCosts>();
213 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
214 return [lp_values, encoder, pseudo_costs, integer_trail]() {
215 double best_score = 0.0;
217 for (IntegerVariable var(0); var < lp_values->size(); var += 2) {
218 // Only look at non-fixed booleans.
219 const IntegerValue lb = integer_trail->LowerBound(var);
220 const IntegerValue ub = integer_trail->UpperBound(var);
221 if (lb != 0 || ub != 1) continue;
222
223 // Get associated literal.
224 const LiteralIndex index =
225 encoder->GetAssociatedLiteral(IntegerLiteral::GreaterOrEqual(var, 1));
226 if (index == kNoLiteralIndex) continue;
227
228 const double lp_value = (*lp_values)[var];
229 const double score =
230 pseudo_costs->BoolPseudoCost(Literal(index), lp_value);
231 if (score > best_score) {
232 best_score = score;
233 decision = BooleanOrIntegerLiteral(Literal(index));
234 }
235 }
236 return decision;
237 };
238}
239
241 auto* lp_values = model->GetOrCreate<ModelLpValues>();
242 auto* pseudo_costs = model->GetOrCreate<PseudoCosts>();
243 return [lp_values, pseudo_costs]() {
244 double best_score = 0.0;
246 for (IntegerVariable var(0); var < lp_values->size(); var += 2) {
247 const PseudoCosts::BranchingInfo info =
248 pseudo_costs->EvaluateVar(var, *lp_values);
249 if (info.is_fixed) continue;
250
251 // When not reliable, we skip integer.
252 //
253 // TODO(user): Use strong branching when not reliable.
254 // TODO(user): do not branch on integer lp? however it seems better to
255 // do that !? Maybe this is because if it has a high pseudo cost
256 // average, it is good anyway?
257 if (!info.is_reliable && info.is_integer) continue;
258
259 // We delay to subsequent heuristic if the score is 0.0.
260 if (info.score > best_score) {
261 best_score = info.score;
262
263 // This direction works better than the inverse in the benchmarks. But
264 // always branching up seems even better. TODO(user): investigate.
265 if (info.down_score > info.up_score) {
266 decision = BooleanOrIntegerLiteral(info.down_branch);
267 } else {
269 }
270 }
271 }
272 return decision;
273 };
274}
275
276std::function<BooleanOrIntegerLiteral()>
278 absl::Span<const IntegerVariable> vars, Model* model) {
279 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
280 return
281 [/*copy */ vars = std::vector<IntegerVariable>(vars.begin(), vars.end()),
282 integer_trail]() {
283 IntegerVariable candidate = kNoIntegerVariable;
284 IntegerValue candidate_lb;
285 for (const IntegerVariable var : vars) {
286 const IntegerValue lb = integer_trail->LowerBound(var);
287 if (lb < integer_trail->UpperBound(var) &&
288 (candidate == kNoIntegerVariable || lb < candidate_lb)) {
289 candidate = var;
290 candidate_lb = lb;
291 }
292 }
293 if (candidate == kNoIntegerVariable) return BooleanOrIntegerLiteral();
294 return BooleanOrIntegerLiteral(AtMinValue(candidate, integer_trail));
295 };
296}
297
299 std::vector<std::function<BooleanOrIntegerLiteral()>> heuristics) {
300 if (DEBUG_MODE) {
301 for (const auto& h : heuristics) {
302 DCHECK(h != nullptr);
303 }
304 }
305 return [heuristics]() {
306 for (const auto& h : heuristics) {
307 const BooleanOrIntegerLiteral decision = h();
308 if (decision.HasValue()) return decision;
309 }
311 };
312}
313
315 std::vector<std::function<IntegerLiteral(IntegerVariable)>>
316 value_selection_heuristics,
317 std::function<BooleanOrIntegerLiteral()> var_selection_heuristic,
318 Model* model) {
319 auto* encoder = model->GetOrCreate<IntegerEncoder>();
320 auto* sat_policy = model->GetOrCreate<SatDecisionPolicy>();
321 return [=]() {
322 // Get the current decision.
323 const BooleanOrIntegerLiteral current_decision = var_selection_heuristic();
324 if (!current_decision.HasValue()) return current_decision;
325
326 // When we are in the "stable" phase, we prefer to follow the SAT polarity
327 // heuristic.
328 if (current_decision.boolean_literal_index != kNoLiteralIndex &&
329 sat_policy->InStablePhase()) {
330 return current_decision;
331 }
332
333 // IntegerLiteral case.
334 if (current_decision.boolean_literal_index == kNoLiteralIndex) {
335 for (const auto& value_heuristic : value_selection_heuristics) {
336 const IntegerLiteral decision =
337 value_heuristic(current_decision.integer_literal.var);
338 if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
339 }
340 return current_decision;
341 }
342
343 // Boolean case. We try to decode the Boolean decision to see if it is
344 // associated with an integer variable.
345 //
346 // TODO(user): we will likely stop at the first non-fixed variable.
347 for (const IntegerVariable var : encoder->GetAllAssociatedVariables(
348 Literal(current_decision.boolean_literal_index))) {
349 // Sequentially try the value selection heuristics.
350 for (const auto& value_heuristic : value_selection_heuristics) {
351 const IntegerLiteral decision = value_heuristic(var);
352 if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
353 }
354 }
355
356 return current_decision;
357 };
358}
359
361 auto* lp_constraints =
363 int num_lp_variables = 0;
364 for (LinearProgrammingConstraint* lp : *lp_constraints) {
365 num_lp_variables += lp->NumVariables();
366 }
367 const int num_integer_variables =
368 model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value() / 2;
369 return (num_integer_variables <= 2 * num_lp_variables);
370}
371
372// Note that all these heuristic do not depend on the variable being positive
373// or negative.
374//
375// TODO(user): Experiment more with value selection heuristics.
377 std::function<BooleanOrIntegerLiteral()> var_selection_heuristic,
378 Model* model) {
379 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
380 std::vector<std::function<IntegerLiteral(IntegerVariable)>>
381 value_selection_heuristics;
382
383 // LP based value.
384 //
385 // Note that we only do this if a big enough percentage of the problem
386 // variables appear in the LP relaxation.
387 if (LinearizedPartIsLarge(model) &&
388 (parameters.exploit_integer_lp_solution() ||
389 parameters.exploit_all_lp_solution())) {
390 value_selection_heuristics.push_back([model](IntegerVariable var) {
391 return SplitAroundLpValue(PositiveVariable(var), model);
392 });
393 }
394
395 // Solution based value.
396 if (parameters.exploit_best_solution()) {
397 auto* response_manager = model->Get<SharedResponseManager>();
398 if (response_manager != nullptr) {
399 VLOG(3) << "Using best solution value selection heuristic.";
400 value_selection_heuristics.push_back(
401 [model, response_manager](IntegerVariable var) {
403 var, response_manager->SolutionPool().BestSolutions(), model);
404 });
405 }
406 }
407
408 // Objective based value.
409 if (parameters.exploit_objective()) {
410 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
411 auto* objective_definition = model->GetOrCreate<ObjectiveDefinition>();
412 value_selection_heuristics.push_back([integer_trail, objective_definition](
413 IntegerVariable var) {
414 return ChooseBestObjectiveValue(var, integer_trail, objective_definition);
415 });
416 }
417
418 return SequentialValueSelection(value_selection_heuristics,
419 var_selection_heuristic, model);
420}
421
423 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
424 Trail* trail = model->GetOrCreate<Trail>();
425 SatDecisionPolicy* decision_policy = model->GetOrCreate<SatDecisionPolicy>();
426 return [sat_solver, trail, decision_policy] {
427 const bool all_assigned = trail->Index() == sat_solver->NumVariables();
428 if (all_assigned) return BooleanOrIntegerLiteral();
429 const Literal result = decision_policy->NextBranch();
430 CHECK(!sat_solver->Assignment().LiteralIsAssigned(result));
431 return BooleanOrIntegerLiteral(result.Index());
432 };
433}
434
435// TODO(user): Do we need a mechanism to reduce the range of possible gaps
436// when nothing gets proven? This could be a parameter or some adaptative code.
438 auto* objective_definition = model->GetOrCreate<ObjectiveDefinition>();
439 const IntegerVariable obj_var = objective_definition->objective_var;
440 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
441 auto* sat_solver = model->GetOrCreate<SatSolver>();
442 auto* random = model->GetOrCreate<ModelRandomGenerator>();
443
444 return [obj_var, integer_trail, sat_solver, random]() {
446 const int level = sat_solver->CurrentDecisionLevel();
447 if (level > 0 || obj_var == kNoIntegerVariable) return result;
448
449 const IntegerValue obj_lb = integer_trail->LowerBound(obj_var);
450 const IntegerValue obj_ub = integer_trail->UpperBound(obj_var);
451 if (obj_lb == obj_ub) return result;
452 const IntegerValue mid = (obj_ub - obj_lb) / 2;
453 const IntegerValue new_ub =
454 obj_lb + absl::LogUniform<int64_t>(*random, 0, mid.value());
455
456 result.integer_literal = IntegerLiteral::LowerOrEqual(obj_var, new_ub);
457 return result;
458 };
459}
460
461std::function<BooleanOrIntegerLiteral()> PseudoCost(Model* model) {
462 auto* objective = model->Get<ObjectiveDefinition>();
463 const bool has_objective =
464 objective != nullptr && objective->objective_var != kNoIntegerVariable;
465 if (!has_objective) {
466 return []() { return BooleanOrIntegerLiteral(); };
467 }
468
469 auto* pseudo_costs = model->GetOrCreate<PseudoCosts>();
470 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
471 return [pseudo_costs, integer_trail]() {
472 const IntegerVariable chosen_var = pseudo_costs->GetBestDecisionVar();
473 if (chosen_var == kNoIntegerVariable) return BooleanOrIntegerLiteral();
474
475 // TODO(user): This will be overridden by the value decision heuristic in
476 // almost all cases.
478 GreaterOrEqualToMiddleValue(chosen_var, integer_trail));
479 };
480}
481
482// A simple heuristic for scheduling models.
484 Model* model) {
485 auto* repo = model->GetOrCreate<IntervalsRepository>();
486 auto* heuristic = model->GetOrCreate<SearchHeuristics>();
487 auto* trail = model->GetOrCreate<Trail>();
488 auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
489 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
490 const int64_t randomization_size = std::max<int64_t>(
491 1,
493 auto* random = model->GetOrCreate<ModelRandomGenerator>();
494
495 // To avoid to scan already fixed intervals, we use a simple reversible int.
496 auto* rev_int_repo = model->GetOrCreate<RevIntRepository>();
497 const int num_intervals = repo->NumIntervals();
498 int rev_fixed = 0;
499 bool rev_is_in_dive = false;
500 std::vector<IntervalVariable> intervals(num_intervals);
501 std::vector<IntegerValue> cached_start_mins(num_intervals);
502 for (IntervalVariable i(0); i < num_intervals; ++i) {
503 intervals[i.value()] = i;
504 }
505
506 // Note(user): only the model is captured for no reason.
507 return [=]() mutable {
508 struct ToSchedule {
509 // Variable to fix.
510 LiteralIndex presence = kNoLiteralIndex;
511 AffineExpression start;
513
514 // Information to select best.
515 IntegerValue size_min = kMaxIntegerValue;
516 IntegerValue start_min = kMaxIntegerValue;
517 IntegerValue start_max = kMaxIntegerValue;
518 double noise = 0.5;
519
520 // We want to pack interval to the left. If two have the same start_min,
521 // we want to choose the one that will likely leave an easier problem for
522 // the other tasks.
523 bool operator<(const ToSchedule& other) const {
524 return std::tie(start_min, start_max, size_min, noise) <
525 std::tie(other.start_min, other.start_max, other.size_min,
526 other.noise);
527 }
528
529 // Generating random noise can take time, so we use this function to
530 // delay it.
531 bool MightBeBetter(const ToSchedule& other) const {
532 return std::tie(start_min, start_max) <=
533 std::tie(other.start_min, other.start_max);
534 }
535 };
536 std::vector<ToSchedule> top_decisions;
537 top_decisions.reserve(randomization_size);
538 top_decisions.resize(1);
539
540 // Save rev_fixed before we modify it.
541 rev_int_repo->SaveState(&rev_fixed);
542
543 // TODO(user): we should also precompute fixed precedences and only fix
544 // interval that have all their predecessors fixed.
545 for (int i = rev_fixed; i < num_intervals; ++i) {
546 const ToSchedule& worst = top_decisions.back();
547 if (rev_is_in_dive && cached_start_mins[i] > worst.start_min) {
548 continue;
549 }
550
551 const IntervalVariable interval = intervals[i];
552 if (repo->IsAbsent(interval)) {
553 std::swap(intervals[i], intervals[rev_fixed]);
554 std::swap(cached_start_mins[i], cached_start_mins[rev_fixed]);
555 ++rev_fixed;
556 continue;
557 }
558
559 const AffineExpression start = repo->Start(interval);
560 const AffineExpression end = repo->End(interval);
561 if (repo->IsPresent(interval) && integer_trail->IsFixed(start) &&
562 integer_trail->IsFixed(end)) {
563 std::swap(intervals[i], intervals[rev_fixed]);
564 std::swap(cached_start_mins[i], cached_start_mins[rev_fixed]);
565 ++rev_fixed;
566 continue;
567 }
568
569 ToSchedule candidate;
570 if (repo->IsOptional(interval)) {
571 // For task whose presence is still unknown, our propagators should
572 // have propagated the minimum time as if it was present. So this
573 // should reflect the earliest time at which this interval can be
574 // scheduled.
575 const Literal lit = repo->PresenceLiteral(interval);
576 candidate.start_min = integer_trail->ConditionalLowerBound(lit, start);
577 candidate.start_max = integer_trail->ConditionalUpperBound(lit, start);
578 } else {
579 candidate.start_min = integer_trail->LowerBound(start);
580 candidate.start_max = integer_trail->UpperBound(start);
581 }
582 cached_start_mins[i] = candidate.start_min;
583 if (top_decisions.size() < randomization_size ||
584 candidate.MightBeBetter(worst)) {
585 // Finish filling candidate.
586 //
587 // For variable size, we compute the min size once the start is fixed
588 // to time. This is needed to never pick the "artificial" makespan
589 // interval at the end in priority compared to intervals that still
590 // need to be scheduled.
591 candidate.start = start;
592 candidate.end = end;
593 candidate.presence = repo->IsOptional(interval)
594 ? repo->PresenceLiteral(interval).Index()
596 candidate.size_min =
597 std::max(integer_trail->LowerBound(repo->Size(interval)),
598 integer_trail->LowerBound(end) - candidate.start_min);
599 candidate.noise = absl::Uniform(*random, 0.0, 1.0);
600
601 if (top_decisions.size() == randomization_size) {
602 // Do not replace if we have a strict inequality now.
603 if (worst < candidate) continue;
604 top_decisions.pop_back();
605 }
606 top_decisions.push_back(candidate);
607 if (top_decisions.size() > 1) {
608 std::sort(top_decisions.begin(), top_decisions.end());
609 }
610 }
611 }
612
613 // Setup rev_is_in_dive to be true on the next call only if there was no
614 // backtrack since the previous call.
615 watcher->SetUntilNextBacktrack(&rev_is_in_dive);
616
617 const ToSchedule best =
618 top_decisions.size() == 1
619 ? top_decisions.front()
620 : top_decisions[absl::Uniform(
621 *random, 0, static_cast<int>(top_decisions.size()))];
622 if (top_decisions.size() > 1) {
623 VLOG(2) << "Choose among " << top_decisions.size() << " "
624 << best.start_min << " " << best.size_min
625 << "[t=" << top_decisions.front().start_min
626 << ", s=" << top_decisions.front().size_min
627 << ", t=" << top_decisions.back().start_min
628 << ", s=" << top_decisions.back().size_min << "]";
629 }
630 if (best.start_min == kMaxIntegerValue) return BooleanOrIntegerLiteral();
631
632 // Use the next_decision_override to fix in turn all the variables from
633 // the selected interval.
634 int num_times = 0;
635 heuristic->next_decision_override = [trail, integer_trail, best,
636 num_times]() mutable {
637 if (++num_times > 5) {
638 // We have been trying to fix this interval for a while. Do we miss
639 // some propagation? In any case, try to see if the heuristic above
640 // would select something else.
641 VLOG(3) << "Skipping ... ";
643 }
644
645 // First make sure the interval is present.
646 if (best.presence != kNoLiteralIndex) {
647 if (!trail->Assignment().LiteralIsAssigned(Literal(best.presence))) {
648 VLOG(3) << "assign " << best.presence;
649 return BooleanOrIntegerLiteral(best.presence);
650 }
651 if (trail->Assignment().LiteralIsFalse(Literal(best.presence))) {
652 VLOG(2) << "unperformed.";
654 }
655 }
656
657 // We assume that start_min is propagated by now.
658 if (!integer_trail->IsFixed(best.start)) {
659 const IntegerValue start_min = integer_trail->LowerBound(best.start);
660 VLOG(3) << "start == " << start_min;
661 return BooleanOrIntegerLiteral(best.start.LowerOrEqual(start_min));
662 }
663
664 // We assume that end_min is propagated by now.
665 if (!integer_trail->IsFixed(best.end)) {
666 const IntegerValue end_min = integer_trail->LowerBound(best.end);
667 VLOG(3) << "end == " << end_min;
668 return BooleanOrIntegerLiteral(best.end.LowerOrEqual(end_min));
669 }
670
671 // Everything is fixed, detach the override.
672 const IntegerValue start = integer_trail->LowerBound(best.start);
673 VLOG(2) << "Fixed @[" << start << ","
674 << integer_trail->LowerBound(best.end) << "]"
675 << (best.presence != kNoLiteralIndex
676 ? absl::StrCat(" presence=",
677 Literal(best.presence).DebugString())
678 : "")
679 << (best.start_min < start ? absl::StrCat(" start_at_selection=",
680 best.start_min.value())
681 : "");
683 };
684
685 return heuristic->next_decision_override();
686 };
687}
688
689namespace {
690
691bool PrecedenceIsBetter(SchedulingConstraintHelper* helper, int a,
692 SchedulingConstraintHelper* other_helper, int other_a) {
693 return std::make_tuple(helper->StartMin(a), helper->StartMax(a),
694 helper->SizeMin(a)) <
695 std::make_tuple(other_helper->StartMin(other_a),
696 other_helper->StartMax(other_a),
697 other_helper->SizeMin(other_a));
698}
699
700} // namespace
701
702// The algo goes as follow:
703// - For each disjunctive, consider the intervals by start time, consider
704// adding the first precedence between overlapping interval.
705// - Take the smallest start time amongst all disjunctive.
707 Model* model) {
708 auto* repo = model->GetOrCreate<IntervalsRepository>();
709 return [repo]() {
710 SchedulingConstraintHelper* best_helper = nullptr;
711 int best_before;
712 int best_after;
713 for (SchedulingConstraintHelper* helper : repo->AllDisjunctiveHelpers()) {
714 if (!helper->SynchronizeAndSetTimeDirection(true)) {
716 }
717
718 // TODO(user): tie break by size/start-max
719 // TODO(user): Use conditional lower bounds? note that in automatic search
720 // all precedence will be fixed before this is called though. In fixed
721 // search maybe we should use the other SchedulingSearchHeuristic().
722 int a = -1;
723 for (auto [b, time] : helper->TaskByIncreasingStartMin()) {
724 if (helper->IsAbsent(b)) continue;
725 if (a == -1 || helper->EndMin(a) <= helper->StartMin(b)) {
726 a = b;
727 continue;
728 }
729
730 // Swap (a,b) if they have the same start_min.
731 if (PrecedenceIsBetter(helper, b, helper, a)) {
732 std::swap(a, b);
733
734 // Corner case in case b can fit before a (size zero)
735 if (helper->EndMin(a) <= helper->StartMin(b)) {
736 a = b;
737 continue;
738 }
739 }
740
741 // TODO(user): Also compare the second part of the precedence in
742 // PrecedenceIsBetter() and not just the interval before?
743 if (best_helper == nullptr ||
744 PrecedenceIsBetter(helper, a, best_helper, best_before)) {
745 best_helper = helper;
746 best_before = a;
747 best_after = b;
748 }
749 break;
750 }
751 }
752
753 if (best_helper != nullptr) {
754 // If one of the task presence is undecided, start by making it present.
755 for (const int t : {best_before, best_after}) {
756 if (!best_helper->IsPresent(t)) {
757 VLOG(2) << "Presence: " << best_helper->TaskDebugString(t);
758 return BooleanOrIntegerLiteral(best_helper->PresenceLiteral(t));
759 }
760 }
761
762 VLOG(2) << "New disjunctive precedence: "
763 << best_helper->TaskDebugString(best_before) << " "
764 << best_helper->TaskDebugString(best_after);
765 const auto a = best_helper->GetIntervalDefinition(best_before);
766 const auto b = best_helper->GetIntervalDefinition(best_after);
768 repo->GetOrCreateDisjunctivePrecedenceLiteralIfNonTrivial(a, b));
769 }
770
772 };
773}
774
775// The algo goes as follow:
776// - Build a profile of all the tasks packed to the right as long as that is
777// feasible.
778// - If we can't grow the profile, we have identified a set of tasks that all
779// overlap if they are packed on the right, and whose sum of demand exceed
780// the capacity.
781// - Look for two tasks in that set that can be made non-overlapping, and take
782// a "precedence" decision between them.
784 Model* model) {
785 auto* repo = model->GetOrCreate<IntervalsRepository>();
786 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
787 auto* trail = model->GetOrCreate<Trail>();
788 auto* search_helper = model->GetOrCreate<IntegerSearchHelper>();
789 return [repo, integer_trail, trail, search_helper]() {
790 SchedulingConstraintHelper* best_helper = nullptr;
791 int best_before = 0;
792 int best_after = 0;
793 for (const auto h : repo->AllCumulativeHelpers()) {
794 auto* helper = h.task_helper;
795 if (!helper->SynchronizeAndSetTimeDirection(true)) {
797 }
798
799 const int num_tasks = helper->NumTasks();
800 std::vector<IntegerValue> added_demand(num_tasks, 0);
801
802 // We use a similar algo as in BuildProfile() in timetable.cc
803 const auto& by_smin = helper->TaskByIncreasingStartMin();
804 const auto& by_emin = helper->TaskByIncreasingEndMin();
805 const IntegerValue capacity_max = integer_trail->UpperBound(h.capacity);
806
807 // Start and height of the currently built profile rectangle.
808 IntegerValue current_height = 0;
809 int first_skipped_task = -1;
810
811 int next_end = 0;
812 int next_start = 0;
813 bool found = false;
814 while (!found && next_end < num_tasks) {
815 IntegerValue time = by_emin[next_end].time;
816 if (next_start < num_tasks) {
817 time = std::min(time, by_smin[next_start].time);
818 }
819
820 // Remove added task ending there.
821 // Set their demand to zero.
822 for (; next_end < num_tasks && by_emin[next_end].time == time;
823 ++next_end) {
824 const int t = by_emin[next_end].task_index;
825 if (!helper->IsPresent(t)) continue;
826 if (added_demand[t] > 0) {
827 current_height -= added_demand[t];
828 added_demand[t] = 0;
829 } else {
830 // Corner case if task is of duration zero.
831 added_demand[t] = -1;
832 }
833 }
834
835 // Add new task starting here.
836 // If the task cannot be added we have a candidate for precedence.
837 // TODO(user): tie-break tasks not fitting in the profile smartly.
838 for (; next_start < num_tasks && by_smin[next_start].time == time;
839 ++next_start) {
840 const int t = by_smin[next_start].task_index;
841 if (!helper->IsPresent(t)) continue;
842 if (added_demand[t] == -1) continue; // Corner case.
843 const IntegerValue demand_min = h.demand_helper->DemandMin(t);
844 if (current_height + demand_min <= capacity_max) {
845 added_demand[t] = demand_min;
846 current_height += demand_min;
847 } else if (first_skipped_task == -1) {
848 // We should have everything needed here to add a new precedence.
849 first_skipped_task = t;
850 found = true;
851 break;
852 }
853 }
854 }
855
856 // If packing everything to the left is feasible, continue.
857 if (first_skipped_task == -1) {
858 continue;
859 }
860
861 // We will use a bunch of heuristic to add a new precedence. All the task
862 // in open_tasks cannot share a time point since they exceed the capacity.
863 // Moreover if we pack all to the left, they have an intersecting point.
864 // So we should be able to make two of them disjoint
865 std::vector<int> open_tasks;
866 for (int t = 0; t < num_tasks; ++t) {
867 if (added_demand[t] <= 0) continue;
868 open_tasks.push_back(t);
869 }
870 open_tasks.push_back(first_skipped_task);
871
872 // TODO(user): If the two box cannot overlap because of high demand, use
873 // repo.CreateDisjunctivePrecedenceLiteralIfNonTrivial() instead.
874 //
875 // TODO(user): Add heuristic ordering for creating interesting precedence
876 // first.
877 bool found_precedence_to_add = false;
878 helper->ResetReason();
879 for (const int s : open_tasks) {
880 for (const int t : open_tasks) {
881 if (s == t) continue;
882
883 // Make sure s could be before t.
884 if (helper->TaskIsBeforeOrIsOverlapping(t, s)) {
885 helper->AddReasonForBeingBeforeAssumingNoOverlap(t, s);
886 continue;
887 }
888
889 // Can we add s <= t ?
890 // All the considered tasks are intersecting if on the left.
891 CHECK_LT(helper->StartMin(s), helper->EndMin(t));
892 CHECK_LT(helper->StartMin(t), helper->EndMin(s));
893
894 // skip if we already have a literal created and assigned to false.
895 const LiteralIndex existing = repo->GetPrecedenceLiteral(
896 helper->Ends()[s], helper->Starts()[t]);
897 if (existing != kNoLiteralIndex) {
898 if (trail->Assignment().LiteralIsTrue(Literal(existing))) {
899 // In normal circumstances, this shouldn't be able to be true here
900 // otherwise we will have s and t disjoint. That said, this can
901 // happen if we are not in the propagation fixed point.
903 }
904
905 // This should always be true in normal usage after SAT search has
906 // fixed all literal, but if it is not, we can just return this
907 // decision.
908 if (trail->Assignment().LiteralIsFalse(Literal(existing))) {
909 helper->AddLiteralReason(Literal(existing));
910 continue;
911 }
912 } else {
913 // It shouldn't be able to fail since s can be before t.
914 CHECK(repo->CreatePrecedenceLiteralIfNonTrivial(
915 helper->Ends()[s], helper->Starts()[t]));
916 }
917
918 // Branch on that precedence.
919 best_helper = helper;
920 best_before = s;
921 best_after = t;
922 found_precedence_to_add = true;
923 break;
924 }
925 if (found_precedence_to_add) break;
926 }
927 if (found_precedence_to_add) break;
928
929 // If no precedence can be created, and all precedence are assigned to
930 // false we have a conflict since all these interval must intersect but
931 // cannot fit in the capacity!
932 //
933 // TODO(user): We need to add the reason for demand_min and capacity_max.
934 if (!h.capacity.IsConstant()) {
935 helper->AddIntegerReason(
936 integer_trail->UpperBoundAsLiteral(h.capacity));
937 }
938 const auto& demands = h.demand_helper->Demands();
939 for (const int t : open_tasks) {
940 if (helper->IsOptional(t)) {
941 CHECK(trail->Assignment().LiteralIsTrue(helper->PresenceLiteral(t)));
942 helper->AddLiteralReason(helper->PresenceLiteral(t).Negated());
943 }
944 const AffineExpression d = demands[t];
945 if (!d.IsConstant()) {
946 helper->AddIntegerReason(integer_trail->LowerBoundAsLiteral(d));
947 }
948 }
949 (void)helper->ReportConflict();
950 search_helper->NotifyThatConflictWasFoundDuringGetDecision();
951 if (VLOG_IS_ON(2)) {
952 LOG(INFO) << "Conflict between precedences !";
953 for (const int t : open_tasks) LOG(INFO) << helper->TaskDebugString(t);
954 }
956 }
957
958 // TODO(user): add heuristic criteria, right now we stop at first
959 // one. See above.
960 if (best_helper != nullptr) {
961 VLOG(2) << "New precedence: " << best_helper->TaskDebugString(best_before)
962 << " " << best_helper->TaskDebugString(best_after);
963 const AffineExpression end_a = best_helper->Ends()[best_before];
964 const AffineExpression start_b = best_helper->Starts()[best_after];
965 repo->CreatePrecedenceLiteralIfNonTrivial(end_a, start_b);
967 repo->GetPrecedenceLiteral(end_a, start_b));
968 }
969
971 };
972}
973
975 bool lns_mode, Model* model) {
976 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
977 SatDecisionPolicy* decision_policy = model->GetOrCreate<SatDecisionPolicy>();
978 SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
979
980 // TODO(user): Add other policies and perform more experiments.
981 std::function<BooleanOrIntegerLiteral()> sat_policy =
982 SatSolverHeuristic(model);
983 std::vector<std::function<BooleanOrIntegerLiteral()>> policies;
984 std::vector<double> weights;
985
986 // Add sat search + fixed_search (to complete the search).
987 policies.push_back(SequentialSearch({sat_policy, heuristics.fixed_search}));
988 weights.push_back(5);
989
990 // Adds user defined search if present.
991 if (heuristics.user_search != nullptr) {
992 policies.push_back(SequentialSearch(
993 {heuristics.user_search, sat_policy, heuristics.fixed_search}));
994 weights.push_back(1);
995 }
996
997 // Add model based heuristic search if present.
998 if (heuristics.heuristic_search != nullptr) {
999 policies.push_back(
1000 SequentialSearch({heuristics.heuristic_search, sat_policy,
1001 heuristics.integer_completion_search}));
1002 weights.push_back(1);
1003 } else if (heuristics.user_search == nullptr) {
1004 // Add pseudo cost search if nothing else is present.
1005 policies.push_back(SequentialSearch(
1006 {PseudoCost(model), sat_policy, heuristics.integer_completion_search}));
1007 weights.push_back(1);
1008 }
1009
1010 // The higher weight for the sat policy is because this policy actually
1011 // contains a lot of variation as we randomize the sat parameters.
1012 // TODO(user): Do more experiments to find better distribution.
1013 std::discrete_distribution<int> var_dist(weights.begin(), weights.end());
1014
1015 // Value selection.
1016 std::vector<std::function<IntegerLiteral(IntegerVariable)>>
1017 value_selection_heuristics;
1018 std::vector<int> value_selection_weight;
1019
1020 // LP Based value.
1021 const int linearization_level =
1022 model->GetOrCreate<SatParameters>()->linearization_level();
1023 if (LinearizedPartIsLarge(model)) {
1024 value_selection_heuristics.push_back([model](IntegerVariable var) {
1025 return SplitAroundLpValue(PositiveVariable(var), model);
1026 });
1027 value_selection_weight.push_back(linearization_level == 2 ? 4 : 2);
1028 }
1029
1030 // Solution based value.
1031 if (!lns_mode) {
1032 auto* response_manager = model->Get<SharedResponseManager>();
1033 CHECK(response_manager != nullptr);
1034 value_selection_heuristics.push_back(
1035 [model, response_manager](IntegerVariable var) {
1037 var, response_manager->SolutionPool().BestSolutions(), model);
1038 });
1039 value_selection_weight.push_back(5);
1040 }
1041
1042 // Min value.
1043 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1044 value_selection_heuristics.push_back([integer_trail](IntegerVariable var) {
1045 return AtMinValue(var, integer_trail);
1046 });
1047 value_selection_weight.push_back(1);
1048
1049 // Special case: Don't change the decision value.
1050 value_selection_weight.push_back(10);
1051
1052 // TODO(user): These distribution values are just guessed values. They
1053 // need to be tuned.
1054 std::discrete_distribution<int> val_dist(value_selection_weight.begin(),
1055 value_selection_weight.end());
1056
1057 int policy_index = 0;
1058 int val_policy_index = 0;
1059 auto* encoder = model->GetOrCreate<IntegerEncoder>();
1060 auto* objective = model->Get<ObjectiveDefinition>();
1061 return [=]() mutable {
1062 if (sat_solver->CurrentDecisionLevel() == 0) {
1063 auto* random = model->GetOrCreate<ModelRandomGenerator>();
1065 decision_policy->ResetDecisionHeuristic();
1066
1067 // Set some assignment preference.
1068 // TODO(user): Also use LP value as assignment like in Bop.
1069 if (objective != nullptr && absl::Bernoulli(*random, 0.2)) {
1070 // Use Boolean objective as assignment preference.
1071 IntegerValue max_abs_weight = 0;
1072 for (const IntegerValue coeff : objective->coeffs) {
1073 max_abs_weight = std::max(max_abs_weight, IntTypeAbs(coeff));
1074 }
1075 const double max_abs_weight_double = ToDouble(max_abs_weight);
1076
1077 const int objective_size = objective->vars.size();
1078 for (int i = 0; i < objective_size; ++i) {
1079 const IntegerVariable var = objective->vars[i];
1080 if (integer_trail->LowerBound(var) != 0) continue;
1081 if (integer_trail->UpperBound(var) != 1) continue;
1082 const LiteralIndex index = encoder->GetAssociatedLiteral(
1084 if (index == kNoLiteralIndex) continue;
1085
1086 const Literal literal(index);
1087 const IntegerValue coeff = objective->coeffs[i];
1088 const double abs_weight =
1089 std::abs(ToDouble(objective->coeffs[i])) / max_abs_weight_double;
1090
1091 // Because this is a minimization problem, we prefer to assign a
1092 // Boolean variable to its "low" objective value. So if a literal
1093 // has a positive weight when true, we want to set it to false.
1094 decision_policy->SetAssignmentPreference(
1095 coeff > 0 ? literal.Negated() : literal, abs_weight);
1096 }
1097 }
1098
1099 // Select the variable selection heuristic.
1100 policy_index = var_dist(*(random));
1101
1102 // Select the value selection heuristic.
1103 val_policy_index = val_dist(*(random));
1104 }
1105
1106 // Get the current decision.
1107 const BooleanOrIntegerLiteral current_decision = policies[policy_index]();
1108 if (!current_decision.HasValue()) return current_decision;
1109
1110 // Special case: Don't override the decision value.
1111 if (val_policy_index >= value_selection_heuristics.size()) {
1112 return current_decision;
1113 }
1114
1115 if (current_decision.boolean_literal_index == kNoLiteralIndex) {
1116 const IntegerLiteral new_decision =
1117 value_selection_heuristics[val_policy_index](
1118 current_decision.integer_literal.var);
1119 if (new_decision.IsValid()) return BooleanOrIntegerLiteral(new_decision);
1120 return current_decision;
1121 }
1122
1123 // Decode the decision and get the variable.
1124 for (const IntegerVariable var : encoder->GetAllAssociatedVariables(
1125 Literal(current_decision.boolean_literal_index))) {
1126 // Try the selected policy.
1127 const IntegerLiteral new_decision =
1128 value_selection_heuristics[val_policy_index](var);
1129 if (new_decision.IsValid()) return BooleanOrIntegerLiteral(new_decision);
1130 }
1131
1132 // Selected policy failed. Revert back to original decision.
1133 return current_decision;
1134 };
1135}
1136
1138 absl::Span<const BooleanOrIntegerVariable> vars,
1139 absl::Span<const IntegerValue> values, Model* model) {
1140 auto* trail = model->GetOrCreate<Trail>();
1141 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1142 auto* rev_int_repo = model->GetOrCreate<RevIntRepository>();
1143
1144 // This is not ideal as we reserve an int for the full duration of the model
1145 // even if we use this FollowHint() function just for a while. But it is
1146 // an easy solution to not have reference to deleted memory in the
1147 // RevIntRepository(). Note that once we backtrack, these reference will
1148 // disappear.
1149 int* rev_start_index = model->TakeOwnership(new int);
1150 *rev_start_index = 0;
1151
1152 return [=,
1153 vars =
1154 std::vector<BooleanOrIntegerVariable>(vars.begin(), vars.end()),
1155 values = std::vector<IntegerValue>(values.begin(), values.end())]() {
1156 rev_int_repo->SaveState(rev_start_index);
1157 for (int i = *rev_start_index; i < vars.size(); ++i) {
1158 const IntegerValue value = values[i];
1159 if (vars[i].bool_var != kNoBooleanVariable) {
1160 if (trail->Assignment().VariableIsAssigned(vars[i].bool_var)) continue;
1161
1162 // If we retake a decision at this level, we will restart from i.
1163 *rev_start_index = i;
1165 Literal(vars[i].bool_var, value == 1).Index());
1166 } else {
1167 const IntegerVariable integer_var = vars[i].int_var;
1168 if (integer_trail->IsFixed(integer_var)) continue;
1169
1170 const IntegerVariable positive_var = PositiveVariable(integer_var);
1171 const IntegerLiteral decision = SplitAroundGivenValue(
1172 positive_var, positive_var != integer_var ? -value : value, model);
1173 if (decision.IsValid()) {
1174 // If we retake a decision at this level, we will restart from i.
1175 *rev_start_index = i;
1176 return BooleanOrIntegerLiteral(decision);
1177 }
1178
1179 // If the value is outside the current possible domain, we skip it.
1180 continue;
1181 }
1182 }
1183 return BooleanOrIntegerLiteral();
1184 };
1185}
1186
1187std::function<bool()> RestartEveryKFailures(int k, SatSolver* solver) {
1188 bool reset_at_next_call = true;
1189 int next_num_failures = 0;
1190 return [=]() mutable {
1191 if (reset_at_next_call) {
1192 next_num_failures = solver->num_failures() + k;
1193 reset_at_next_call = false;
1194 } else if (solver->num_failures() >= next_num_failures) {
1195 reset_at_next_call = true;
1196 }
1197 return reset_at_next_call;
1198 };
1199}
1200
1201std::function<bool()> SatSolverRestartPolicy(Model* model) {
1202 auto policy = model->GetOrCreate<RestartPolicy>();
1203 return [policy]() { return policy->ShouldRestart(); };
1204}
1205
1206namespace {
1207
1208std::function<BooleanOrIntegerLiteral()> WrapIntegerLiteralHeuristic(
1209 std::function<IntegerLiteral()> f) {
1210 return [f]() { return BooleanOrIntegerLiteral(f()); };
1211}
1212
1213} // namespace
1214
1216 SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
1217 CHECK(heuristics.fixed_search != nullptr);
1218 heuristics.policy_index = 0;
1219 heuristics.decision_policies.clear();
1220 heuristics.restart_policies.clear();
1221
1222 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1223 switch (parameters.search_branching()) {
1225 std::function<BooleanOrIntegerLiteral()> decision_policy =
1228 {SatSolverHeuristic(model), heuristics.fixed_search}),
1229 model);
1230 if (parameters.use_objective_lb_search()) {
1231 heuristics.decision_policies = {
1232 SequentialSearch({ShaveObjectiveLb(model), decision_policy})};
1233 } else {
1234 heuristics.decision_policies = {decision_policy};
1235 }
1236 heuristics.restart_policies = {SatSolverRestartPolicy(model)};
1237 return;
1238 }
1240 // Not all Boolean might appear in fixed_search(), so once there is no
1241 // decision left, we fix all Booleans that are still undecided.
1242 heuristics.decision_policies = {SequentialSearch(
1243 {heuristics.fixed_search, SatSolverHeuristic(model)})};
1244
1245 if (parameters.randomize_search()) {
1246 heuristics.restart_policies = {SatSolverRestartPolicy(model)};
1247 return;
1248 }
1249
1250 // TODO(user): We might want to restart if external info is available.
1251 // Code a custom restart for this?
1252 auto no_restart = []() { return false; };
1253 heuristics.restart_policies = {no_restart};
1254 return;
1255 }
1257 // Push user search if present.
1258 if (heuristics.user_search != nullptr) {
1259 heuristics.decision_policies.push_back(
1261 heuristics.fixed_search}));
1262 }
1263
1264 // Do a portfolio with the default sat heuristics.
1265 heuristics.decision_policies.push_back(SequentialSearch(
1266 {SatSolverHeuristic(model), heuristics.fixed_search}));
1267
1268 // Use default restart policies.
1269 heuristics.restart_policies.assign(heuristics.decision_policies.size(),
1270 SatSolverRestartPolicy(model));
1271 return;
1272 }
1274 CHECK(heuristics.hint_search != nullptr);
1275 heuristics.decision_policies = {
1277 heuristics.fixed_search})};
1278 auto no_restart = []() { return false; };
1279 heuristics.restart_policies = {no_restart};
1280 return;
1281 }
1283 heuristics.decision_policies = {
1284 RandomizeOnRestartHeuristic(/*lns_mode=*/true, model)};
1285 heuristics.restart_policies = {SatSolverRestartPolicy(model)};
1286 return;
1287 }
1289 std::vector<std::function<BooleanOrIntegerLiteral()>> lp_heuristics;
1290 for (const auto& ct :
1292 lp_heuristics.push_back(WrapIntegerLiteralHeuristic(
1293 ct->HeuristicLpReducedCostAverageBranching()));
1294 }
1295 if (lp_heuristics.empty()) { // Revert to fixed search.
1296 heuristics.decision_policies = {SequentialSearch(
1297 {heuristics.fixed_search, SatSolverHeuristic(model)})},
1298 heuristics.restart_policies = {SatSolverRestartPolicy(model)};
1299 return;
1300 }
1302 lp_heuristics, IntegerValueSelectionHeuristic(
1304 heuristics.fixed_search}),
1305 model));
1306 heuristics.restart_policies.assign(heuristics.decision_policies.size(),
1307 SatSolverRestartPolicy(model));
1308 return;
1309 }
1311 std::function<BooleanOrIntegerLiteral()> search =
1313 heuristics.fixed_search});
1314 heuristics.decision_policies = {
1315 IntegerValueSelectionHeuristic(search, model)};
1316 heuristics.restart_policies = {SatSolverRestartPolicy(model)};
1317 return;
1318 }
1320 std::function<BooleanOrIntegerLiteral()> search = SequentialSearch(
1321 {RandomizeOnRestartHeuristic(/*lns_mode=*/false, model),
1322 heuristics.fixed_search});
1323 heuristics.decision_policies = {search};
1324 heuristics.restart_policies = {
1326 return;
1327 }
1329 heuristics.decision_policies = {
1330 RandomizeOnRestartHeuristic(/*lns_mode=*/false, model)};
1331 heuristics.restart_policies = {SatSolverRestartPolicy(model)};
1332 return;
1333 }
1334 }
1335}
1336
1337std::vector<std::function<BooleanOrIntegerLiteral()>> CompleteHeuristics(
1338 absl::Span<const std::function<BooleanOrIntegerLiteral()>>
1339 incomplete_heuristics,
1340 const std::function<BooleanOrIntegerLiteral()>& completion_heuristic) {
1341 std::vector<std::function<BooleanOrIntegerLiteral()>> complete_heuristics;
1342 complete_heuristics.reserve(incomplete_heuristics.size());
1343 for (const auto& incomplete : incomplete_heuristics) {
1344 complete_heuristics.push_back(
1345 SequentialSearch({incomplete, completion_heuristic}));
1346 }
1347 return complete_heuristics;
1348}
1349
1351 : parameters_(*model->GetOrCreate<SatParameters>()),
1352 model_(model),
1353 sat_solver_(model->GetOrCreate<SatSolver>()),
1354 binary_implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
1355 integer_trail_(model->GetOrCreate<IntegerTrail>()),
1356 encoder_(model->GetOrCreate<IntegerEncoder>()),
1357 implied_bounds_(model->GetOrCreate<ImpliedBounds>()),
1358 prober_(model->GetOrCreate<Prober>()),
1359 product_detector_(model->GetOrCreate<ProductDetector>()),
1360 time_limit_(model->GetOrCreate<TimeLimit>()),
1361 pseudo_costs_(model->GetOrCreate<PseudoCosts>()),
1362 inprocessing_(model->GetOrCreate<Inprocessing>()) {}
1363
1365 DCHECK(sat_solver_->PropagationIsDone());
1366
1367 // If we pushed root level deductions, we go back to level zero and call
1368 // Propagate() to incorporate them. Note that the propagation is not strictly
1369 // needed, but it is nicer to be at fixed point when we call the level zero
1370 // callbacks.
1371 if (integer_trail_->HasPendingRootLevelDeduction()) {
1372 sat_solver_->Backtrack(0);
1373 if (!sat_solver_->Propagate()) {
1374 // This adds the UNSAT proof to the LRAT handler, if any.
1375 sat_solver_->ProcessCurrentConflict();
1376 sat_solver_->NotifyThatModelIsUnsat();
1377 return false;
1378 }
1379 }
1380
1381 // The rest only trigger at level zero.
1382 if (sat_solver_->CurrentDecisionLevel() != 0) return true;
1383
1384 // Rather than doing it in each callback, we detect newly fixed variables or
1385 // tighter bounds, and propagate just once when everything was added.
1386 const int saved_bool_index = sat_solver_->LiteralTrail().Index();
1387 const int saved_integer_index = integer_trail_->num_enqueues();
1388
1389 auto* level_zero_callbacks = model_->GetOrCreate<LevelZeroCallbackHelper>();
1390 for (const auto& cb : level_zero_callbacks->callbacks) {
1391 if (!cb()) {
1392 sat_solver_->NotifyThatModelIsUnsat();
1393 return false;
1394 }
1395 }
1396
1397 // We propagate if needed.
1398 if (sat_solver_->LiteralTrail().Index() > saved_bool_index ||
1399 integer_trail_->num_enqueues() > saved_integer_index ||
1400 integer_trail_->HasPendingRootLevelDeduction()) {
1401 if (!sat_solver_->Propagate()) {
1402 // This adds the UNSAT proof to the LRAT handler, if any.
1403 sat_solver_->ProcessCurrentConflict();
1404 sat_solver_->NotifyThatModelIsUnsat();
1405 return false;
1406 }
1407 }
1408 DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0)
1409 << "Level zero callback left decisions on the trail ?";
1410
1411 // We apply inprocessing if we have budget.
1412 if (parameters_.use_sat_inprocessing() &&
1413 !inprocessing_->InprocessingRound()) {
1414 sat_solver_->NotifyThatModelIsUnsat();
1415 return false;
1416 }
1417 DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0)
1418 << "Inprocessing left decisions on the trail ?";
1419
1420 // Finally, once all the root-level work has been done, if we have
1421 // assumptions, we re-add them on the first decision level and continue
1422 // from there.
1423 return sat_solver_->ReapplyAssumptionsIfNeeded();
1424}
1425
1427 const BooleanOrIntegerLiteral& decision) {
1428 DCHECK(decision.HasValue());
1429
1430 // Convert integer decision to literal one if needed.
1431 //
1432 // TODO(user): Ideally it would be cool to delay the creation even more
1433 // until we have a conflict with these decisions, but it is currently
1434 // hard to do so.
1435 const Literal literal =
1437 ? Literal(decision.boolean_literal_index)
1438 : encoder_->GetOrCreateAssociatedLiteral(decision.integer_literal);
1439 if (sat_solver_->Assignment().LiteralIsAssigned(literal)) {
1440 // TODO(user): It would be nicer if this can never happen. For now, it
1441 // does because of the Propagate() not reaching the fixed point as
1442 // mentioned in a TODO above. As a work-around, we display a message
1443 // but do not crash and recall the decision heuristic.
1444 VLOG(1) << "Trying to take a decision that is already assigned!"
1445 << " Fix this. Continuing for now...";
1446 return kNoLiteralIndex;
1447 }
1448 return literal.Index();
1449}
1450
1452 const std::function<BooleanOrIntegerLiteral()>& f, LiteralIndex* decision) {
1453 *decision = kNoLiteralIndex;
1454 do {
1455 BooleanOrIntegerLiteral new_decision;
1456 if (integer_trail_->InPropagationLoop()) {
1457 const IntegerVariable var =
1458 integer_trail_->NextVariableToBranchOnInPropagationLoop();
1459 if (var != kNoIntegerVariable) {
1460 new_decision.integer_literal =
1461 GreaterOrEqualToMiddleValue(var, integer_trail_);
1462 }
1463 }
1464 if (!new_decision.HasValue()) {
1465 new_decision = f();
1466 if (must_process_conflict_) {
1467 must_process_conflict_ = false;
1468 sat_solver_->ProcessCurrentConflict();
1469 (void)sat_solver_->FinishPropagation();
1470 return false;
1471 }
1472 }
1473 if (!new_decision.HasValue() &&
1474 integer_trail_->CurrentBranchHadAnIncompletePropagation()) {
1475 const IntegerVariable var = integer_trail_->FirstUnassignedVariable();
1476 if (var != kNoIntegerVariable) {
1477 new_decision.integer_literal = AtMinValue(var, integer_trail_);
1478 }
1479 }
1480 if (!new_decision.HasValue()) break;
1481
1482 *decision = GetDecisionLiteral(new_decision);
1483 if (*decision != kNoLiteralIndex) break;
1484 } while (!time_limit_->LimitReached());
1485 return true;
1486}
1487
1489 bool use_representative) {
1490 // If we are about to take a decision on a redundant literal, always
1491 // prefer to branch on the representative. This should helps learn more
1492 // consistent conflict.
1493 //
1494 // TODO(user): Ideally never learn anything on redundant variable. This is
1495 // a bit of work.
1496 if (use_representative) {
1497 decision = binary_implication_graph_->RepresentativeOf(decision);
1498 CHECK(!sat_solver_->Assignment().LiteralIsAssigned(decision));
1499 }
1500
1501 pseudo_costs_->BeforeTakingDecision(decision);
1502
1503 // Note that kUnsatTrailIndex might also mean ASSUMPTIONS_UNSAT.
1504 //
1505 // TODO(user): on some problems, this function can be quite long. Expand
1506 // so that we can check the time limit at each step?
1507 const int old_level = sat_solver_->CurrentDecisionLevel();
1508 const int index = sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision);
1509 if (index == kUnsatTrailIndex) return false;
1510
1511 // Update the implied bounds each time we enqueue a literal at level zero.
1512 // This is "almost free", so we might as well do it.
1513 if (old_level == 0 && sat_solver_->CurrentDecisionLevel() == 1) {
1514 if (!implied_bounds_->ProcessIntegerTrail(decision)) return false;
1515 product_detector_->ProcessTrailAtLevelOne();
1516 }
1517
1518 // Update the pseudo costs.
1519 pseudo_costs_->AfterTakingDecision(
1520 /*conflict=*/sat_solver_->CurrentDecisionLevel() <= old_level);
1521
1522 sat_solver_->AdvanceDeterministicTime(time_limit_);
1523 return sat_solver_->ReapplyAssumptionsIfNeeded();
1524}
1525
1527 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
1528
1529 SearchHeuristics& heuristics = *model_->GetOrCreate<SearchHeuristics>();
1530 const int num_policies = heuristics.decision_policies.size();
1531 CHECK_NE(num_policies, 0);
1532 CHECK_EQ(num_policies, heuristics.restart_policies.size());
1533
1534 // Note that it is important to do the level-zero propagation if it wasn't
1535 // already done because EnqueueDecisionAndBackjumpOnConflict() assumes that
1536 // the solver is in a "propagated" state.
1537 //
1538 // TODO(user): We have the issue that at level zero. calling the propagation
1539 // loop more than once can propagate more! This is because we call the LP
1540 // again and again on each level zero propagation. This is causing some
1541 // CHECKs() to fail in multithread (rarely) because when we associate new
1542 // literals to integer ones, Propagate() is indirectly called. Not sure yet
1543 // how to fix.
1544 if (!sat_solver_->FinishPropagation()) return sat_solver_->UnsatStatus();
1545
1546 // Main search loop.
1547 const int64_t old_num_conflicts = sat_solver_->num_failures();
1548 const int64_t conflict_limit = parameters_.max_number_of_conflicts();
1549 int64_t num_decisions_since_last_lp_record_ = 0;
1550 while (!time_limit_->LimitReached() &&
1551 (sat_solver_->num_failures() - old_num_conflicts < conflict_limit)) {
1552 // If needed, restart and switch decision_policy.
1553 if (heuristics.restart_policies[heuristics.policy_index]()) {
1554 // Note that in the presence of assumptions, BeforeTakingDecision()
1555 // will make sure to restore them. On restart, we always call Propagate()
1556 // as we might want to spent more effort on the root level LP relaxation
1557 // for instance.
1558 sat_solver_->IncreaseNumRestarts();
1559 sat_solver_->Backtrack(0);
1560 if (!sat_solver_->FinishPropagation()) {
1561 return sat_solver_->UnsatStatus();
1562 }
1563
1564 heuristics.policy_index = (heuristics.policy_index + 1) % num_policies;
1565 }
1566
1567 if (!BeforeTakingDecision()) return sat_solver_->UnsatStatus();
1568
1569 LiteralIndex decision = kNoLiteralIndex;
1570 while (true) {
1571 if (sat_solver_->ModelIsUnsat()) return sat_solver_->UnsatStatus();
1572 if (heuristics.next_decision_override != nullptr) {
1573 // Note that to properly count the num_times, we do not want to move
1574 // this function, but actually call that copy.
1575 if (!GetDecision(heuristics.next_decision_override, &decision)) {
1576 continue;
1577 }
1578 if (decision == kNoLiteralIndex) {
1579 heuristics.next_decision_override = nullptr;
1580 }
1581 }
1582 if (decision == kNoLiteralIndex) {
1583 if (!GetDecision(heuristics.decision_policies[heuristics.policy_index],
1584 &decision)) {
1585 continue;
1586 }
1587 }
1588 break;
1589 }
1590
1591 // No decision means that we reached a leave of the search tree and that
1592 // we have a feasible solution.
1593 //
1594 // Tricky: If the time limit is reached during the final propagation when
1595 // all variables are fixed, there is no guarantee that the propagation
1596 // responsible for testing the validity of the solution was run to
1597 // completion. So we cannot report a feasible solution.
1598 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
1599 if (decision == kNoLiteralIndex) {
1600 // Save the current polarity of all Booleans in the solution. It will be
1601 // followed for the next SAT decisions. This is known to be a good policy
1602 // for optimization problem. Note that for decision problem we don't care
1603 // since we are just done as soon as a solution is found.
1604 //
1605 // This idea is kind of "well known", see for instance the "LinSBPS"
1606 // submission to the maxSAT 2018 competition by Emir Demirovic and Peter
1607 // Stuckey where they show it is a good idea and provide more references.
1608 if (parameters_.use_optimization_hints()) {
1609 auto* sat_decision = model_->GetOrCreate<SatDecisionPolicy>();
1610 const auto& trail = *model_->GetOrCreate<Trail>();
1611 for (int i = 0; i < trail.Index(); ++i) {
1612 sat_decision->SetAssignmentPreference(trail[i], 0.0);
1613 }
1614 }
1615 return SatSolver::FEASIBLE;
1616 }
1617
1618 if (!TakeDecision(Literal(decision))) {
1619 return sat_solver_->UnsatStatus();
1620 }
1621
1622 // In multi-thread, we really only want to save the LP relaxation for thread
1623 // with high linearization level to avoid to pollute the repository with
1624 // sub-par lp solutions.
1625 //
1626 // TODO(user): Experiment more around dynamically changing the
1627 // threshold for storing LP solutions in the pool. Alternatively expose
1628 // this as parameter so this can be tuned later.
1629 //
1630 // TODO(user): Avoid adding the same solution many time if the LP didn't
1631 // change. Avoid adding solution that are too deep in the tree (most
1632 // variable fixed). Also use a callback rather than having this here, we
1633 // don't want this file to depend on cp_model.proto.
1634 if (model_->Get<SharedLPSolutionRepository>() != nullptr &&
1635 parameters_.linearization_level() >= 2) {
1636 num_decisions_since_last_lp_record_++;
1637 if (num_decisions_since_last_lp_record_ >= 100) {
1638 // NOTE: We can actually record LP solutions more frequently. However
1639 // this process is time consuming and workers waste a lot of time doing
1640 // this. To avoid this we don't record solutions after each decision.
1642 num_decisions_since_last_lp_record_ = 0;
1643 }
1644 }
1645 }
1647}
1648
1650 const std::vector<Literal>& assumptions, Model* model) {
1651 // Backtrack to level zero.
1652 auto* sat_solver = model->GetOrCreate<SatSolver>();
1653 if (!sat_solver->ResetToLevelZero()) {
1654 return sat_solver->UnsatStatus();
1655 }
1656
1657 // Sync bounds and maybe do some inprocessing.
1658 // We reuse the BeforeTakingDecision() code
1659 auto* helper = model->GetOrCreate<IntegerSearchHelper>();
1660 DCHECK(sat_solver->PropagationIsDone());
1661 if (!helper->BeforeTakingDecision()) {
1662 return sat_solver->UnsatStatus();
1663 }
1664
1665 // Add the assumptions if any and solve.
1666 if (!sat_solver->ResetWithGivenAssumptions(assumptions)) {
1667 return sat_solver->UnsatStatus();
1668 }
1669
1670 return helper->SolveIntegerProblem();
1671}
1672
1674 const IntegerVariable num_vars =
1675 model->GetOrCreate<IntegerTrail>()->NumIntegerVariables();
1676 std::vector<IntegerVariable> all_variables;
1677 for (IntegerVariable var(0); var < num_vars; ++var) {
1678 all_variables.push_back(var);
1679 }
1680
1681 SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
1682 heuristics.policy_index = 0;
1683 heuristics.decision_policies = {SequentialSearch(
1684 {SatSolverHeuristic(model),
1685 FirstUnassignedVarAtItsMinHeuristic(all_variables, model)})};
1686 heuristics.restart_policies = {SatSolverRestartPolicy(model)};
1687 return ResetAndSolveIntegerProblem(/*assumptions=*/{}, model);
1688}
1689
1690#define RETURN_IF_NOT_FEASIBLE(test) \
1691 const SatSolver::Status status = (test); \
1692 if (status != SatSolver::FEASIBLE) return status;
1693
1695 Model* model)
1696 : model_(model),
1697 sat_solver_(model->GetOrCreate<SatSolver>()),
1698 time_limit_(model->GetOrCreate<TimeLimit>()),
1699 binary_implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
1700 clause_manager_(model->GetOrCreate<ClauseManager>()),
1701 trail_(model->GetOrCreate<Trail>()),
1702 integer_trail_(model->GetOrCreate<IntegerTrail>()),
1703 encoder_(model->GetOrCreate<IntegerEncoder>()),
1704 inprocessing_(model->GetOrCreate<Inprocessing>()),
1705 parameters_(*(model->GetOrCreate<SatParameters>())),
1706 level_zero_callbacks_(model->GetOrCreate<LevelZeroCallbackHelper>()),
1707 prober_(model->GetOrCreate<Prober>()),
1708 shared_response_manager_(model->Mutable<SharedResponseManager>()),
1709 shared_bounds_manager_(model->Mutable<SharedBoundsManager>()),
1710 random_(model->GetOrCreate<ModelRandomGenerator>()),
1711 active_limit_(parameters_.shaving_search_deterministic_time()) {
1712 auto* mapping = model_->GetOrCreate<CpModelMapping>();
1713 absl::flat_hash_set<BooleanVariable> visited;
1714
1715 trail_index_at_start_of_iteration_ = trail_->Index();
1716 integer_trail_index_at_start_of_iteration_ = integer_trail_->Index();
1717
1718 // Build variable lists.
1719 // TODO(user): Ideally, we should scan the internal model. But there can be
1720 // a large blowup of variables during loading, which slows down the probing
1721 // part. Using model variables is a good heuristic to select 'impactful'
1722 // Boolean variables.
1723 for (int v = 0; v < model_proto.variables_size(); ++v) {
1724 if (mapping->IsBoolean(v)) {
1725 const BooleanVariable bool_var = mapping->Literal(v).Variable();
1726 const auto [_, inserted] = visited.insert(bool_var);
1727 if (inserted) {
1728 bool_vars_.push_back(bool_var);
1729 }
1730 } else {
1731 IntegerVariable var = mapping->Integer(v);
1732 if (integer_trail_->IsFixed(var)) continue;
1733 int_vars_.push_back(var);
1734 }
1735 }
1736
1737 VLOG(2) << "Start continuous probing with " << bool_vars_.size()
1738 << " Boolean variables, " << int_vars_.size()
1739 << " integer variables, deterministic time limit = "
1740 << time_limit_->GetDeterministicLimit() << " on " << model_->Name();
1741}
1742
1743// Continuous probing procedure.
1744// TODO(user):
1745// - sort variables before the iteration (statically or dynamically)
1746// - compress clause databases regularly (especially the implication graph)
1747// - better interleaving of the probing and shaving phases
1748// - move the shaving code directly in the probing class
1749// - probe all variables and not just the model ones
1751 // Backtrack to level 0 in case we are not there.
1752 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
1753
1754 while (!time_limit_->LimitReached()) {
1755 if (parameters_.use_sat_inprocessing() &&
1756 !inprocessing_->InprocessingRound()) {
1757 sat_solver_->NotifyThatModelIsUnsat();
1758 return sat_solver_->UnsatStatus();
1759 }
1760
1761 // Store current statistics to detect an iteration without any improvement.
1762 const int64_t initial_num_literals_fixed =
1763 prober_->num_new_literals_fixed();
1764 const int64_t initial_num_bounds_shaved = num_bounds_shaved_;
1765 const auto& assignment = sat_solver_->Assignment();
1766
1767 // Probe variable bounds.
1768 // TODO(user): Probe optional variables.
1769 for (; current_int_var_ < int_vars_.size(); ++current_int_var_) {
1770 const IntegerVariable int_var = int_vars_[current_int_var_];
1771 if (integer_trail_->IsFixed(int_var)) continue;
1772
1773 const Literal shave_lb_literal =
1774 encoder_->GetOrCreateAssociatedLiteral(IntegerLiteral::LowerOrEqual(
1775 int_var, integer_trail_->LowerBound(int_var)));
1776 const BooleanVariable shave_lb_var = shave_lb_literal.Variable();
1777 const auto [_lb, lb_inserted] = probed_bool_vars_.insert(shave_lb_var);
1778 if (lb_inserted) {
1779 if (!prober_->ProbeOneVariable(shave_lb_var)) {
1780 return SatSolver::INFEASIBLE;
1781 }
1782 num_literals_probed_++;
1783 }
1784
1785 const Literal shave_ub_literal =
1786 encoder_->GetOrCreateAssociatedLiteral(IntegerLiteral::GreaterOrEqual(
1787 int_var, integer_trail_->UpperBound(int_var)));
1788 const BooleanVariable shave_ub_var = shave_ub_literal.Variable();
1789 const auto [_ub, ub_inserted] = probed_bool_vars_.insert(shave_ub_var);
1790 if (ub_inserted) {
1791 if (!prober_->ProbeOneVariable(shave_ub_var)) {
1792 return SatSolver::INFEASIBLE;
1793 }
1794 num_literals_probed_++;
1795 }
1796
1797 if (use_shaving_) {
1798 const SatSolver::Status lb_status = ShaveLiteral(shave_lb_literal);
1799 if (ReportStatus(lb_status)) return lb_status;
1800
1801 const SatSolver::Status ub_status = ShaveLiteral(shave_ub_literal);
1802 if (ReportStatus(ub_status)) return ub_status;
1803 }
1804
1805 RETURN_IF_NOT_FEASIBLE(PeriodicSyncAndCheck());
1806 }
1807
1808 // Probe Boolean variables from the model.
1809 for (; current_bool_var_ < bool_vars_.size(); ++current_bool_var_) {
1810 const BooleanVariable& bool_var = bool_vars_[current_bool_var_];
1811
1812 if (assignment.VariableIsAssigned(bool_var)) continue;
1813
1814 const auto [_, inserted] = probed_bool_vars_.insert(bool_var);
1815 if (!inserted) continue;
1816
1817 if (!prober_->ProbeOneVariable(bool_var)) {
1818 return SatSolver::INFEASIBLE;
1819 }
1820 num_literals_probed_++;
1821
1822 const Literal literal(bool_var, true);
1823 if (use_shaving_ && !assignment.LiteralIsAssigned(literal)) {
1824 const SatSolver::Status true_status = ShaveLiteral(literal);
1825 if (ReportStatus(true_status)) return true_status;
1826 if (true_status == SatSolver::ASSUMPTIONS_UNSAT) continue;
1827
1828 const SatSolver::Status false_status = ShaveLiteral(literal.Negated());
1829 if (ReportStatus(false_status)) return false_status;
1830 }
1831
1832 RETURN_IF_NOT_FEASIBLE(PeriodicSyncAndCheck());
1833 }
1834
1835 if (parameters_.use_extended_probing()) {
1836 const auto at_least_one_literal_is_true =
1837 [&assignment](absl::Span<const Literal> literals) {
1838 for (const Literal literal : literals) {
1839 if (assignment.LiteralIsTrue(literal)) {
1840 return true;
1841 }
1842 }
1843 return false;
1844 };
1845
1846 // Probe clauses of the SAT model.
1847 for (;;) {
1848 const SatClause* clause = clause_manager_->NextClauseToProbe();
1849 if (clause == nullptr) break;
1850 if (at_least_one_literal_is_true(clause->AsSpan())) continue;
1851
1852 tmp_dnf_.clear();
1853 for (const Literal literal : clause->AsSpan()) {
1854 if (assignment.LiteralIsAssigned(literal)) continue;
1855 tmp_dnf_.push_back({literal});
1856 }
1857 ++num_at_least_one_probed_;
1858 if (!prober_->ProbeDnf("at_least_one", tmp_dnf_,
1860 return SatSolver::INFEASIBLE;
1861 }
1862
1863 RETURN_IF_NOT_FEASIBLE(PeriodicSyncAndCheck());
1864 }
1865
1866 // Probe at_most_ones of the SAT model.
1867 for (;;) {
1868 const absl::Span<const Literal> at_most_one =
1869 binary_implication_graph_->NextAtMostOne();
1870 if (at_most_one.empty()) break;
1871 if (at_least_one_literal_is_true(at_most_one)) continue;
1872
1873 tmp_dnf_.clear();
1874 tmp_literals_.clear();
1875 for (const Literal literal : at_most_one) {
1876 if (assignment.LiteralIsAssigned(literal)) continue;
1877 tmp_dnf_.push_back({literal});
1878 tmp_literals_.push_back(literal.Negated());
1879 }
1880 tmp_dnf_.push_back(tmp_literals_);
1881 ++num_at_most_one_probed_;
1882 if (!prober_->ProbeDnf("at_most_one", tmp_dnf_,
1884 return SatSolver::INFEASIBLE;
1885 }
1886
1887 RETURN_IF_NOT_FEASIBLE(PeriodicSyncAndCheck());
1888 }
1889
1890 // Probe combinations of Booleans variables.
1891 const int limit = parameters_.probing_num_combinations_limit();
1892 const int max_num_bool_vars_for_pairs_probing =
1893 static_cast<int>(std::sqrt(2 * limit));
1894 const int num_bool_vars = bool_vars_.size();
1895
1896 if (num_bool_vars < max_num_bool_vars_for_pairs_probing) {
1897 for (; current_bv1_ + 1 < bool_vars_.size(); ++current_bv1_) {
1898 const BooleanVariable bv1 = bool_vars_[current_bv1_];
1899 if (assignment.VariableIsAssigned(bv1)) continue;
1900 current_bv2_ = std::max(current_bv1_ + 1, current_bv2_);
1901 for (; current_bv2_ < bool_vars_.size(); ++current_bv2_) {
1902 const BooleanVariable& bv2 = bool_vars_[current_bv2_];
1903 if (assignment.VariableIsAssigned(bv2)) continue;
1904 if (!prober_->ProbeDnf("pair_of_bool_vars",
1905 {{Literal(bv1, false), Literal(bv2, false)},
1906 {Literal(bv1, false), Literal(bv2, true)},
1907 {Literal(bv1, true), Literal(bv2, false)},
1908 {Literal(bv1, true), Literal(bv2, true)}},
1910 return SatSolver::INFEASIBLE;
1911 }
1912 RETURN_IF_NOT_FEASIBLE(PeriodicSyncAndCheck());
1913 }
1914 current_bv2_ = 0;
1915 }
1916 } else {
1917 for (; random_pair_of_bool_vars_probed_ < 10000;
1918 ++random_pair_of_bool_vars_probed_) {
1919 const BooleanVariable bv1 =
1920 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1921 if (assignment.VariableIsAssigned(bv1)) continue;
1922 const BooleanVariable bv2 =
1923 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1924 if (assignment.VariableIsAssigned(bv2) || bv1 == bv2) {
1925 continue;
1926 }
1927 if (!prober_->ProbeDnf("rnd_pair_of_bool_vars",
1928 {{Literal(bv1, false), Literal(bv2, false)},
1929 {Literal(bv1, false), Literal(bv2, true)},
1930 {Literal(bv1, true), Literal(bv2, false)},
1931 {Literal(bv1, true), Literal(bv2, true)}},
1933 return SatSolver::INFEASIBLE;
1934 }
1935
1936 RETURN_IF_NOT_FEASIBLE(PeriodicSyncAndCheck());
1937 }
1938 }
1939
1940 // Note that the product is always >= 0.
1941 const int max_num_bool_vars_for_triplet_probing =
1942 static_cast<int>(std::cbrt(2 * limit));
1943 // We use a limit to make sure we do not overflow.
1944 const int loop_limit =
1945 num_bool_vars < max_num_bool_vars_for_triplet_probing
1946 ? num_bool_vars * (num_bool_vars - 1) * (num_bool_vars - 2) / 2
1947 : limit;
1948 for (; random_triplet_of_bool_vars_probed_ < loop_limit;
1949 ++random_triplet_of_bool_vars_probed_) {
1950 const BooleanVariable bv1 =
1951 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1952 if (assignment.VariableIsAssigned(bv1)) continue;
1953 const BooleanVariable bv2 =
1954 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1955 if (assignment.VariableIsAssigned(bv2) || bv1 == bv2) {
1956 continue;
1957 }
1958 const BooleanVariable bv3 =
1959 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1960 if (assignment.VariableIsAssigned(bv3) || bv1 == bv3 || bv2 == bv3) {
1961 continue;
1962 }
1963 tmp_dnf_.clear();
1964 for (int i = 0; i < 8; ++i) {
1965 tmp_dnf_.push_back({Literal(bv1, (i & 4) > 0),
1966 Literal(bv2, (i & 2) > 0),
1967 Literal(bv3, (i & 1) > 0)});
1968 }
1969
1970 if (!prober_->ProbeDnf("rnd_triplet_of_bool_vars", tmp_dnf_,
1972 return SatSolver::INFEASIBLE;
1973 }
1974
1975 RETURN_IF_NOT_FEASIBLE(PeriodicSyncAndCheck());
1976 }
1977 }
1978
1979 // Adjust the active_limit.
1980 if (use_shaving_) {
1981 const double dtime = parameters_.shaving_search_deterministic_time();
1982 const bool something_has_been_detected =
1983 num_bounds_shaved_ != initial_num_bounds_shaved ||
1984 prober_->num_new_literals_fixed() != initial_num_literals_fixed;
1985 if (something_has_been_detected) { // Reset the limit.
1986 active_limit_ = dtime;
1987 } else if (active_limit_ <= 128 * dtime) { // Bump the limit.
1988 active_limit_ *= 2;
1989 }
1990 }
1991
1992 // Reset all counters.
1993 ++iteration_;
1994 current_bool_var_ = 0;
1995 current_int_var_ = 0;
1996 current_bv1_ = 0;
1997 current_bv2_ = 1;
1998 random_pair_of_bool_vars_probed_ = 0;
1999 random_triplet_of_bool_vars_probed_ = 0;
2000 binary_implication_graph_->ResetAtMostOneIterator();
2001 clause_manager_->ResetToProbeIndex();
2002 probed_bool_vars_.clear();
2003 shaved_literals_.clear();
2004
2005 const int new_trail_index = trail_->Index();
2006 const int new_integer_trail_index = integer_trail_->Index();
2007
2008 // Update the use_shaving_ parameter.
2009 // TODO(user): Currently, the heuristics is that we alternate shaving and
2010 // not shaving, unless shaving_deterministic_time_in_probing_search is <= 0.
2011 use_shaving_ =
2012 parameters_.shaving_deterministic_time_in_probing_search() > 0.0;
2013 trail_index_at_start_of_iteration_ = new_trail_index;
2014 integer_trail_index_at_start_of_iteration_ = new_integer_trail_index;
2015
2016 // Remove fixed Boolean variables.
2017 int new_size = 0;
2018 for (int i = 0; i < bool_vars_.size(); ++i) {
2019 if (!sat_solver_->Assignment().VariableIsAssigned(bool_vars_[i])) {
2020 bool_vars_[new_size++] = bool_vars_[i];
2021 }
2022 }
2023 bool_vars_.resize(new_size);
2024
2025 // Remove fixed integer variables.
2026 new_size = 0;
2027 for (int i = 0; i < int_vars_.size(); ++i) {
2028 if (!integer_trail_->IsFixed(int_vars_[i])) {
2029 int_vars_[new_size++] = int_vars_[i];
2030 }
2031 }
2032 int_vars_.resize(new_size);
2033 }
2034 return SatSolver::LIMIT_REACHED;
2035}
2036
2037#undef RETURN_IF_NOT_FEASIBLE
2038
2039SatSolver::Status ContinuousProber::PeriodicSyncAndCheck() {
2040 // Check limit.
2041 if (--num_test_limit_remaining_ <= 0) {
2042 num_test_limit_remaining_ = kTestLimitPeriod;
2043 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
2044 }
2045
2046 // Log the state of the prober.
2047 if (--num_logs_remaining_ <= 0) {
2048 num_logs_remaining_ = kLogPeriod;
2049 LogStatistics();
2050 }
2051
2052 // Sync with shared storage.
2053 if (--num_syncs_remaining_ <= 0) {
2054 num_syncs_remaining_ = kSyncPeriod;
2055 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
2056 for (const auto& cb : level_zero_callbacks_->callbacks) {
2057 if (!cb()) {
2058 sat_solver_->NotifyThatModelIsUnsat();
2059 return SatSolver::INFEASIBLE;
2060 }
2061 }
2062 }
2063
2064 return SatSolver::FEASIBLE;
2065}
2066
2067SatSolver::Status ContinuousProber::ShaveLiteral(Literal literal) {
2068 const auto [_, inserted] = shaved_literals_.insert(literal.Index());
2069 if (trail_->Assignment().LiteralIsAssigned(literal) || !inserted) {
2070 return SatSolver::LIMIT_REACHED;
2071 }
2072 num_bounds_tried_++;
2073
2074 const double original_dtime_limit = time_limit_->GetDeterministicLimit();
2075 time_limit_->ChangeDeterministicLimit(
2076 std::min(original_dtime_limit,
2077 time_limit_->GetElapsedDeterministicTime() + active_limit_));
2078 const SatSolver::Status status =
2079 ResetAndSolveIntegerProblem({literal}, model_);
2080 time_limit_->ChangeDeterministicLimit(original_dtime_limit);
2081 if (ReportStatus(status)) return status;
2082
2083 if (status == SatSolver::ASSUMPTIONS_UNSAT) {
2084 num_bounds_shaved_++;
2085 }
2086
2087 // Important: we want to reset the solver right away, as we check for
2088 // fixed variable in the main loop!
2089 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
2090 return status;
2091}
2092
2093bool ContinuousProber::ReportStatus(const SatSolver::Status status) {
2094 return status == SatSolver::INFEASIBLE || status == SatSolver::FEASIBLE;
2095}
2096
2097void ContinuousProber::LogStatistics() {
2098 if (shared_response_manager_ == nullptr ||
2099 shared_bounds_manager_ == nullptr) {
2100 return;
2101 }
2102 if (VLOG_IS_ON(1)) {
2103 shared_response_manager_->LogMessageWithThrottling(
2104 "Probe",
2105 absl::StrCat(
2106 " (iterations=", iteration_, " linearization_level=",
2107 parameters_.linearization_level(), " active_limit=", active_limit_,
2108 " shaving=", use_shaving_, " active_bool_vars=", bool_vars_.size(),
2109 " active_int_vars=", integer_trail_->NumIntegerVariables(),
2110 " literals fixed/probed=", prober_->num_new_literals_fixed(), "/",
2111 num_literals_probed_, " bounds shaved/tried=", num_bounds_shaved_,
2112 "/", num_bounds_tried_, " new_integer_bounds=",
2113 shared_bounds_manager_->NumBoundsExported("probing"),
2114 " new_binary_clause=", prober_->num_new_binary_clauses(),
2115 " num_at_least_one_probed=", num_at_least_one_probed_,
2116 " num_at_most_one_probed=", num_at_most_one_probed_, ")"));
2117 }
2118}
2119
2120} // namespace sat
2121} // namespace operations_research
ContinuousProber(const CpModelProto &model_proto, Model *model)
LiteralIndex GetAssociatedLiteral(IntegerLiteral i_lit) const
Definition integer.cc:533
bool TakeDecision(Literal decision, bool use_representative=true)
LiteralIndex GetDecisionLiteral(const BooleanOrIntegerLiteral &decision)
bool GetDecision(const std::function< BooleanOrIntegerLiteral()> &f, LiteralIndex *decision)
IntegerValue LowerBound(IntegerVariable i) const
Definition integer.h:1537
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1541
std::string DebugString() const
Definition sat_base.h:101
LiteralIndex Index() const
Definition sat_base.h:92
BooleanVariable Variable() const
Definition sat_base.h:88
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition model.h:110
absl::Span< const Literal > AsSpan() const
Definition clause.h:100
static constexpr SearchBranching PARTIAL_FIXED_SEARCH
static constexpr SearchBranching PORTFOLIO_SEARCH
static constexpr SearchBranching AUTOMATIC_SEARCH
static constexpr SearchBranching RANDOMIZED_SEARCH
static constexpr SearchBranching FIXED_SEARCH
static constexpr SearchBranching PSEUDO_COST_SEARCH
static constexpr SearchBranching HINT_SEARCH
::operations_research::sat::SatParameters_SearchBranching search_branching() const
static constexpr SearchBranching PORTFOLIO_WITH_QUICK_RESTART_SEARCH
static constexpr SearchBranching LP_SEARCH
const VariablesAssignment & Assignment() const
Definition sat_solver.h:420
IntervalDefinition GetIntervalDefinition(int index) const
absl::Span< const AffineExpression > Starts() const
absl::Span< const AffineExpression > Ends() const
ValueType GetVariableValueInSolution(int var_index, int solution_index) const
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:209
#define RETURN_IF_NOT_FEASIBLE(test)
void ConfigureSearchHeuristics(Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Definition util.cc:90
std::function< BooleanOrIntegerLiteral()> FirstUnassignedVarAtItsMinHeuristic(absl::Span< const IntegerVariable > vars, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> DisjunctivePrecedenceSearchHeuristic(Model *model)
std::vector< std::function< BooleanOrIntegerLiteral()> > CompleteHeuristics(absl::Span< const std::function< BooleanOrIntegerLiteral()> > incomplete_heuristics, const std::function< BooleanOrIntegerLiteral()> &completion_heuristic)
std::function< BooleanOrIntegerLiteral()> BoolPseudoCostHeuristic(Model *model)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
std::function< BooleanOrIntegerLiteral()> SequentialValueSelection(std::vector< std::function< IntegerLiteral(IntegerVariable)> > value_selection_heuristics, std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
std::function< BooleanOrIntegerLiteral()> ShaveObjectiveLb(Model *model)
std::function< BooleanOrIntegerLiteral()> IntegerValueSelectionHeuristic(std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
IntegerLiteral SplitAroundLpValue(IntegerVariable var, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
std::function< BooleanOrIntegerLiteral()> UnassignedVarWithLowestMinAtItsMinHeuristic(absl::Span< const IntegerVariable > vars, Model *model)
IntegerLiteral ChooseBestObjectiveValue(IntegerVariable var, IntegerTrail *integer_trail, ObjectiveDefinition *objective_definition)
IntegerLiteral AtMinValue(IntegerVariable var, IntegerTrail *integer_trail)
void RecordLPRelaxationValues(Model *model)
Definition rins.cc:40
SatSolver::Status SolveIntegerProblemWithLazyEncoding(Model *model)
std::function< BooleanOrIntegerLiteral()> PseudoCost(Model *model)
IntegerVariable PositiveVariable(IntegerVariable i)
std::function< BooleanOrIntegerLiteral()> SchedulingSearchHeuristic(Model *model)
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition integer.h:1825
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
std::function< bool()> RestartEveryKFailures(int k, SatSolver *solver)
std::function< BooleanOrIntegerLiteral()> RandomizeOnRestartHeuristic(bool lns_mode, Model *model)
IntegerLiteral SplitUsingBestSolutionValueInRepository(IntegerVariable var, const SharedSolutionRepository< int64_t > &solution_repo, Model *model)
std::function< BooleanOrIntegerLiteral()> FollowHint(absl::Span< const BooleanOrIntegerVariable > vars, absl::Span< const IntegerValue > values, Model *model)
bool LinearizedPartIsLarge(Model *model)
IntegerLiteral SplitAroundGivenValue(IntegerVariable var, IntegerValue value, Model *model)
const BooleanVariable kNoBooleanVariable(-1)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
std::function< bool()> SatSolverRestartPolicy(Model *model)
std::function< BooleanOrIntegerLiteral()> LpPseudoCostHeuristic(Model *model)
IntegerLiteral GreaterOrEqualToMiddleValue(IntegerVariable var, IntegerTrail *integer_trail)
double ToDouble(IntegerValue value)
std::function< BooleanOrIntegerLiteral()> MostFractionalHeuristic(Model *model)
std::function< BooleanOrIntegerLiteral()> CumulativePrecedenceSearchHeuristic(Model *model)
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()> > heuristics)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
const bool DEBUG_MODE
Definition radix_sort.h:266
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
absl::flat_hash_set< IntegerVariable > objective_impacting_variables
std::function< BooleanOrIntegerLiteral()> fixed_search
std::vector< std::function< bool()> > restart_policies
std::function< BooleanOrIntegerLiteral()> user_search
std::vector< std::function< BooleanOrIntegerLiteral()> > decision_policies
std::function< BooleanOrIntegerLiteral()> hint_search
std::function< BooleanOrIntegerLiteral()> next_decision_override