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