OR-Tools  9.3
lb_tree_search.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <cstdint>
18#include <functional>
19#include <memory>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/random/distributions.h"
25#include "absl/strings/str_cat.h"
26#include "absl/time/clock.h"
27#include "absl/time/time.h"
31#include "ortools/sat/integer.h"
35#include "ortools/sat/model.h"
38#include "ortools/sat/sat_parameters.pb.h"
41#include "ortools/sat/util.h"
44
45namespace operations_research {
46namespace sat {
47
49 : time_limit_(model->GetOrCreate<TimeLimit>()),
50 random_(model->GetOrCreate<ModelRandomGenerator>()),
51 sat_solver_(model->GetOrCreate<SatSolver>()),
52 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
53 integer_trail_(model->GetOrCreate<IntegerTrail>()),
54 shared_response_(model->GetOrCreate<SharedResponseManager>()),
55 sat_decision_(model->GetOrCreate<SatDecisionPolicy>()),
56 search_helper_(model->GetOrCreate<IntegerSearchHelper>()),
57 parameters_(*model->GetOrCreate<SatParameters>()) {
58 // We should create this class only in the presence of an objective.
59 //
60 // TODO(user): Starts with an initial variable score for all variable in
61 // the objective at their minimum value? this should emulate the first step of
62 // the core approach and gives a similar bound.
63 const ObjectiveDefinition* objective = model->Get<ObjectiveDefinition>();
64 CHECK(objective != nullptr);
65 objective_var_ = objective->objective_var;
66
67 // Identify an LP with the same objective variable.
68 //
69 // TODO(user): if we have many independent LP, this will find nothing.
72 if (lp->ObjectiveVariable() == objective_var_) {
73 lp_constraint_ = lp;
74 }
75 }
76
77 // We use the normal SAT search but we will bump the variable activity
78 // slightly differently. In addition to the conflicts, we also bump it each
79 // time the objective lower bound increase in a sub-node.
80 search_heuristic_ =
82 model->GetOrCreate<SearchHeuristics>()->fixed_search});
83
84 last_logging_time_ = absl::Now();
85}
86
87void LbTreeSearch::UpdateParentObjective(int level) {
88 CHECK_GE(level, 0);
89 CHECK_LT(level, current_branch_.size());
90 if (level == 0) return;
91 const NodeIndex parent_index = current_branch_[level - 1];
92 Node& parent = nodes_[parent_index];
93 const NodeIndex child_index = current_branch_[level];
94 const Node& child = nodes_[child_index];
95 if (parent.true_child == child_index) {
96 parent.UpdateTrueObjective(child.MinObjective());
97 } else {
98 CHECK_EQ(parent.false_child, child_index);
99 parent.UpdateFalseObjective(child.MinObjective());
100 }
101}
102
103void LbTreeSearch::UpdateObjectiveFromParent(int level) {
104 CHECK_GE(level, 0);
105 CHECK_LT(level, current_branch_.size());
106 if (level == 0) return;
107 const NodeIndex parent_index = current_branch_[level - 1];
108 const Node& parent = nodes_[parent_index];
109 CHECK_GE(parent.MinObjective(), current_objective_lb_);
110 const NodeIndex child_index = current_branch_[level];
111 Node& child = nodes_[child_index];
112 if (parent.true_child == child_index) {
113 child.UpdateObjective(parent.true_objective);
114 } else {
115 CHECK_EQ(parent.false_child, child_index);
116 child.UpdateObjective(parent.false_objective);
117 }
118}
119
120void LbTreeSearch::DebugDisplayTree(NodeIndex root) const {
121 int num_nodes = 0;
122 const IntegerValue root_lb = nodes_[root].MinObjective();
123 const auto shifted_lb = [root_lb](IntegerValue lb) {
124 return std::max<int64_t>(0, (lb - root_lb).value());
125 };
126
127 absl::StrongVector<NodeIndex, int> level(nodes_.size(), 0);
128 std::vector<NodeIndex> to_explore = {root};
129 while (!to_explore.empty()) {
130 NodeIndex n = to_explore.back();
131 to_explore.pop_back();
132
133 ++num_nodes;
134 const Node& node = nodes_[n];
135
136 std::string s(level[n], ' ');
137 absl::StrAppend(&s, "#", n.value());
138
139 if (node.true_child < nodes_.size()) {
140 absl::StrAppend(&s, " [t:#", node.true_child.value(), " ",
141 shifted_lb(node.true_objective), "]");
142 to_explore.push_back(node.true_child);
143 level[node.true_child] = level[n] + 1;
144 } else {
145 absl::StrAppend(&s, " [t:## ", shifted_lb(node.true_objective), "]");
146 }
147 if (node.false_child < nodes_.size()) {
148 absl::StrAppend(&s, " [f:#", node.false_child.value(), " ",
149 shifted_lb(node.false_objective), "]");
150 to_explore.push_back(node.false_child);
151 level[node.false_child] = level[n] + 1;
152 } else {
153 absl::StrAppend(&s, " [f:## ", shifted_lb(node.false_objective), "]");
154 }
155 LOG(INFO) << s;
156 }
157 LOG(INFO) << "num_nodes: " << num_nodes;
158}
159
161 const std::function<void()>& feasible_solution_observer) {
162 if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
163 return sat_solver_->UnsatStatus();
164 }
165
166 // We currently restart the search tree from scratch from time to times:
167 // - At most every kNumBranchesBeforePeriodicRestarts branches explored for
168 // at most kMaxNumInitialRestarts times.
169 // - Every time we backtrack to level zero, we count how many nodes are
170 // worse than the best known objective lower bound. If this is true for more
171 // than half of the existing nodes, we restart and clear all nodes.
172 // If if this happens during the initial restarts phase, it reset the above
173 // counter and uses 1 of the available initial restarts.
174 //
175 // This has 2 advantages:
176 // - It allows our "pseudo-cost" to kick in and experimentally result in
177 // smaller trees down the road.
178 // - It removes large inefficient search trees.
179 //
180 // TODO(user): a strong branching initial start, or allowing a few decision
181 // per nodes might be a better approach.
182 //
183 // TODO(user): It would also be cool to exploit the reason for the LB increase
184 // even more.
185 const int64_t kNumBranchesBeforePeriodicRestarts = 1000;
186 int64_t num_restarts = 0;
187 const int kMaxNumInitialRestarts = 10;
188
189 while (!time_limit_->LimitReached() && !shared_response_->ProblemIsSolved()) {
190 // This is the current bound we try to improve. We cache it here to avoid
191 // getting the lock many times and it is also easier to follow the code if
192 // this is assumed constant for one iteration.
193 current_objective_lb_ = shared_response_->GetInnerObjectiveLowerBound();
194
195 // Propagate upward in the tree the new objective lb.
196 if (!current_branch_.empty()) {
197 // Our branch is always greater or equal to the level.
198 // We increase the objective_lb of the current node if needed.
199 {
200 const int current_level = sat_solver_->CurrentDecisionLevel();
201 CHECK_GE(current_branch_.size(), current_level);
202 for (int i = 0; i < current_level; ++i) {
203 CHECK(sat_solver_->Assignment().LiteralIsAssigned(
204 nodes_[current_branch_[i]].literal));
205 }
206 if (current_level < current_branch_.size()) {
207 nodes_[current_branch_[current_level]].UpdateObjective(
208 integer_trail_->LowerBound(objective_var_));
209 }
210
211 // Minor optim: sometimes, because of the LP and cuts, the reason for
212 // objective_var_ only contains lower level literals, so we can exploit
213 // that.
214 //
215 // TODO(user): No point checking that if the objective lb wasn't
216 // assigned at this level.
217 //
218 // TODO(user): Exploit the reasons further.
219 if (integer_trail_->LowerBound(objective_var_) >
220 integer_trail_->LevelZeroLowerBound(objective_var_)) {
221 const std::vector<Literal> reason =
223 objective_var_, integer_trail_->LowerBound(objective_var_)));
224 int max_level = 0;
225 for (const Literal l : reason) {
226 max_level = std::max<int>(
227 max_level,
228 sat_solver_->LiteralTrail().Info(l.Variable()).level);
229 }
230 if (max_level < current_level) {
231 nodes_[current_branch_[max_level]].UpdateObjective(
232 integer_trail_->LowerBound(objective_var_));
233 }
234 }
235 }
236
237 // Propagate upward and then forward any new bounds.
238 for (int level = current_branch_.size(); --level > 0;) {
239 UpdateParentObjective(level);
240 }
241 nodes_[current_branch_[0]].UpdateObjective(current_objective_lb_);
242 for (int level = 1; level < current_branch_.size(); ++level) {
243 UpdateObjectiveFromParent(level);
244 }
245
246 // If the root lb increased, update global shared objective lb.
247 const IntegerValue bound = nodes_[current_branch_[0]].MinObjective();
248 if (bound > current_objective_lb_) {
249 shared_response_->UpdateInnerObjectiveBounds(
250 absl::StrCat("lb_tree_search #nodes:", nodes_.size(),
251 " #rc:", num_rc_detected_, " #imports:", num_imports_,
252 " #restarts:", num_restarts),
253 bound, integer_trail_->LevelZeroUpperBound(objective_var_));
254 current_objective_lb_ = bound;
255 if (VLOG_IS_ON(3)) DebugDisplayTree(current_branch_[0]);
256 }
257 }
258
259 // Each time we are back here, we bump the activities of the variable that
260 // are part of the objective lower bound reason.
261 //
262 // Note that this is why we prefer not to increase the lower zero lower
263 // bound of objective_var_ with the tree root lower bound, so we can exploit
264 // more reasons.
265 //
266 // TODO(user): This is slightly different than bumping each time we
267 // push a decision that result in an LB increase. This is also called on
268 // backjump for instance.
269 if (integer_trail_->LowerBound(objective_var_) >
270 integer_trail_->LevelZeroLowerBound(objective_var_)) {
271 std::vector<Literal> reason =
273 objective_var_, integer_trail_->LowerBound(objective_var_)));
274 sat_decision_->BumpVariableActivities(reason);
275 sat_decision_->UpdateVariableActivityIncrement();
276 }
277
278 // Forget the whole tree and restart.
279 // We will do it periodically at the beginning of the search each time we
280 // cross the k * kNumBranchesBeforeInitialRestarts branches explored.
281 // This will happen at most kMaxNumInitialRestarts times.
282 if (num_decisions_taken_ >= num_decisions_taken_at_last_restart_ +
283 kNumBranchesBeforePeriodicRestarts &&
284 num_restarts < kMaxNumInitialRestarts) {
285 ++num_restarts;
286 num_decisions_taken_at_last_restart_ = num_decisions_taken_;
287 VLOG(2) << "lb_tree_search initial_restart nodes: " << nodes_.size()
288 << ", branches:" << num_decisions_taken_
289 << ", restarts: " << num_restarts;
290 nodes_.clear();
291 current_branch_.clear();
292 if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
293 return sat_solver_->UnsatStatus();
294 }
295 }
296
297 // Backtrack if needed.
298 //
299 // Our algorithm stop exploring a branch as soon as its objective lower
300 // bound is greater than the root lower bound. We then backtrack to the
301 // first node in the branch that is not yet closed under this bound.
302 //
303 // TODO(user): If we remember how far we can backjump for both true/false
304 // branch, we could be more efficient.
305 while (current_branch_.size() > sat_solver_->CurrentDecisionLevel() + 1 ||
306 (current_branch_.size() > 1 &&
307 nodes_[current_branch_.back()].MinObjective() >
308 current_objective_lb_)) {
309 current_branch_.pop_back();
310 }
311
312 // Backtrack the solver.
313 {
314 int backtrack_level =
315 std::max(0, static_cast<int>(current_branch_.size()) - 1);
316
317 // Periodic restart.
318 if (num_decisions_taken_ >= num_decisions_taken_at_last_import_ + 10000) {
319 backtrack_level = 0;
320 }
321
322 sat_solver_->Backtrack(backtrack_level);
323 if (!sat_solver_->FinishPropagation()) {
324 return sat_solver_->UnsatStatus();
325 }
326 }
327
328 // This will import other workers bound if we are back to level zero.
329 if (sat_solver_->CurrentDecisionLevel() == 0) {
330 ++num_imports_;
331 num_decisions_taken_at_last_import_ = num_decisions_taken_;
332 }
333 if (!search_helper_->BeforeTakingDecision()) {
334 return sat_solver_->UnsatStatus();
335 }
336
337 // If the search has not just been restarted (in which case nodes_ would be
338 // empty), and if we are at level zero (either naturally, or if the
339 // backtrack level was set to zero in the above code), let's run a different
340 // heuristic to decide whether to retart the search from scratch or not.
341 //
342 // We ignore small search trees.
343 if (sat_solver_->CurrentDecisionLevel() == 0 && nodes_.size() > 50) {
344 // Let's count how many nodes have worse objective bounds than the best
345 // known external objective lower bound.
346 const IntegerValue latest_lb =
347 shared_response_->GetInnerObjectiveLowerBound();
348 int num_nodes_with_lower_objective = 0;
349 for (const Node& node : nodes_) {
350 if (node.MinObjective() < latest_lb) num_nodes_with_lower_objective++;
351 }
352 if (num_nodes_with_lower_objective * 2 > nodes_.size()) {
353 ++num_restarts;
354 num_decisions_taken_at_last_restart_ = num_decisions_taken_;
355 VLOG(2) << "lb_tree_search restart nodes: "
356 << num_nodes_with_lower_objective << "/" << nodes_.size()
357 << " : "
358 << 100.0 * num_nodes_with_lower_objective / nodes_.size() << "%"
359 << ", branches:" << num_decisions_taken_
360 << ", restarts: " << num_restarts;
361 nodes_.clear();
362 current_branch_.clear();
363 }
364 }
365
366 // Dive: Follow the branch with lowest objective.
367 // Note that we do not creates new nodes here.
368 while (current_branch_.size() == sat_solver_->CurrentDecisionLevel() + 1) {
369 const int level = current_branch_.size() - 1;
370 CHECK_EQ(level, sat_solver_->CurrentDecisionLevel());
371 Node& node = nodes_[current_branch_[level]];
372 node.UpdateObjective(std::max(
373 current_objective_lb_, integer_trail_->LowerBound(objective_var_)));
374 if (node.MinObjective() > current_objective_lb_) {
375 break;
376 }
377 CHECK_EQ(node.MinObjective(), current_objective_lb_) << level;
378
379 // This will be set to the next node index.
380 NodeIndex n;
381
382 // If the variable is already fixed, we bypass the node and connect
383 // its parent directly to the relevant child.
384 if (sat_solver_->Assignment().LiteralIsAssigned(node.literal)) {
385 IntegerValue new_lb;
386 if (sat_solver_->Assignment().LiteralIsTrue(node.literal)) {
387 n = node.true_child;
388 new_lb = node.true_objective;
389 } else {
390 n = node.false_child;
391 new_lb = node.false_objective;
392 }
393
394 // We jump directly to the subnode.
395 // Else we will change the root.
396 current_branch_.pop_back();
397 if (!current_branch_.empty()) {
398 const NodeIndex parent = current_branch_.back();
399 if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
400 nodes_[parent].true_child = n;
401 nodes_[parent].UpdateTrueObjective(new_lb);
402 } else {
403 CHECK(sat_solver_->Assignment().LiteralIsFalse(
404 nodes_[parent].literal));
405 nodes_[parent].false_child = n;
406 nodes_[parent].UpdateFalseObjective(new_lb);
407 }
408 if (nodes_[parent].MinObjective() > current_objective_lb_) break;
409 }
410 } else {
411 // If both lower bound are the same, we pick a random sub-branch.
412 bool choose_true = node.true_objective < node.false_objective;
413 if (node.true_objective == node.false_objective) {
414 choose_true = absl::Bernoulli(*random_, 0.5);
415 }
416 if (choose_true) {
417 n = node.true_child;
418 search_helper_->TakeDecision(node.literal);
419 } else {
420 n = node.false_child;
421 search_helper_->TakeDecision(node.literal.Negated());
422 }
423
424 num_decisions_taken_++;
425
426 // Conflict?
427 if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
428 if (choose_true) {
429 node.UpdateTrueObjective(kMaxIntegerValue);
430 } else {
431 node.UpdateFalseObjective(kMaxIntegerValue);
432 }
433 break;
434 }
435
436 // Update the proper field and abort the dive if we crossed the
437 // threshold.
438 const IntegerValue lb = integer_trail_->LowerBound(objective_var_);
439 if (choose_true) {
440 node.UpdateTrueObjective(lb);
441 } else {
442 node.UpdateFalseObjective(lb);
443 }
444 if (lb > current_objective_lb_) break;
445 }
446
447 shared_response_->LogPeriodicMessage(
448 "TreeS",
449 absl::StrCat("#nodes:", nodes_.size(), " #branches:",
450 num_decisions_taken_, " #imports:", num_imports_),
451 &last_logging_time_);
452
453 if (n < nodes_.size()) {
454 current_branch_.push_back(n);
455 } else {
456 break;
457 }
458 }
459
460 // If a conflict occurred, we will backtrack.
461 if (current_branch_.size() != sat_solver_->CurrentDecisionLevel()) {
462 continue;
463 }
464
465 // This test allow to not take a decision when the branch is already closed
466 // (i.e. the true branch or false branch lb is high enough). Adding it
467 // basically changes if we take the decision later when we explore the
468 // branch or right now.
469 //
470 // I feel taking it later is better. It also avoid creating uneeded nodes.
471 // It does change the behavior on a few problem though. For instance on
472 // irp.mps.gz, the search works better without this, whatever the random
473 // seed. Not sure why, maybe it creates more diversity?
474 //
475 // Another difference is that if the search is done and we have a feasible
476 // solution, we will not report it because of this test (except if we are
477 // at the optimal).
478 if (integer_trail_->LowerBound(objective_var_) > current_objective_lb_) {
479 continue;
480 }
481
482 // Increase the size of the tree by exploring a new decision.
483 const LiteralIndex decision =
484 search_helper_->GetDecision(search_heuristic_);
485
486 // No new decision: search done.
487 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
488 if (decision == kNoLiteralIndex) {
489 feasible_solution_observer();
490 continue;
491 }
492
493 // Create a new node.
494 // Note that the decision will be pushed to the solver on the next loop.
495 const NodeIndex n(nodes_.size());
496 nodes_.emplace_back(Literal(decision),
497 std::max(current_objective_lb_,
498 integer_trail_->LowerBound(objective_var_)));
499 if (!current_branch_.empty()) {
500 const NodeIndex parent = current_branch_.back();
501 if (sat_solver_->Assignment().LiteralIsTrue(nodes_[parent].literal)) {
502 nodes_[parent].true_child = n;
503 nodes_[parent].UpdateTrueObjective(nodes_.back().MinObjective());
504 } else {
505 CHECK(sat_solver_->Assignment().LiteralIsFalse(nodes_[parent].literal));
506 nodes_[parent].false_child = n;
507 nodes_[parent].UpdateFalseObjective(nodes_.back().MinObjective());
508 }
509 }
510 current_branch_.push_back(n);
511
512 // Looking at the reduced costs, we can already have a bound for one of the
513 // branch. Increasing the corresponding objective can save some branches,
514 // and also allow for a more incremental LP solving since we do less back
515 // and forth.
516 //
517 // TODO(user): The code to recover that is a bit convoluted. Alternatively
518 // Maybe we should do a "fast" propagation without the LP in each branch.
519 // That will work as long as we keep these optimal LP constraints around
520 // and propagate them.
521 //
522 // TODO(user): Incorporate this in the heuristic so we choose more Boolean
523 // inside these LP explanations?
524 if (lp_constraint_ != nullptr) {
525 // Note that this return literal EQUIVALENT to the decision, not just
526 // implied by it. We need that for correctness.
527 int num_tests = 0;
528 for (const IntegerLiteral integer_literal :
529 integer_encoder_->GetIntegerLiterals(Literal(decision))) {
530 if (integer_trail_->IsCurrentlyIgnored(integer_literal.var)) continue;
531
532 // To avoid bad corner case. Not sure it ever triggers.
533 if (++num_tests > 10) break;
534
535 // TODO(user): we could consider earlier constraint instead of just
536 // looking at the last one, but experiments didn't really show a big
537 // gain.
538 const auto& cts = lp_constraint_->OptimalConstraints();
539 if (cts.empty()) continue;
540
541 const std::unique_ptr<IntegerSumLE>& rc = cts.back();
542 const std::pair<IntegerValue, IntegerValue> bounds =
543 rc->ConditionalLb(integer_literal, objective_var_);
544 Node& node = nodes_[n];
545 if (bounds.first > node.false_objective) {
546 ++num_rc_detected_;
547 node.UpdateFalseObjective(bounds.first);
548 }
549 if (bounds.second > node.true_objective) {
550 ++num_rc_detected_;
551 node.UpdateTrueObjective(bounds.second);
552 }
553 }
554 }
555 }
556
558}
559
560} // namespace sat
561} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_LT(val1, val2)
Definition: base/logging.h:706
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GE(val1, val2)
Definition: base/logging.h:707
#define LOG(severity)
Definition: base/logging.h:420
#define VLOG(verboselevel)
Definition: base/logging.h:984
size_type size() const
void emplace_back(Args &&... args)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:546
const InlinedIntegerLiteralVector & GetIntegerLiterals(Literal lit) const
Definition: integer.h:469
LiteralIndex GetDecision(const std::function< BooleanOrIntegerLiteral()> &f)
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:705
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
Definition: integer.cc:1637
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1534
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1529
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1445
SatSolver::Status Search(const std::function< void()> &feasible_solution_observer)
const std::vector< std::unique_ptr< IntegerSumLE > > & OptimalConstraints() const
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void BumpVariableActivities(const std::vector< Literal > &literals)
const VariablesAssignment & Assignment() const
Definition: sat_solver.h:378
const Trail & LiteralTrail() const
Definition: sat_solver.h:377
void Backtrack(int target_level)
Definition: sat_solver.cc:991
void LogPeriodicMessage(const std::string &prefix, const std::string &message, absl::Time *last_logging_time)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:384
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:156
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:153
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:150
SharedBoundsManager * bounds
int64_t value
GRBmodel * model
const int INFO
Definition: log_severity.h:31
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()> > heuristics)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
Collection of objects used to extend the Constraint Solver library.
int64_t bound
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1387
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:44