22#include "absl/cleanup/cleanup.h"
23#include "absl/log/check.h"
24#include "absl/random/bit_gen_ref.h"
25#include "absl/random/distributions.h"
26#include "absl/strings/string_view.h"
30#include "ortools/bop/bop_parameters.pb.h"
38#include "ortools/sat/boolean_problem.pb.h"
42#include "ortools/sat/sat_parameters.pb.h"
51using ::operations_research::glop::ColIndex;
52using ::operations_research::glop::DenseRow;
53using ::operations_research::sat::LinearBooleanConstraint;
54using ::operations_research::sat::LinearBooleanProblem;
61void UseBopSolutionForSatAssignmentPreference(
const BopSolution&
solution,
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(
124 const ProblemState& problem_state)
const {
125 return problem_state.solution().IsFeasible();
129 const BopParameters&
parameters,
const ProblemState& problem_state,
130 LearnedInfo* learned_info, TimeLimit*
time_limit) {
132 CHECK(learned_info !=
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(
182 const BopParameters&
parameters,
const LinearBooleanProblem& problem,
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;
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),
228 absl::string_view
name,
bool use_lp_to_guide_sat,
229 NeighborhoodGenerator* neighborhood_generator,
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,
250 CHECK(learned_info !=
nullptr);
252 learned_info->Clear();
256 auto sat_propagator_cleanup =
257 ::absl::MakeCleanup([initial_dt,
this, &learned_info, &
time_limit]() {
275 const BopParameters& local_parameters =
parameters;
278 num_tries < local_parameters.num_random_lns_tries()) {
282 neighborhood_generator_->GenerateNeighborhood(problem_state, difficulty,
286 VLOG(2) << num_tries <<
" difficulty:" << difficulty
287 <<
" luby:" << adaptive_difficulty_.
luby_value()
289 << problem_state.original_problem().num_variables();
294 VLOG(2) <<
"Nothing fixed!";
304 sat::SatParameters params;
305 params.set_max_number_of_conflicts(
306 local_parameters.max_number_of_conflicts_for_quick_check());
307 params.set_max_time_in_seconds(
time_limit->GetTimeLeft());
308 params.set_max_deterministic_time(
time_limit->GetDeterministicTimeLeft());
309 params.set_random_seed(
parameters.random_seed());
318 &learned_info->solution);
339 return problem_state.solution().IsFeasible()
350 const int conflict_limit =
352 parameters.max_number_of_conflicts_in_random_lns();
354 sat::SatParameters params;
355 params.set_max_number_of_conflicts(conflict_limit);
356 params.set_max_time_in_seconds(
time_limit->GetTimeLeft());
357 params.set_max_deterministic_time(
time_limit->GetDeterministicTimeLeft());
358 params.set_random_seed(
parameters.random_seed());
360 sat::SatSolver sat_solver;
361 sat_solver.SetParameters(params);
364 const LinearBooleanProblem& problem = problem_state.original_problem();
365 sat_solver.SetNumVariables(problem.num_variables());
367 CHECK(sat_solver.AddUnitClause(sat_propagator_->
LiteralTrail()[i]));
382 if (use_lp_to_guide_sat_) {
383 if (!UseLinearRelaxationForSatAssignmentPreference(
392 problem, sat::Coefficient(problem_state.solution().GetCost()) - 1,
401 time_limit->AdvanceDeterministicTime(sat_solver.deterministic_time());
405 &learned_info->solution);
410 if (sat_solver.num_failures() < 0.5 * conflict_limit) {
412 }
else if (sat_solver.num_failures() > 0.95 * conflict_limit) {
426std::vector<sat::Literal> ObjectiveVariablesAssignedToTheirLowCostValue(
427 const ProblemState& problem_state,
429 std::vector<sat::Literal> result;
430 DCHECK(problem_state.solution().IsFeasible());
431 for (
const BopConstraintTerm& term : objective_terms) {
432 if (((problem_state.solution().Value(term.var_id) && term.weight < 0) ||
433 (!problem_state.solution().Value(term.var_id) && term.weight > 0))) {
435 sat::Literal(sat::BooleanVariable(term.var_id.value()),
436 problem_state.solution().Value(term.var_id)));
444void ObjectiveBasedNeighborhood::GenerateNeighborhood(
445 const ProblemState& problem_state,
double difficulty,
446 sat::SatSolver* sat_propagator) {
448 std::vector<sat::Literal> candidates =
449 ObjectiveVariablesAssignedToTheirLowCostValue(problem_state,
451 std::shuffle(candidates.begin(), candidates.end(), random_);
455 const int num_variables = sat_propagator->NumVariables();
456 const int target = round((1.0 - difficulty) * num_variables);
458 sat_propagator->Backtrack(0);
459 for (
const sat::Literal
literal : candidates) {
460 if (sat_propagator->LiteralTrail().Index() == target)
break;
461 if (sat_propagator->LiteralTrail().Index() > target) {
464 sat_propagator->Backtrack(
465 std::max(0, sat_propagator->CurrentDecisionLevel() - 1));
468 sat_propagator->EnqueueDecisionAndBacktrackOnConflict(
literal);
469 if (sat_propagator->IsModelUnsat())
return;
473void ConstraintBasedNeighborhood::GenerateNeighborhood(
474 const ProblemState& problem_state,
double difficulty,
475 sat::SatSolver* sat_propagator) {
477 const LinearBooleanProblem& problem = problem_state.original_problem();
478 const int num_constraints = problem.constraints_size();
479 std::vector<int> ct_ids(num_constraints, 0);
480 for (
int ct_id = 0; ct_id < num_constraints; ++ct_id) ct_ids[ct_id] = ct_id;
481 std::shuffle(ct_ids.begin(), ct_ids.end(), random_);
485 const int num_variables = sat_propagator->NumVariables();
486 const int target = round(difficulty * num_variables);
488 std::vector<bool> variable_is_relaxed(problem.num_variables(),
false);
489 for (
int i = 0;
i < ct_ids.size(); ++
i) {
490 if (num_relaxed >= target)
break;
491 const LinearBooleanConstraint& constraint = problem.constraints(ct_ids[i]);
495 if (constraint.literals_size() > 0.7 * num_variables)
continue;
497 for (
int j = 0; j < constraint.literals_size(); ++j) {
498 const VariableIndex var_id(constraint.literals(j) - 1);
499 if (!variable_is_relaxed[var_id.value()]) {
501 variable_is_relaxed[var_id.value()] =
true;
511 sat_propagator->Backtrack(0);
512 const std::vector<sat::Literal> to_fix =
513 ObjectiveVariablesAssignedToTheirLowCostValue(problem_state,
515 for (
const sat::Literal
literal : to_fix) {
516 if (variable_is_relaxed[
literal.Variable().value()])
continue;
517 sat_propagator->EnqueueDecisionAndBacktrackOnConflict(
literal);
518 if (sat_propagator->IsModelUnsat())
return;
523 const LinearBooleanProblem& problem, absl::BitGenRef random)
525 const int num_variables = problem.num_variables();
526 columns_.resize(num_variables);
535 const double kSizeThreshold = 0.1;
536 for (
int i = 0; i < problem.constraints_size(); ++i) {
537 const LinearBooleanConstraint& constraint = problem.constraints(i);
538 if (constraint.literals_size() > kSizeThreshold * num_variables)
continue;
539 for (
int j = 0; j < constraint.literals_size(); ++j) {
541 columns_[VariableIndex(
literal.Variable().value())].push_back(
547void RelationGraphBasedNeighborhood::GenerateNeighborhood(
548 const ProblemState& problem_state,
double difficulty,
551 const int num_variables = sat_propagator->
NumVariables();
552 const int target = round(difficulty * num_variables);
554 std::vector<bool> variable_is_relaxed(num_variables,
false);
555 std::deque<int> queue;
560 queue.push_back(absl::Uniform(random_, 0, num_variables));
561 variable_is_relaxed[queue.back()] =
true;
562 while (!queue.empty() && num_relaxed < target) {
563 const int var = queue.front();
565 for (ConstraintIndex
ct_index : columns_[VariableIndex(
var)]) {
566 const LinearBooleanConstraint& constraint =
567 problem_state.original_problem().constraints(
ct_index.value());
568 for (
int i = 0; i < constraint.literals_size(); ++i) {
570 const int next_var =
literal.Variable().value();
571 if (!variable_is_relaxed[next_var]) {
573 variable_is_relaxed[next_var] =
true;
574 queue.push_back(next_var);
582 DCHECK(problem_state.solution().IsFeasible());
584 for (sat::BooleanVariable
var(0);
var < num_variables; ++
var) {
586 var, problem_state.solution().Value(VariableIndex(
var.value())));
587 if (variable_is_relaxed[
literal.Variable().value()])
continue;
592 if (variable_is_relaxed
593 [sat_propagator->
LiteralTrail()[i].Variable().value()]) {
600 VLOG(2) <<
"target:" << target <<
" relaxed:" << num_relaxed <<
" actual:"
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)
@ ABORT
There is no need to call this optimizer again on the same problem state.
bool Value(VariableIndex var) const
double GetParameterValue() const
int64_t update_stamp() const
const BopSolution & solution() const
RelationGraphBasedNeighborhood(const sat::LinearBooleanProblem &problem, absl::BitGenRef random)
Status EnqueueDecisionAndBacktrackOnConflict(Literal true_literal, int *first_propagation_index=nullptr)
double deterministic_time() const
bool RestoreSolverToAssumptionLevel()
void Backtrack(int target_level)
bool IsModelUnsat() const
void SetAssumptionLevel(int assumption_level)
const VariablesAssignment & Assignment() const
int CurrentDecisionLevel() const
const Trail & LiteralTrail() const
void SetParameters(const SatParameters ¶meters)
const std::string name
A name for logging purposes.
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)
ProblemStatus
Different statuses for a given problem.
bool LoadBooleanProblem(const LinearBooleanProblem &problem, SatSolver *solver)
Loads a BooleanProblem into a given SatSolver instance.
void ConvertBooleanProblemToLinearProgram(const LinearBooleanProblem &problem, glop::LinearProgram *lp)
Converts a Boolean optimization problem to its lp formulation.
bool AddObjectiveUpperBound(const LinearBooleanProblem &problem, Coefficient upper_bound, SatSolver *solver)
Adds the constraint that the objective is smaller than the given upper bound.
void UseObjectiveForSatAssignmentPreference(const LinearBooleanProblem &problem, SatSolver *solver)
In SWIG mode, we don't want anything besides these top-level includes.
#define SCOPED_TIME_STAT(stats)
Represents a term in a pseudo-Boolean formula.