19#include "absl/memory/memory.h"
20#include "absl/strings/str_format.h"
28using ::operations_research::sat::LinearBooleanConstraint;
29using ::operations_research::sat::LinearBooleanProblem;
30using ::operations_research::sat::LinearObjective;
37 int max_num_decisions,
38 absl::BitGenRef random,
42 max_num_decisions_(max_num_decisions),
43 sat_wrapper_(sat_propagator),
44 assignment_iterator_(),
49bool LocalSearchOptimizer::ShouldBeRun(
55 const BopParameters&
parameters,
const ProblemState& problem_state,
57 CHECK(learned_info !=
nullptr);
59 learned_info->Clear();
61 if (assignment_iterator_ ==
nullptr) {
62 assignment_iterator_ = absl::make_unique<LocalSearchAssignmentIterator>(
63 problem_state, max_num_decisions_,
64 parameters.max_num_broken_constraints_in_ls(), random_, &sat_wrapper_);
67 if (state_update_stamp_ != problem_state.update_stamp()) {
69 state_update_stamp_ = problem_state.update_stamp();
70 assignment_iterator_->Synchronize(problem_state);
72 assignment_iterator_->SynchronizeSatWrapper();
74 double prev_deterministic_time = assignment_iterator_->deterministic_time();
75 assignment_iterator_->UseTranspositionTable(
77 assignment_iterator_->UsePotentialOneFlipRepairs(
78 parameters.use_potential_one_flip_repairs_in_ls());
79 int64_t num_assignments_to_explore =
80 parameters.max_number_of_explored_assignments_per_try_in_ls();
82 while (!
time_limit->LimitReached() && num_assignments_to_explore > 0 &&
83 assignment_iterator_->NextAssignment()) {
85 assignment_iterator_->deterministic_time() - prev_deterministic_time);
86 prev_deterministic_time = assignment_iterator_->deterministic_time();
87 --num_assignments_to_explore;
92 return problem_state.solution().IsFeasible()
100 if (assignment_iterator_->BetterSolutionHasBeenFound()) {
102 learned_info->solution = assignment_iterator_->LastReferenceAssignment();
111 if (num_assignments_to_explore <= 0) {
125template <
typename IntType>
128 saved_sizes_.clear();
129 saved_stack_sizes_.clear();
131 in_stack_.assign(n.value(),
false);
134template <
typename IntType>
136 bool should_be_inside) {
137 size_ += should_be_inside ? 1 : -1;
138 if (!in_stack_[i.value()]) {
139 in_stack_[i.value()] =
true;
144template <
typename IntType>
146 saved_stack_sizes_.push_back(stack_.size());
147 saved_sizes_.push_back(size_);
150template <
typename IntType>
152 if (saved_stack_sizes_.empty()) {
155 for (
int i = saved_stack_sizes_.back(); i < stack_.size(); ++i) {
156 in_stack_[stack_[i].value()] =
false;
158 stack_.resize(saved_stack_sizes_.back());
159 saved_stack_sizes_.pop_back();
160 size_ = saved_sizes_.back();
161 saved_sizes_.pop_back();
165template <
typename IntType>
167 for (
int i = 0; i < stack_.size(); ++i) {
168 in_stack_[stack_[i].value()] =
false;
171 saved_stack_sizes_.clear();
173 saved_sizes_.clear();
186 const LinearBooleanProblem& problem, absl::BitGenRef random)
187 : by_variable_matrix_(problem.num_variables()),
188 constraint_lower_bounds_(),
189 constraint_upper_bounds_(),
190 assignment_(problem,
"Assignment"),
191 reference_(problem,
"Assignment"),
192 constraint_values_(),
193 flipped_var_trail_backtrack_levels_(),
194 flipped_var_trail_(),
195 constraint_set_hasher_(random) {
197 const LinearObjective& objective = problem.objective();
198 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
199 for (
int i = 0; i < objective.literals_size(); ++i) {
201 CHECK_NE(objective.coefficients(i), 0);
203 const VariableIndex
var(objective.literals(i) - 1);
204 const int64_t
weight = objective.coefficients(i);
205 by_variable_matrix_[
var].push_back(
209 constraint_values_.push_back(0);
213 ConstraintIndex num_constraints_with_objective(1);
214 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
215 if (constraint.literals_size() <= 2) {
222 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
223 for (
int i = 0; i < constraint.literals_size(); ++i) {
224 const VariableIndex
var(constraint.literals(i) - 1);
225 const int64_t
weight = constraint.coefficients(i);
226 by_variable_matrix_[
var].push_back(
227 ConstraintEntry(num_constraints_with_objective,
weight));
229 constraint_lower_bounds_.push_back(
230 constraint.has_lower_bound() ? constraint.lower_bound()
232 constraint_values_.push_back(0);
233 constraint_upper_bounds_.push_back(
234 constraint.has_upper_bound() ? constraint.upper_bound()
237 ++num_constraints_with_objective;
241 infeasible_constraint_set_.ClearAndResize(
242 ConstraintIndex(constraint_values_.size()));
244 CHECK_EQ(constraint_values_.size(), constraint_lower_bounds_.size());
245 CHECK_EQ(constraint_values_.size(), constraint_upper_bounds_.size());
251void AssignmentAndConstraintFeasibilityMaintainer::SetReferenceSolution(
254 infeasible_constraint_set_.BacktrackAll();
256 assignment_ = reference_solution;
257 reference_ = assignment_;
258 flipped_var_trail_backtrack_levels_.clear();
259 flipped_var_trail_.clear();
260 AddBacktrackingLevel();
264 for (VariableIndex
var(0);
var < assignment_.Size(); ++
var) {
265 if (assignment_.Value(
var)) {
266 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
267 constraint_values_[entry.constraint] += entry.weight;
272 MakeObjectiveConstraintInfeasible(1);
275void AssignmentAndConstraintFeasibilityMaintainer::
276 UseCurrentStateAsReference() {
277 for (
const VariableIndex
var : flipped_var_trail_) {
278 reference_.SetValue(
var, assignment_.Value(
var));
280 flipped_var_trail_.clear();
281 flipped_var_trail_backtrack_levels_.clear();
282 AddBacktrackingLevel();
283 MakeObjectiveConstraintInfeasible(1);
286void AssignmentAndConstraintFeasibilityMaintainer::
287 MakeObjectiveConstraintInfeasible(
int delta) {
289 CHECK(flipped_var_trail_.empty());
292 infeasible_constraint_set_.BacktrackAll();
294 infeasible_constraint_set_.AddBacktrackingLevel();
296 CHECK(!IsFeasible());
299 CHECK(ConstraintIsFeasible(
ct));
304void AssignmentAndConstraintFeasibilityMaintainer::Assign(
305 const std::vector<sat::Literal>& literals) {
307 const VariableIndex
var(
literal.Variable().value());
309 if (assignment_.Value(
var) !=
value) {
310 flipped_var_trail_.push_back(
var);
312 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
313 const bool was_feasible = ConstraintIsFeasible(entry.constraint);
314 constraint_values_[entry.constraint] +=
315 value ? entry.weight : -entry.weight;
316 if (ConstraintIsFeasible(entry.constraint) != was_feasible) {
317 infeasible_constraint_set_.ChangeState(entry.constraint,
325void AssignmentAndConstraintFeasibilityMaintainer::AddBacktrackingLevel() {
326 flipped_var_trail_backtrack_levels_.push_back(flipped_var_trail_.size());
327 infeasible_constraint_set_.AddBacktrackingLevel();
330void AssignmentAndConstraintFeasibilityMaintainer::BacktrackOneLevel() {
332 for (
int i = flipped_var_trail_backtrack_levels_.back();
333 i < flipped_var_trail_.size(); ++i) {
334 const VariableIndex
var(flipped_var_trail_[i]);
335 const bool new_value = !assignment_.Value(
var);
337 assignment_.SetValue(
var, new_value);
338 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
339 constraint_values_[entry.constraint] +=
340 new_value ? entry.weight : -entry.weight;
343 flipped_var_trail_.resize(flipped_var_trail_backtrack_levels_.back());
344 flipped_var_trail_backtrack_levels_.pop_back();
345 infeasible_constraint_set_.BacktrackOneLevel();
348void AssignmentAndConstraintFeasibilityMaintainer::BacktrackAll() {
349 while (!flipped_var_trail_backtrack_levels_.empty()) BacktrackOneLevel();
352const std::vector<sat::Literal>&
353AssignmentAndConstraintFeasibilityMaintainer::PotentialOneFlipRepairs() {
354 if (!constraint_set_hasher_.IsInitialized()) {
355 InitializeConstraintSetHasher();
364 for (
const ConstraintIndex ci : PossiblyInfeasibleConstraints()) {
365 const int64_t
value = ConstraintValue(ci);
366 if (
value > ConstraintUpperBound(ci)) {
367 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci,
false));
368 }
else if (
value < ConstraintLowerBound(ci)) {
369 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci,
true));
373 tmp_potential_repairs_.clear();
374 const auto it = hash_to_potential_repairs_.find(
hash);
375 if (it != hash_to_potential_repairs_.end()) {
378 if (assignment_.Value(VariableIndex(
literal.Variable().value())) !=
380 tmp_potential_repairs_.push_back(
literal);
384 return tmp_potential_repairs_;
387std::string AssignmentAndConstraintFeasibilityMaintainer::DebugString()
const {
390 for (
bool value : assignment_) {
391 str +=
value ?
" 1 " :
" 0 ";
393 str +=
"\nFlipped variables: ";
395 for (
const VariableIndex
var : flipped_var_trail_) {
396 str += absl::StrFormat(
" %d",
var.value());
398 str +=
"\nmin curr max\n";
399 for (ConstraintIndex
ct(0);
ct < constraint_values_.size(); ++
ct) {
401 str += absl::StrFormat(
"- %d %d\n", constraint_values_[
ct],
402 constraint_upper_bounds_[
ct]);
405 absl::StrFormat(
"%d %d %d\n", constraint_lower_bounds_[
ct],
406 constraint_values_[
ct], constraint_upper_bounds_[
ct]);
412void AssignmentAndConstraintFeasibilityMaintainer::
413 InitializeConstraintSetHasher() {
414 const int num_constraints_with_objective = constraint_upper_bounds_.size();
419 constraint_set_hasher_.Initialize(2 * num_constraints_with_objective);
420 constraint_set_hasher_.IgnoreElement(
422 constraint_set_hasher_.IgnoreElement(
424 for (VariableIndex
var(0);
var < by_variable_matrix_.size(); ++
var) {
427 for (
const bool flip_is_positive : {
true,
false}) {
429 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
430 const bool coeff_is_positive = entry.weight > 0;
431 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(
433 flip_is_positive ? coeff_is_positive : !coeff_is_positive));
435 hash_to_potential_repairs_[
hash].push_back(
436 sat::Literal(sat::BooleanVariable(
var.value()), flip_is_positive));
445OneFlipConstraintRepairer::OneFlipConstraintRepairer(
446 const LinearBooleanProblem& problem,
449 : by_constraint_matrix_(problem.constraints_size() + 1),
450 maintainer_(maintainer),
451 sat_assignment_(sat_assignment) {
458 ConstraintIndex num_constraint(0);
459 const LinearObjective& objective = problem.objective();
460 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
461 for (
int i = 0; i < objective.literals_size(); ++i) {
463 CHECK_NE(objective.coefficients(i), 0);
465 const VariableIndex
var(objective.literals(i) - 1);
466 const int64_t
weight = objective.coefficients(i);
467 by_constraint_matrix_[num_constraint].push_back(
472 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
473 if (constraint.literals_size() <= 2) {
481 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
482 for (
int i = 0; i < constraint.literals_size(); ++i) {
483 const VariableIndex
var(constraint.literals(i) - 1);
484 const int64_t
weight = constraint.coefficients(i);
485 by_constraint_matrix_[num_constraint].push_back(
490 SortTermsOfEachConstraints(problem.num_variables());
505 const std::vector<ConstraintIndex>& infeasible_constraints =
507 for (
int index = infeasible_constraints.size() - 1;
index >= 0; --
index) {
508 const ConstraintIndex& i = infeasible_constraints[
index];
510 --num_infeasible_constraints_left;
515 if (num_infeasible_constraints_left == 0 &&
524 int32_t num_branches = 0;
527 sat::BooleanVariable(term.var.value()))) {
530 const int64_t new_value =
532 (maintainer_.
Assignment(term.var) ? -term.weight : term.weight);
533 if (new_value >= lb && new_value <= ub) {
535 if (num_branches >= selected_num_branches)
break;
540 if (num_branches == 0)
continue;
541 if (num_branches < selected_num_branches) {
543 selected_num_branches = num_branches;
544 if (num_branches == 1)
break;
551 ConstraintIndex ct_index, TermIndex init_term_index,
552 TermIndex start_term_index)
const {
554 by_constraint_matrix_[ct_index];
555 const int64_t constraint_value = maintainer_.
ConstraintValue(ct_index);
559 const TermIndex end_term_index(terms.
size() + init_term_index + 1);
560 for (TermIndex loop_term_index(
561 start_term_index + 1 +
562 (start_term_index < init_term_index ? terms.
size() : 0));
563 loop_term_index < end_term_index; ++loop_term_index) {
564 const TermIndex term_index(loop_term_index % terms.
size());
567 sat::BooleanVariable(term.
var.value()))) {
570 const int64_t new_value =
573 if (new_value >= lb && new_value <= ub) {
581 TermIndex term_index)
const {
583 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
585 sat::BooleanVariable(term.
var.value()))) {
588 const int64_t new_value =
594 return (new_value >= lb && new_value <= ub);
598 TermIndex term_index)
const {
599 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
604void OneFlipConstraintRepairer::SortTermsOfEachConstraints(
int num_variables) {
606 for (
const ConstraintTerm& term :
609 objective[term.var] = std::abs(term.weight);
612 by_constraint_matrix_) {
613 std::sort(terms.begin(), terms.end(),
614 [&objective](
const ConstraintTerm&
a,
const ConstraintTerm&
b) {
615 return objective[a.var] > objective[b.var];
629 std::vector<sat::Literal> propagated_literals;
631 for (
int trail_index = 0; trail_index < trail.
Index(); ++trail_index) {
632 propagated_literals.push_back(trail[trail_index]);
634 return propagated_literals;
638 std::vector<sat::Literal>* propagated_literals) {
641 CHECK(propagated_literals !=
nullptr);
643 propagated_literals->clear();
645 const int new_trail_index =
648 return old_decision_level + 1;
655 for (
int trail_index = new_trail_index;
656 trail_index < propagation_trail.
Index(); ++trail_index) {
657 propagated_literals->push_back(propagation_trail[trail_index]);
665 if (old_decision_level > 0) {
666 sat_solver_->
Backtrack(old_decision_level - 1);
683 const ProblemState& problem_state,
int max_num_decisions,
684 int max_num_broken_constraints, absl::BitGenRef random,
686 : max_num_decisions_(max_num_decisions),
687 max_num_broken_constraints_(max_num_broken_constraints),
688 maintainer_(problem_state.original_problem(), random),
689 sat_wrapper_(sat_wrapper),
690 repairer_(problem_state.original_problem(), maintainer_,
691 sat_wrapper->SatAssignment()),
694 problem_state.original_problem().constraints_size() + 1,
696 use_transposition_table_(false),
697 use_potential_one_flip_repairs_(false),
699 num_skipped_nodes_(0),
700 num_improvements_(0),
701 num_improvements_by_one_flip_repairs_(0),
702 num_inspected_one_flip_repairs_(0) {}
705 VLOG(1) <<
"LS " << max_num_decisions_
706 <<
"\n num improvements: " << num_improvements_
707 <<
"\n num improvements with one flip repairs: "
708 << num_improvements_by_one_flip_repairs_
709 <<
"\n num inspected one flip repairs: "
710 << num_inspected_one_flip_repairs_;
715 better_solution_has_been_found_ =
false;
717 for (
const SearchNode& node : search_nodes_) {
718 initial_term_index_[node.constraint] = node.term_index;
720 search_nodes_.
clear();
721 transposition_table_.clear();
723 num_skipped_nodes_ = 0;
730 CHECK_EQ(better_solution_has_been_found_,
false);
731 const std::vector<SearchNode> copy = search_nodes_;
741 search_nodes_.clear();
742 for (
const SearchNode& node : copy) {
743 if (!repairer_.
RepairIsValid(node.constraint, node.term_index))
break;
744 search_nodes_.push_back(node);
745 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
749void LocalSearchAssignmentIterator::UseCurrentStateAsReference() {
750 better_solution_has_been_found_ =
true;
758 for (
const SearchNode& node : search_nodes_) {
759 initial_term_index_[node.constraint] = node.term_index;
761 search_nodes_.
clear();
762 transposition_table_.clear();
764 num_skipped_nodes_ = 0;
771 UseCurrentStateAsReference();
780 if (use_potential_one_flip_repairs_ &&
781 search_nodes_.size() == max_num_decisions_) {
787 ++num_inspected_one_flip_repairs_;
792 num_improvements_by_one_flip_repairs_++;
793 UseCurrentStateAsReference();
809 if (search_nodes_.empty()) {
810 VLOG(1) << std::string(27,
' ') +
"LS " << max_num_decisions_
812 <<
" #explored:" << num_nodes_
813 <<
" #stored:" << transposition_table_.size()
814 <<
" #skipped:" << num_skipped_nodes_;
819 const SearchNode node = search_nodes_.back();
820 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
833 std::string str =
"Search nodes:\n";
834 for (
int i = 0; i < search_nodes_.size(); ++i) {
835 str += absl::StrFormat(
" %d: %d %d\n", i,
836 search_nodes_[i].constraint.value(),
837 search_nodes_[i].term_index.value());
844 const int num_backtracks =
848 if (num_backtracks == 0) {
850 maintainer_.
Assign(tmp_propagated_literals_);
853 CHECK_LE(num_backtracks, search_nodes_.size());
856 for (
int i = 0; i < num_backtracks - 1; ++i) {
859 maintainer_.
Assign(tmp_propagated_literals_);
860 search_nodes_.resize(search_nodes_.size() - num_backtracks);
864void LocalSearchAssignmentIterator::InitializeTranspositionTableKey(
865 std::array<int32_t, kStoredMaxDecisions>*
a) {
867 for (
const SearchNode& n : search_nodes_) {
875 while (i < kStoredMaxDecisions) {
881bool LocalSearchAssignmentIterator::NewStateIsInTranspositionTable(
883 if (search_nodes_.size() + 1 > kStoredMaxDecisions)
return false;
886 std::array<int32_t, kStoredMaxDecisions>
a;
887 InitializeTranspositionTableKey(&
a);
888 a[search_nodes_.size()] = l.SignedValue();
889 std::sort(
a.begin(),
a.begin() + 1 + search_nodes_.size());
891 if (transposition_table_.find(
a) == transposition_table_.end()) {
894 ++num_skipped_nodes_;
899void LocalSearchAssignmentIterator::InsertInTranspositionTable() {
901 if (search_nodes_.size() > kStoredMaxDecisions)
return;
904 std::array<int32_t, kStoredMaxDecisions>
a;
905 InitializeTranspositionTableKey(&
a);
906 std::sort(
a.begin(),
a.begin() + search_nodes_.size());
908 transposition_table_.insert(
a);
911bool LocalSearchAssignmentIterator::EnqueueNextRepairingTermIfAny(
912 ConstraintIndex ct_to_repair, TermIndex term_index) {
913 if (term_index == initial_term_index_[ct_to_repair])
return false;
915 term_index = initial_term_index_[ct_to_repair];
919 ct_to_repair, initial_term_index_[ct_to_repair], term_index);
921 if (!use_transposition_table_ ||
922 !NewStateIsInTranspositionTable(
923 repairer_.
GetFlip(ct_to_repair, term_index))) {
924 search_nodes_.push_back(SearchNode(ct_to_repair, term_index));
927 if (term_index == initial_term_index_[ct_to_repair])
return false;
931bool LocalSearchAssignmentIterator::GoDeeper() {
933 if (search_nodes_.size() >= max_num_decisions_) {
961 return EnqueueNextRepairingTermIfAny(ct_to_repair,
965void LocalSearchAssignmentIterator::Backtrack() {
966 while (!search_nodes_.empty()) {
971 if (use_transposition_table_) InsertInTranspositionTable();
973 const SearchNode last_node = search_nodes_.back();
974 search_nodes_.pop_back();
977 if (EnqueueNextRepairingTermIfAny(last_node.constraint,
978 last_node.term_index)) {
#define CHECK_EQ(val1, val2)
#define CHECK_GT(val1, val2)
#define CHECK_NE(val1, val2)
#define CHECK_LE(val1, val2)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
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
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
const std::vector< ConstraintIndex > & PossiblyInfeasibleConstraints() 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
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
const sat::VariablesAssignment & SatAssignment() const
double deterministic_time() const
BooleanVariable Variable() const
const VariablesAssignment & Assignment() const
const Trail & LiteralTrail() 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.