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