24 #include "absl/memory/memory.h"
25 #include "absl/strings/str_format.h"
33 using ::operations_research::sat::LinearBooleanConstraint;
34 using ::operations_research::sat::LinearBooleanProblem;
35 using ::operations_research::sat::LinearObjective;
42 int max_num_decisions,
43 absl::BitGenRef random,
47 max_num_decisions_(max_num_decisions),
48 sat_wrapper_(sat_propagator),
49 assignment_iterator_(),
54 bool LocalSearchOptimizer::ShouldBeRun(
60 const BopParameters&
parameters,
const ProblemState& problem_state,
62 CHECK(learned_info !=
nullptr);
64 learned_info->Clear();
66 if (assignment_iterator_ ==
nullptr) {
67 assignment_iterator_ = std::make_unique<LocalSearchAssignmentIterator>(
68 problem_state, max_num_decisions_,
69 parameters.max_num_broken_constraints_in_ls(), random_, &sat_wrapper_);
72 if (state_update_stamp_ != problem_state.update_stamp()) {
74 state_update_stamp_ = problem_state.update_stamp();
75 assignment_iterator_->Synchronize(problem_state);
77 assignment_iterator_->SynchronizeSatWrapper();
79 double prev_deterministic_time = assignment_iterator_->deterministic_time();
80 assignment_iterator_->UseTranspositionTable(
82 assignment_iterator_->UsePotentialOneFlipRepairs(
83 parameters.use_potential_one_flip_repairs_in_ls());
84 int64_t num_assignments_to_explore =
85 parameters.max_number_of_explored_assignments_per_try_in_ls();
87 while (!
time_limit->LimitReached() && num_assignments_to_explore > 0 &&
88 assignment_iterator_->NextAssignment()) {
90 assignment_iterator_->deterministic_time() - prev_deterministic_time);
91 prev_deterministic_time = assignment_iterator_->deterministic_time();
92 --num_assignments_to_explore;
97 return problem_state.solution().IsFeasible()
105 if (assignment_iterator_->BetterSolutionHasBeenFound()) {
107 learned_info->solution = assignment_iterator_->LastReferenceAssignment();
116 if (num_assignments_to_explore <= 0) {
130 template <
typename IntType>
133 saved_sizes_.clear();
134 saved_stack_sizes_.clear();
136 in_stack_.assign(n.value(),
false);
139 template <
typename IntType>
141 bool should_be_inside) {
142 size_ += should_be_inside ? 1 : -1;
143 if (!in_stack_[i.value()]) {
144 in_stack_[i.value()] =
true;
149 template <
typename IntType>
151 saved_stack_sizes_.push_back(stack_.size());
152 saved_sizes_.push_back(size_);
155 template <
typename IntType>
157 if (saved_stack_sizes_.empty()) {
160 for (
int i = saved_stack_sizes_.back(); i < stack_.size(); ++i) {
161 in_stack_[stack_[i].value()] =
false;
163 stack_.resize(saved_stack_sizes_.back());
164 saved_stack_sizes_.pop_back();
165 size_ = saved_sizes_.back();
166 saved_sizes_.pop_back();
170 template <
typename IntType>
172 for (
int i = 0; i < stack_.size(); ++i) {
173 in_stack_[stack_[i].value()] =
false;
176 saved_stack_sizes_.clear();
178 saved_sizes_.clear();
191 const LinearBooleanProblem& problem, absl::BitGenRef random)
192 : by_variable_matrix_(problem.num_variables()),
193 constraint_lower_bounds_(),
194 constraint_upper_bounds_(),
195 assignment_(problem,
"Assignment"),
196 reference_(problem,
"Assignment"),
197 constraint_values_(),
198 flipped_var_trail_backtrack_levels_(),
199 flipped_var_trail_(),
200 constraint_set_hasher_(random) {
202 const LinearObjective& objective = problem.objective();
203 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
204 for (
int i = 0; i < objective.literals_size(); ++i) {
205 CHECK_GT(objective.literals(i), 0);
206 CHECK_NE(objective.coefficients(i), 0);
208 const VariableIndex
var(objective.literals(i) - 1);
209 const int64_t
weight = objective.coefficients(i);
210 by_variable_matrix_[
var].push_back(
214 constraint_values_.push_back(0);
218 ConstraintIndex num_constraints_with_objective(1);
219 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
220 if (constraint.literals_size() <= 2) {
227 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
228 for (
int i = 0; i < constraint.literals_size(); ++i) {
229 const VariableIndex
var(constraint.literals(i) - 1);
230 const int64_t
weight = constraint.coefficients(i);
231 by_variable_matrix_[
var].push_back(
232 ConstraintEntry(num_constraints_with_objective,
weight));
234 constraint_lower_bounds_.push_back(
235 constraint.has_lower_bound() ? constraint.lower_bound()
237 constraint_values_.push_back(0);
238 constraint_upper_bounds_.push_back(
239 constraint.has_upper_bound() ? constraint.upper_bound()
242 ++num_constraints_with_objective;
246 infeasible_constraint_set_.ClearAndResize(
247 ConstraintIndex(constraint_values_.size()));
249 CHECK_EQ(constraint_values_.size(), constraint_lower_bounds_.size());
250 CHECK_EQ(constraint_values_.size(), constraint_upper_bounds_.size());
253 const ConstraintIndex
256 void AssignmentAndConstraintFeasibilityMaintainer::SetReferenceSolution(
259 infeasible_constraint_set_.BacktrackAll();
261 assignment_ = reference_solution;
262 reference_ = assignment_;
263 flipped_var_trail_backtrack_levels_.clear();
264 flipped_var_trail_.clear();
265 AddBacktrackingLevel();
269 for (VariableIndex
var(0);
var < assignment_.Size(); ++
var) {
270 if (assignment_.Value(
var)) {
271 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
272 constraint_values_[entry.constraint] += entry.weight;
277 MakeObjectiveConstraintInfeasible(1);
280 void AssignmentAndConstraintFeasibilityMaintainer::
281 UseCurrentStateAsReference() {
282 for (
const VariableIndex
var : flipped_var_trail_) {
283 reference_.SetValue(
var, assignment_.Value(
var));
285 flipped_var_trail_.clear();
286 flipped_var_trail_backtrack_levels_.clear();
287 AddBacktrackingLevel();
288 MakeObjectiveConstraintInfeasible(1);
291 void AssignmentAndConstraintFeasibilityMaintainer::
292 MakeObjectiveConstraintInfeasible(
int delta) {
294 CHECK(flipped_var_trail_.empty());
297 infeasible_constraint_set_.BacktrackAll();
299 infeasible_constraint_set_.AddBacktrackingLevel();
301 CHECK(!IsFeasible());
304 CHECK(ConstraintIsFeasible(
ct));
309 void AssignmentAndConstraintFeasibilityMaintainer::Assign(
310 const std::vector<sat::Literal>& literals) {
312 const VariableIndex
var(
literal.Variable().value());
314 if (assignment_.Value(
var) !=
value) {
315 flipped_var_trail_.push_back(
var);
317 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
318 const bool was_feasible = ConstraintIsFeasible(entry.constraint);
319 constraint_values_[entry.constraint] +=
320 value ? entry.weight : -entry.weight;
321 if (ConstraintIsFeasible(entry.constraint) != was_feasible) {
322 infeasible_constraint_set_.ChangeState(entry.constraint,
330 void AssignmentAndConstraintFeasibilityMaintainer::AddBacktrackingLevel() {
331 flipped_var_trail_backtrack_levels_.push_back(flipped_var_trail_.size());
332 infeasible_constraint_set_.AddBacktrackingLevel();
335 void AssignmentAndConstraintFeasibilityMaintainer::BacktrackOneLevel() {
337 for (
int i = flipped_var_trail_backtrack_levels_.back();
338 i < flipped_var_trail_.size(); ++i) {
339 const VariableIndex
var(flipped_var_trail_[i]);
340 const bool new_value = !assignment_.Value(
var);
341 DCHECK_EQ(new_value, reference_.Value(
var));
342 assignment_.SetValue(
var, new_value);
343 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
344 constraint_values_[entry.constraint] +=
345 new_value ? entry.weight : -entry.weight;
348 flipped_var_trail_.resize(flipped_var_trail_backtrack_levels_.back());
349 flipped_var_trail_backtrack_levels_.pop_back();
350 infeasible_constraint_set_.BacktrackOneLevel();
353 void AssignmentAndConstraintFeasibilityMaintainer::BacktrackAll() {
354 while (!flipped_var_trail_backtrack_levels_.empty()) BacktrackOneLevel();
357 const std::vector<sat::Literal>&
358 AssignmentAndConstraintFeasibilityMaintainer::PotentialOneFlipRepairs() {
359 if (!constraint_set_hasher_.IsInitialized()) {
360 InitializeConstraintSetHasher();
369 for (
const ConstraintIndex ci : PossiblyInfeasibleConstraints()) {
370 const int64_t
value = ConstraintValue(ci);
371 if (
value > ConstraintUpperBound(ci)) {
372 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci,
false));
373 }
else if (
value < ConstraintLowerBound(ci)) {
374 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci,
true));
378 tmp_potential_repairs_.clear();
379 const auto it = hash_to_potential_repairs_.find(
hash);
380 if (it != hash_to_potential_repairs_.end()) {
383 if (assignment_.Value(VariableIndex(
literal.Variable().value())) !=
385 tmp_potential_repairs_.push_back(
literal);
389 return tmp_potential_repairs_;
392 std::string AssignmentAndConstraintFeasibilityMaintainer::DebugString()
const {
395 for (
bool value : assignment_) {
396 str +=
value ?
" 1 " :
" 0 ";
398 str +=
"\nFlipped variables: ";
400 for (
const VariableIndex
var : flipped_var_trail_) {
401 str += absl::StrFormat(
" %d",
var.value());
403 str +=
"\nmin curr max\n";
404 for (ConstraintIndex
ct(0);
ct < constraint_values_.size(); ++
ct) {
406 str += absl::StrFormat(
"- %d %d\n", constraint_values_[
ct],
407 constraint_upper_bounds_[
ct]);
410 absl::StrFormat(
"%d %d %d\n", constraint_lower_bounds_[
ct],
411 constraint_values_[
ct], constraint_upper_bounds_[
ct]);
417 void AssignmentAndConstraintFeasibilityMaintainer::
418 InitializeConstraintSetHasher() {
419 const int num_constraints_with_objective = constraint_upper_bounds_.size();
424 constraint_set_hasher_.Initialize(2 * num_constraints_with_objective);
425 constraint_set_hasher_.IgnoreElement(
427 constraint_set_hasher_.IgnoreElement(
429 for (VariableIndex
var(0);
var < by_variable_matrix_.size(); ++
var) {
432 for (
const bool flip_is_positive : {
true,
false}) {
434 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
435 const bool coeff_is_positive = entry.weight > 0;
436 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(
438 flip_is_positive ? coeff_is_positive : !coeff_is_positive));
440 hash_to_potential_repairs_[
hash].push_back(
441 sat::Literal(sat::BooleanVariable(
var.value()), flip_is_positive));
450 OneFlipConstraintRepairer::OneFlipConstraintRepairer(
451 const LinearBooleanProblem& problem,
454 : by_constraint_matrix_(problem.constraints_size() + 1),
455 maintainer_(maintainer),
456 sat_assignment_(sat_assignment) {
463 ConstraintIndex num_constraint(0);
464 const LinearObjective& objective = problem.objective();
465 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
466 for (
int i = 0; i < objective.literals_size(); ++i) {
467 CHECK_GT(objective.literals(i), 0);
468 CHECK_NE(objective.coefficients(i), 0);
470 const VariableIndex
var(objective.literals(i) - 1);
471 const int64_t
weight = objective.coefficients(i);
472 by_constraint_matrix_[num_constraint].push_back(
477 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
478 if (constraint.literals_size() <= 2) {
486 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
487 for (
int i = 0; i < constraint.literals_size(); ++i) {
488 const VariableIndex
var(constraint.literals(i) - 1);
489 const int64_t
weight = constraint.coefficients(i);
490 by_constraint_matrix_[num_constraint].push_back(
495 SortTermsOfEachConstraints(problem.num_variables());
510 const std::vector<ConstraintIndex>& infeasible_constraints =
512 for (
int index = infeasible_constraints.size() - 1;
index >= 0; --
index) {
513 const ConstraintIndex& i = infeasible_constraints[
index];
515 --num_infeasible_constraints_left;
520 if (num_infeasible_constraints_left == 0 &&
529 int32_t num_branches = 0;
532 sat::BooleanVariable(term.var.value()))) {
535 const int64_t new_value =
537 (maintainer_.
Assignment(term.var) ? -term.weight : term.weight);
538 if (new_value >= lb && new_value <= ub) {
540 if (num_branches >= selected_num_branches)
break;
545 if (num_branches == 0)
continue;
546 if (num_branches < selected_num_branches) {
548 selected_num_branches = num_branches;
549 if (num_branches == 1)
break;
556 ConstraintIndex ct_index, TermIndex init_term_index,
557 TermIndex start_term_index)
const {
559 by_constraint_matrix_[ct_index];
560 const int64_t constraint_value = maintainer_.
ConstraintValue(ct_index);
564 const TermIndex end_term_index(terms.
size() + init_term_index + 1);
565 for (TermIndex loop_term_index(
566 start_term_index + 1 +
567 (start_term_index < init_term_index ? terms.
size() : 0));
568 loop_term_index < end_term_index; ++loop_term_index) {
569 const TermIndex term_index(loop_term_index % terms.
size());
572 sat::BooleanVariable(term.
var.value()))) {
575 const int64_t new_value =
578 if (new_value >= lb && new_value <= ub) {
586 TermIndex term_index)
const {
588 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
590 sat::BooleanVariable(term.
var.value()))) {
593 const int64_t new_value =
599 return (new_value >= lb && new_value <= ub);
603 TermIndex term_index)
const {
604 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
609 void OneFlipConstraintRepairer::SortTermsOfEachConstraints(
int num_variables) {
611 for (
const ConstraintTerm& term :
614 objective[term.var] = std::abs(term.weight);
617 by_constraint_matrix_) {
618 std::sort(terms.begin(), terms.end(),
619 [&objective](
const ConstraintTerm&
a,
const ConstraintTerm&
b) {
620 return objective[a.var] > objective[b.var];
634 std::vector<sat::Literal> propagated_literals;
636 for (
int trail_index = 0; trail_index < trail.
Index(); ++trail_index) {
637 propagated_literals.push_back(trail[trail_index]);
639 return propagated_literals;
643 std::vector<sat::Literal>* propagated_literals) {
646 CHECK(propagated_literals !=
nullptr);
648 propagated_literals->clear();
650 const int new_trail_index =
653 return old_decision_level + 1;
660 for (
int trail_index = new_trail_index;
661 trail_index < propagation_trail.
Index(); ++trail_index) {
662 propagated_literals->push_back(propagation_trail[trail_index]);
670 if (old_decision_level > 0) {
671 sat_solver_->
Backtrack(old_decision_level - 1);
688 const ProblemState& problem_state,
int max_num_decisions,
689 int max_num_broken_constraints, absl::BitGenRef random,
691 : max_num_decisions_(max_num_decisions),
692 max_num_broken_constraints_(max_num_broken_constraints),
693 maintainer_(problem_state.original_problem(), random),
694 sat_wrapper_(sat_wrapper),
695 repairer_(problem_state.original_problem(), maintainer_,
696 sat_wrapper->SatAssignment()),
699 problem_state.original_problem().constraints_size() + 1,
701 use_transposition_table_(false),
702 use_potential_one_flip_repairs_(false),
704 num_skipped_nodes_(0),
705 num_improvements_(0),
706 num_improvements_by_one_flip_repairs_(0),
707 num_inspected_one_flip_repairs_(0) {}
710 VLOG(1) <<
"LS " << max_num_decisions_
711 <<
"\n num improvements: " << num_improvements_
712 <<
"\n num improvements with one flip repairs: "
713 << num_improvements_by_one_flip_repairs_
714 <<
"\n num inspected one flip repairs: "
715 << num_inspected_one_flip_repairs_;
720 better_solution_has_been_found_ =
false;
722 for (
const SearchNode& node : search_nodes_) {
723 initial_term_index_[node.constraint] = node.term_index;
725 search_nodes_.
clear();
726 transposition_table_.clear();
728 num_skipped_nodes_ = 0;
735 CHECK_EQ(better_solution_has_been_found_,
false);
736 const std::vector<SearchNode> copy = search_nodes_;
746 search_nodes_.clear();
747 for (
const SearchNode& node : copy) {
748 if (!repairer_.
RepairIsValid(node.constraint, node.term_index))
break;
749 search_nodes_.push_back(node);
750 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
754 void LocalSearchAssignmentIterator::UseCurrentStateAsReference() {
755 better_solution_has_been_found_ =
true;
763 for (
const SearchNode& node : search_nodes_) {
764 initial_term_index_[node.constraint] = node.term_index;
766 search_nodes_.
clear();
767 transposition_table_.clear();
769 num_skipped_nodes_ = 0;
776 UseCurrentStateAsReference();
785 if (use_potential_one_flip_repairs_ &&
786 search_nodes_.size() == max_num_decisions_) {
792 ++num_inspected_one_flip_repairs_;
797 num_improvements_by_one_flip_repairs_++;
798 UseCurrentStateAsReference();
814 if (search_nodes_.empty()) {
815 VLOG(1) << std::string(27,
' ') +
"LS " << max_num_decisions_
817 <<
" #explored:" << num_nodes_
818 <<
" #stored:" << transposition_table_.size()
819 <<
" #skipped:" << num_skipped_nodes_;
824 const SearchNode node = search_nodes_.back();
825 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
838 std::string str =
"Search nodes:\n";
839 for (
int i = 0; i < search_nodes_.size(); ++i) {
840 str += absl::StrFormat(
" %d: %d %d\n", i,
841 search_nodes_[i].constraint.value(),
842 search_nodes_[i].term_index.value());
849 const int num_backtracks =
853 if (num_backtracks == 0) {
855 maintainer_.
Assign(tmp_propagated_literals_);
857 CHECK_GT(num_backtracks, 0);
858 CHECK_LE(num_backtracks, search_nodes_.size());
861 for (
int i = 0; i < num_backtracks - 1; ++i) {
864 maintainer_.
Assign(tmp_propagated_literals_);
865 search_nodes_.resize(search_nodes_.size() - num_backtracks);
869 void LocalSearchAssignmentIterator::InitializeTranspositionTableKey(
870 std::array<int32_t, kStoredMaxDecisions>*
a) {
872 for (
const SearchNode& n : search_nodes_) {
880 while (i < kStoredMaxDecisions) {
886 bool LocalSearchAssignmentIterator::NewStateIsInTranspositionTable(
888 if (search_nodes_.size() + 1 > kStoredMaxDecisions)
return false;
891 std::array<int32_t, kStoredMaxDecisions>
a;
892 InitializeTranspositionTableKey(&
a);
893 a[search_nodes_.size()] = l.SignedValue();
894 std::sort(
a.begin(),
a.begin() + 1 + search_nodes_.size());
896 if (transposition_table_.find(
a) == transposition_table_.end()) {
899 ++num_skipped_nodes_;
904 void LocalSearchAssignmentIterator::InsertInTranspositionTable() {
906 if (search_nodes_.size() > kStoredMaxDecisions)
return;
909 std::array<int32_t, kStoredMaxDecisions>
a;
910 InitializeTranspositionTableKey(&
a);
911 std::sort(
a.begin(),
a.begin() + search_nodes_.size());
913 transposition_table_.insert(
a);
916 bool LocalSearchAssignmentIterator::EnqueueNextRepairingTermIfAny(
917 ConstraintIndex ct_to_repair, TermIndex term_index) {
918 if (term_index == initial_term_index_[ct_to_repair])
return false;
920 term_index = initial_term_index_[ct_to_repair];
924 ct_to_repair, initial_term_index_[ct_to_repair], term_index);
926 if (!use_transposition_table_ ||
927 !NewStateIsInTranspositionTable(
928 repairer_.
GetFlip(ct_to_repair, term_index))) {
929 search_nodes_.push_back(SearchNode(ct_to_repair, term_index));
932 if (term_index == initial_term_index_[ct_to_repair])
return false;
936 bool LocalSearchAssignmentIterator::GoDeeper() {
938 if (search_nodes_.size() >= max_num_decisions_) {
966 return EnqueueNextRepairingTermIfAny(ct_to_repair,
970 void LocalSearchAssignmentIterator::Backtrack() {
971 while (!search_nodes_.empty()) {
976 if (use_transposition_table_) InsertInTranspositionTable();
978 const SearchNode last_node = search_nodes_.back();
979 search_nodes_.pop_back();
982 if (EnqueueNextRepairingTermIfAny(last_node.constraint,
983 last_node.term_index)) {
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
int64_t ConstraintLowerBound(ConstraintIndex constraint) const
const std::vector< ConstraintIndex > & PossiblyInfeasibleConstraints() const
AssignmentAndConstraintFeasibilityMaintainer(const sat::LinearBooleanProblem &problem, absl::BitGenRef random)
void AddBacktrackingLevel()
void UseCurrentStateAsReference()
int NumInfeasibleConstraints() const
void SetReferenceSolution(const BopSolution &reference_solution)
int64_t ConstraintUpperBound(ConstraintIndex constraint) const
bool ConstraintIsFeasible(ConstraintIndex constraint) const
int64_t ConstraintValue(ConstraintIndex constraint) const
bool Assignment(VariableIndex var) const
void Assign(const std::vector< sat::Literal > &literals)
const std::vector< sat::Literal > & PotentialOneFlipRepairs()
void ClearAndResize(IntType n)
void AddBacktrackingLevel()
void ChangeState(IntType i, bool should_be_inside)
~LocalSearchAssignmentIterator()
void SynchronizeSatWrapper()
std::string DebugString() const
LocalSearchAssignmentIterator(const ProblemState &problem_state, int max_num_decisions, int max_num_broken_constraints, absl::BitGenRef random, SatWrapper *sat_wrapper)
void Synchronize(const ProblemState &problem_state)
double deterministic_time() const
LocalSearchOptimizer(const std::string &name, int max_num_decisions, absl::BitGenRef random, sat::SatSolver *sat_propagator)
~LocalSearchOptimizer() override
sat::Literal GetFlip(ConstraintIndex ct_index, TermIndex term_index) const
static const TermIndex kInvalidTerm
ConstraintIndex ConstraintToRepair() const
bool RepairIsValid(ConstraintIndex ct_index, TermIndex term_index) const
TermIndex NextRepairingTerm(ConstraintIndex ct_index, TermIndex init_term_index, TermIndex start_term_index) const
static const TermIndex kInitTerm
static const ConstraintIndex kInvalidConstraint
const BopSolution & solution() const
const sat::VariablesAssignment & SatAssignment() const
SatWrapper(sat::SatSolver *sat_solver)
std::vector< sat::Literal > FullSatTrail() const
int ApplyDecision(sat::Literal decision_literal, std::vector< sat::Literal > *propagated_literals)
void ExtractLearnedInfo(LearnedInfo *info)
bool IsModelUnsat() const
double deterministic_time() const
BooleanVariable Variable() const
const Trail & LiteralTrail() const
const VariablesAssignment & Assignment() const
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
void Backtrack(int target_level)
bool IsModelUnsat() const
int CurrentDecisionLevel() const
double deterministic_time() const
bool VariableIsAssigned(BooleanVariable var) const
ModelSharedTimeLimit * time_limit
void ExtractLearnedInfoFromSatSolver(sat::SatSolver *solver, LearnedInfo *info)
int NumConstraints(const LinearConstraintsProto &linear_constraints)
constexpr int kObjectiveConstraint
Collection of objects used to extend the Constraint Solver library.
#define VLOG(verboselevel)