22#include "absl/cleanup/cleanup.h"
23#include "absl/log/check.h"
24#include "absl/log/log.h"
25#include "absl/random/bit_gen_ref.h"
26#include "absl/random/distributions.h"
27#include "absl/strings/string_view.h"
51using ::operations_research::glop::ColIndex;
53using ::operations_research::sat::LinearBooleanConstraint;
54using ::operations_research::sat::LinearBooleanProblem;
62 sat::SatSolver* solver) {
64 solver->SetAssignmentPreference(
65 sat::Literal(sat::BooleanVariable(i),
solution.Value(VariableIndex(i))),
75 objective_terms_(objective_terms) {}
80 const ProblemState& problem_state,
int num_relaxed_vars) {
81 if (state_update_stamp_ == problem_state.
update_stamp()) {
87 sat_solver_ = std::make_unique<sat::SatSolver>();
98 std::vector<sat::LiteralWithCoeff> cst;
99 for (BopConstraintTerm term : objective_terms_) {
100 if (problem_state.
solution().
Value(term.var_id) && term.weight < 0) {
102 sat::Literal(sat::BooleanVariable(term.var_id.value()),
false), 1.0));
105 cst.push_back(sat::LiteralWithCoeff(
106 sat::Literal(sat::BooleanVariable(term.var_id.value()),
true), 1.0));
109 sat_solver_->AddLinearConstraint(
110 false, sat::Coefficient(0),
111 true, sat::Coefficient(num_relaxed_vars), &cst);
118 UseBopSolutionForSatAssignmentPreference(problem_state.
solution(),
123bool BopCompleteLNSOptimizer::ShouldBeRun(
125 return problem_state.solution().IsFeasible();
130 LearnedInfo* learned_info, TimeLimit* time_limit) {
132 CHECK(learned_info !=
nullptr);
133 CHECK(time_limit !=
nullptr);
134 learned_info->Clear();
137 SynchronizeIfNeeded(problem_state, parameters.num_relaxed_vars());
142 CHECK(sat_solver_ !=
nullptr);
143 const double initial_dt = sat_solver_->deterministic_time();
144 auto advance_dt = ::absl::MakeCleanup([initial_dt,
this, &time_limit]() {
145 time_limit->AdvanceDeterministicTime(sat_solver_->deterministic_time() -
152 sat::SatParameters sat_params;
153 sat_params.set_max_number_of_conflicts(
154 parameters.max_number_of_conflicts_in_random_lns());
155 sat_params.set_max_time_in_seconds(time_limit->GetTimeLeft());
156 sat_params.set_max_deterministic_time(time_limit->GetDeterministicTimeLeft());
157 sat_params.set_random_seed(parameters.random_seed());
159 sat_solver_->SetParameters(sat_params);
163 &learned_info->solution);
181bool UseLinearRelaxationForSatAssignmentPreference(
183 sat::SatSolver* sat_solver, TimeLimit* time_limit) {
186 glop::LinearProgram lp_model;
190 const sat::Trail& propagation_trail = sat_solver->LiteralTrail();
191 for (
int trail_index = 0; trail_index < propagation_trail.Index();
193 const sat::Literal fixed_literal = propagation_trail[trail_index];
195 lp_model.SetVariableBounds(ColIndex(fixed_literal.Variable().value()),
199 glop::LPSolver lp_solver;
200 NestedTimeLimit nested_time_limit(time_limit, time_limit->GetTimeLeft(),
201 parameters.lp_max_deterministic_time());
203 lp_solver.SolveWithTimeLimit(lp_model, nested_time_limit.GetTimeLimit());
213 for (ColIndex col(0); col < lp_solver.variable_values().size(); ++col) {
214 const double value = lp_solver.variable_values()[col];
215 sat_solver->SetAssignmentPreference(
216 sat::Literal(sat::BooleanVariable(col.value()), round(value) == 1),
217 1 - fabs(value - round(value)));
228 absl::string_view name,
bool use_lp_to_guide_sat,
230 sat::SatSolver* sat_propagator)
232 use_lp_to_guide_sat_(use_lp_to_guide_sat),
233 neighborhood_generator_(neighborhood_generator),
234 sat_propagator_(sat_propagator),
235 adaptive_difficulty_(0.001) {
236 CHECK(sat_propagator !=
nullptr);
241bool BopAdaptiveLNSOptimizer::ShouldBeRun(
247 const BopParameters& parameters,
const ProblemState& problem_state,
248 LearnedInfo* learned_info,
TimeLimit* time_limit) {
250 CHECK(learned_info !=
nullptr);
251 CHECK(time_limit !=
nullptr);
252 learned_info->Clear();
256 auto sat_propagator_cleanup =
257 ::absl::MakeCleanup([initial_dt,
this, &learned_info, &time_limit]() {
274 const BopParameters& local_parameters = parameters;
277 num_tries < local_parameters.num_random_lns_tries()) {
279 adaptive_difficulty_.UpdateLuby();
280 const double difficulty = adaptive_difficulty_.GetParameterValue();
281 neighborhood_generator_->GenerateNeighborhood(problem_state, difficulty,
285 VLOG(2) << num_tries <<
" difficulty:" << difficulty
286 <<
" luby:" << adaptive_difficulty_.luby_value()
287 <<
" fixed:" << sat_propagator_->LiteralTrail().Index() <<
"/"
288 << problem_state.original_problem().num_variables();
291 if (!sat_propagator_->ModelIsUnsat()) {
292 if (sat_propagator_->CurrentDecisionLevel() == 0) {
293 VLOG(2) <<
"Nothing fixed!";
294 adaptive_difficulty_.DecreaseParameter();
302 if (!sat_propagator_->ModelIsUnsat()) {
303 sat::SatParameters params;
304 params.set_max_number_of_conflicts(
305 local_parameters.max_number_of_conflicts_for_quick_check());
306 params.set_max_time_in_seconds(time_limit->
GetTimeLeft());
308 params.set_random_seed(parameters.random_seed());
309 sat_propagator_->SetParameters(params);
310 sat_propagator_->SetAssumptionLevel(
311 sat_propagator_->CurrentDecisionLevel());
315 adaptive_difficulty_.IncreaseParameter();
317 &learned_info->solution);
321 adaptive_difficulty_.IncreaseParameter();
331 if (!sat_propagator_->ModelIsUnsat()) {
332 (void)sat_propagator_->ResetToLevelZero();
337 if (sat_propagator_->ModelIsUnsat()) {
338 return problem_state.solution().IsFeasible()
349 const int conflict_limit =
350 adaptive_difficulty_.luby_value() *
351 parameters.max_number_of_conflicts_in_random_lns();
353 sat::SatParameters params;
354 params.set_max_number_of_conflicts(conflict_limit);
355 params.set_max_time_in_seconds(time_limit->
GetTimeLeft());
357 params.set_random_seed(parameters.random_seed());
359 sat::SatSolver sat_solver;
360 sat_solver.SetParameters(params);
363 const LinearBooleanProblem& problem = problem_state.original_problem();
364 sat_solver.SetNumVariables(problem.num_variables());
365 for (
int i = 0;
i < sat_propagator_->LiteralTrail().
Index(); ++
i) {
366 CHECK(sat_solver.AddUnitClause(sat_propagator_->LiteralTrail()[i]));
377 adaptive_difficulty_.IncreaseParameter();
381 if (use_lp_to_guide_sat_) {
382 if (!UseLinearRelaxationForSatAssignmentPreference(
383 parameters, problem, &sat_solver, time_limit)) {
391 problem, sat::Coefficient(problem_state.solution().GetCost()) - 1,
394 adaptive_difficulty_.IncreaseParameter();
404 &learned_info->solution);
409 if (sat_solver.num_failures() < 0.5 * conflict_limit) {
410 adaptive_difficulty_.IncreaseParameter();
411 }
else if (sat_solver.num_failures() > 0.95 * conflict_limit) {
412 adaptive_difficulty_.DecreaseParameter();
425std::vector<sat::Literal> ObjectiveVariablesAssignedToTheirLowCostValue(
428 std::vector<sat::Literal> result;
429 DCHECK(problem_state.solution().IsFeasible());
431 if (((problem_state.solution().Value(term.var_id) && term.weight < 0) ||
432 (!problem_state.solution().Value(term.var_id) && term.weight > 0))) {
434 sat::Literal(sat::BooleanVariable(term.var_id.value()),
435 problem_state.solution().Value(term.var_id)));
443void ObjectiveBasedNeighborhood::GenerateNeighborhood(
445 sat::SatSolver* sat_propagator) {
447 std::vector<sat::Literal> candidates =
448 ObjectiveVariablesAssignedToTheirLowCostValue(problem_state,
450 std::shuffle(candidates.begin(), candidates.end(), random_);
454 const int num_variables = sat_propagator->NumVariables();
455 const int target = round((1.0 - difficulty) * num_variables);
457 sat_propagator->Backtrack(0);
458 for (
const sat::Literal literal : candidates) {
459 if (sat_propagator->LiteralTrail().Index() == target)
break;
460 if (sat_propagator->LiteralTrail().Index() > target) {
463 sat_propagator->Backtrack(
464 std::max(0, sat_propagator->CurrentDecisionLevel() - 1));
467 sat_propagator->EnqueueDecisionAndBacktrackOnConflict(literal);
468 if (sat_propagator->ModelIsUnsat())
return;
472void ConstraintBasedNeighborhood::GenerateNeighborhood(
474 sat::SatSolver* sat_propagator) {
476 const LinearBooleanProblem& problem = problem_state.original_problem();
477 const int num_constraints = problem.constraints_size();
478 std::vector<int> ct_ids(num_constraints, 0);
479 for (
int ct_id = 0; ct_id < num_constraints; ++ct_id) ct_ids[ct_id] = ct_id;
480 std::shuffle(ct_ids.begin(), ct_ids.end(), random_);
484 const int num_variables = sat_propagator->NumVariables();
485 const int target = round(difficulty * num_variables);
487 std::vector<bool> variable_is_relaxed(problem.num_variables(),
false);
488 for (
int i = 0;
i < ct_ids.size(); ++
i) {
489 if (num_relaxed >= target)
break;
490 const LinearBooleanConstraint& constraint = problem.constraints(ct_ids[i]);
494 if (constraint.
literals_size() > 0.7 * num_variables)
continue;
497 const VariableIndex var_id(constraint.
literals(j) - 1);
498 if (!variable_is_relaxed[var_id.value()]) {
500 variable_is_relaxed[var_id.value()] =
true;
510 sat_propagator->Backtrack(0);
511 const std::vector<sat::Literal> to_fix =
512 ObjectiveVariablesAssignedToTheirLowCostValue(problem_state,
514 for (
const sat::Literal literal : to_fix) {
515 if (variable_is_relaxed[literal.Variable().value()])
continue;
516 sat_propagator->EnqueueDecisionAndBacktrackOnConflict(literal);
517 if (sat_propagator->ModelIsUnsat())
return;
524 const int num_variables = problem.num_variables();
525 columns_.resize(num_variables);
534 const double kSizeThreshold = 0.1;
535 for (
int i = 0; i < problem.constraints_size(); ++i) {
537 if (constraint.
literals_size() > kSizeThreshold * num_variables)
continue;
540 columns_[VariableIndex(literal.
Variable().value())].push_back(
546void RelationGraphBasedNeighborhood::GenerateNeighborhood(
547 const ProblemState& problem_state,
double difficulty,
550 const int num_variables = sat_propagator->
NumVariables();
551 const int target = round(difficulty * num_variables);
553 std::vector<bool> variable_is_relaxed(num_variables,
false);
554 std::deque<int> queue;
559 queue.push_back(absl::Uniform(random_, 0, num_variables));
560 variable_is_relaxed[queue.back()] =
true;
561 while (!queue.empty() && num_relaxed < target) {
562 const int var = queue.front();
564 for (ConstraintIndex ct_index : columns_[VariableIndex(var)]) {
566 problem_state.original_problem().constraints(ct_index.value());
569 const int next_var = literal.Variable().value();
570 if (!variable_is_relaxed[next_var]) {
572 variable_is_relaxed[next_var] =
true;
573 queue.push_back(next_var);
581 DCHECK(problem_state.solution().IsFeasible());
583 for (sat::BooleanVariable var(0); var < num_variables; ++var) {
584 const sat::Literal literal(
585 var, problem_state.solution().Value(VariableIndex(var.value())));
586 if (variable_is_relaxed[literal.Variable().value()])
continue;
591 if (variable_is_relaxed
592 [sat_propagator->
LiteralTrail()[i].Variable().value()]) {
599 VLOG(2) <<
"target:" << target <<
" relaxed:" << num_relaxed <<
" actual:"
double GetTimeLeft() const
void AdvanceDeterministicTime(double deterministic_duration)
double GetDeterministicTimeLeft() const
BopAdaptiveLNSOptimizer(absl::string_view name, bool use_lp_to_guide_sat, NeighborhoodGenerator *neighborhood_generator, sat::SatSolver *sat_propagator)
~BopAdaptiveLNSOptimizer() final
~BopCompleteLNSOptimizer() final
BopCompleteLNSOptimizer(absl::string_view name, const BopConstraintTerms &objective_terms)
BopOptimizerBase(absl::string_view name)
const std::string & name() const
bool Value(VariableIndex var) const
int literals_size() const
::int32_t literals(int index) const
int64_t update_stamp() const
const BopSolution & solution() const
RelationGraphBasedNeighborhood(const sat::LinearBooleanProblem &problem, absl::BitGenRef random)
int literals_size() const
::int32_t literals(int index) const
BooleanVariable Variable() const
Status EnqueueDecisionAndBacktrackOnConflict(Literal true_literal, int *first_propagation_index=nullptr)
double deterministic_time() const
bool ModelIsUnsat() const
void Backtrack(int target_level)
int CurrentDecisionLevel() const
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
const Trail & LiteralTrail() const
BopOptimizerBase::Status LoadStateProblemToSatSolver(const ProblemState &problem_state, sat::SatSolver *sat_solver)
util_intops::StrongVector< SparseIndex, BopConstraintTerm > BopConstraintTerms
void SatAssignmentToBopSolution(const sat::VariablesAssignment &assignment, BopSolution *solution)
void ExtractLearnedInfoFromSatSolver(sat::SatSolver *solver, LearnedInfo *info)
StrictITIVector< ColIndex, Fractional > DenseRow
bool LoadBooleanProblem(const LinearBooleanProblem &problem, SatSolver *solver)
void ConvertBooleanProblemToLinearProgram(const LinearBooleanProblem &problem, glop::LinearProgram *lp)
bool AddObjectiveUpperBound(const LinearBooleanProblem &problem, Coefficient upper_bound, SatSolver *solver)
void UseObjectiveForSatAssignmentPreference(const LinearBooleanProblem &problem, SatSolver *solver)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
#define SCOPED_TIME_STAT(stats)