14#ifndef OR_TOOLS_SAT_FEASIBILITY_JUMP_H_
15#define OR_TOOLS_SAT_FEASIBILITY_JUMP_H_
28#include "absl/container/flat_hash_map.h"
29#include "absl/functional/any_invocable.h"
30#include "absl/log/check.h"
31#include "absl/random/distributions.h"
32#include "absl/strings/str_join.h"
33#include "absl/strings/string_view.h"
34#include "absl/synchronization/mutex.h"
35#include "absl/types/span.h"
40#include "ortools/sat/sat_parameters.pb.h"
61 absl::AnyInvocable<std::pair<int64_t, double>(
int)
const> compute_jump);
66 std::pair<int64_t, double>
GetJump(
int var);
70 void SetJump(
int var, int64_t delta,
double score);
77 double Score(
int var)
const {
return scores_[var]; }
81 absl::Span<const int64_t>
Deltas()
const {
82 return absl::MakeConstSpan(deltas_);
84 absl::Span<const double>
Scores()
const {
85 return absl::MakeConstSpan(scores_);
97 absl::AnyInvocable<std::pair<int64_t, double>(
int)
const> compute_jump_;
104 std::vector<int64_t> deltas_;
105 std::vector<double> scores_;
106 std::vector<bool> needs_recomputation_;
115 shared_bounds == nullptr ? 0 : shared_bounds->RegisterNewId()),
116 shared_bounds_(shared_bounds) {}
120 size_t size()
const {
return domains_.size(); }
123 domains_.resize(num_vars);
124 has_two_values_.resize(num_vars);
125 is_fixed_.resize(num_vars,
false);
126 objective_is_positive_.resize(num_vars,
false);
127 objective_is_negative_.resize(num_vars,
false);
128 has_better_objective_value_.resize(num_vars,
false);
133 if (is_fixed_[var]) {
137 is_fixed_[var] =
true;
138 fixed_vars_.push_back(var);
140 domains_[var] = std::move(d);
146 if (shared_bounds_ ==
nullptr)
return true;
147 shared_bounds_->GetChangedBounds(shared_bounds_id_, &tmp_variables_,
148 &tmp_new_lower_bounds_,
149 &tmp_new_upper_bounds_);
150 for (
int i = 0;
i < tmp_variables_.size(); ++
i) {
151 const int var = tmp_variables_[
i];
153 Domain(tmp_new_lower_bounds_[
i], tmp_new_upper_bounds_[
i]));
154 if (new_domain.
IsEmpty())
return false;
155 Set(var, new_domain);
160 absl::Span<const Domain>
AsSpan()
const {
return domains_; }
163 if (!cp_model_proto.has_objective())
return;
164 const int num_terms = cp_model_proto.objective().vars().size();
165 for (
int i = 0;
i < num_terms; ++
i) {
166 const int var = cp_model_proto.objective().vars(
i);
167 const int coeff = cp_model_proto.objective().coeffs(
i);
168 objective_is_positive_[var] = coeff > 0;
169 objective_is_negative_[var] = coeff < 0;
173 bool IsFixed(
int var)
const {
return is_fixed_[var]; }
176 return has_better_objective_value_[var];
181 has_better_objective_value_[var] =
182 (objective_is_positive_[var] && value > domains_[var].Min()) ||
183 (objective_is_negative_[var] && value < domains_[var].Max());
189 const int shared_bounds_id_;
193 std::vector<bool> objective_is_positive_;
194 std::vector<bool> objective_is_negative_;
197 std::vector<Domain> domains_;
198 std::vector<bool> has_two_values_;
199 std::vector<bool> is_fixed_;
200 std::vector<int> fixed_vars_;
203 std::vector<bool> has_better_objective_value_;
206 std::vector<int> tmp_variables_;
207 std::vector<int64_t> tmp_new_lower_bounds_;
208 std::vector<int64_t> tmp_new_upper_bounds_;
257 std::vector<absl::string_view> parts;
264 return absl::StrJoin(parts,
"_");
268 template <
typename H>
284 absl::Bernoulli(*random, 0.5)
286 : params.feasibility_jump_var_randomization_probability();
287 use_decay = absl::Bernoulli(*random, 0.5);
304 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
324 std::unique_ptr<CompoundMoveBuilder>
move;
355 : name_(name), params_(params), stat_tables_(stat_tables) {
364 const int index = states_.size();
365 states_.emplace_back(
new LsState());
366 taken_.push_back(
false);
367 num_selected_.push_back(0);
370 states_.back()->options.use_restart = (index % 16 != 15);
377 absl::MutexLock mutex_lock(&mutex_);
379 const int num_states = states_.size();
380 for (
int i = 0;
i < num_states; ++
i) {
381 const int index = round_robin_index_;
382 round_robin_index_ = (round_robin_index_ + 1) % num_states;
383 if (taken_[index])
continue;
384 if (next == -1 || num_selected_[index] < num_selected_[next]) {
395 --states_[next]->num_batches_before_change;
397 num_selected_[next]++;
398 return states_[next].get();
402 absl::MutexLock mutex_lock(&mutex_);
403 for (
int i = 0;
i < states_.size(); ++
i) {
404 if (state == states_[
i].get()) {
412 absl::MutexLock mutex_lock(&mutex_);
423 absl::MutexLock mutex_lock(&mutex_);
424 const int factor = std::max(1, params_.feasibility_jump_restart_factor());
426 const int64_t next = factor *
SUniv(++luby_counter_);
434 absl::MutexLock mutex_lock(&mutex_);
436 options_to_num_restarts_[state.
options]++;
440 const std::string name_;
441 const SatParameters& params_;
444 mutable absl::Mutex mutex_;
445 int round_robin_index_ = 0;
446 std::vector<std::unique_ptr<LsState>> states_;
447 std::vector<bool> taken_;
448 std::vector<int> num_selected_;
449 int luby_counter_ = 0;
451 absl::flat_hash_map<LsOptions, LsCounters> options_to_stats_;
452 absl::flat_hash_map<LsOptions, int> options_to_num_restarts_;
470 const LinearModel* linear_model, SatParameters params,
471 std::shared_ptr<SharedLsStates> ls_states,
479 linear_model_(linear_model),
481 states_(
std::move(ls_states)),
482 shared_time_limit_(shared_time_limit),
483 shared_response_(shared_response),
484 shared_hints_(shared_hints),
485 stat_tables_(stat_tables),
487 var_domains_(shared_bounds) {}
497 if (!model_is_supported_.load())
return true;
501 shared_response_->first_solution_solvers_should_stop()->load();
505 if (
IsDone())
return false;
506 if (task_generated_.load())
return false;
507 if (shared_response_->ProblemIsSolved())
return false;
508 if (shared_time_limit_->LimitReached())
return false;
510 return (shared_response_->SolutionsRepository().NumSolutions() > 0) ==
521 void ResetCurrentSolution(
bool use_hint,
bool use_objective,
522 double perturbation_probability);
523 void PerturbateCurrentSolution(
double perturbation_probability);
524 std::string OneLineStats()
const;
526 absl::Span<const double> ScanWeights()
const {
533 double ComputeScore(absl::Span<const double> weights,
int var, int64_t delta,
538 std::pair<int64_t, double> ComputeLinearJump(
int var);
542 std::pair<int64_t, double> ComputeGeneralJump(
int var);
546 void MarkJumpsThatNeedToBeRecomputed(
int changed_var);
549 bool DoSomeLinearIterations();
550 bool DoSomeGeneralIterations();
553 bool ScanRelevantVariables(
int num_to_scan,
int* var, int64_t* value,
558 void UpdateViolatedConstraintWeights();
563 bool ShouldScan(
int var)
const;
565 void AddVarToScan(
int var);
566 void RecomputeVarsToScan();
572 void ResetChangedCompoundWeights();
578 bool ShouldExtendCompoundMove(
double score,
double novelty);
582 bool SlowCheckNumViolatedConstraints()
const;
584 double DeterministicTime()
const {
585 return evaluator_->DeterministicTime() + num_ops_ * 1e-8;
588 const LinearModel* linear_model_;
589 SatParameters params_;
590 std::shared_ptr<SharedLsStates> states_;
591 ModelSharedTimeLimit* shared_time_limit_;
592 SharedResponseManager* shared_response_;
593 SharedLsSolutionRepository* shared_hints_;
594 SharedStatTables* stat_tables_;
595 ModelRandomGenerator random_;
597 VarDomainWrapper var_domains_;
603 bool is_initialized_ =
false;
604 std::atomic<bool> model_is_supported_ =
true;
605 std::atomic<bool> task_generated_ =
false;
606 bool time_limit_crossed_ =
false;
608 std::unique_ptr<LsEvaluator> evaluator_;
609 std::vector<bool> var_occurs_in_non_linear_constraint_;
612 std::vector<double> for_weight_update_;
618 std::vector<bool> in_vars_to_scan_;
619 FixedCapacityVector<int> vars_to_scan_;
621 std::vector<int64_t> tmp_breakpoints_;
624 int64_t num_ops_ = 0;
634 : var_on_stack_(num_variables, false) {}
638 void Push(
int var, int64_t prev_value,
double score);
643 bool Backtrack(
int* var, int64_t* value,
double* score);
649 int Size()
const {
return stack_.size(); }
656 return stack_.empty() ? 0.0 : stack_.back().cumulative_score;
660 return stack_.empty() ? 0.0 : stack_.back().best_child_score;
665 return stack_.empty() ? 0 : stack_.back().discrepancy;
679 double cumulative_score;
684 double best_child_score = 0.0;
687 std::vector<bool> var_on_stack_;
688 std::vector<UnitMove> stack_;
Domain IntersectionWith(const Domain &domain) const
bool HasTwoValues() const
CompoundMoveBuilder(int num_variables)
bool StackValuesInDomains(absl::Span< const Domain > var_domains) const
Returns true if all prev_values on the stack are in the appropriate domain.
double Score() const
Returns the sum of scores of atomic moved pushed to this compound move.
void Clear()
Removes all moves on the stack.
int Size() const
Returns the number of variables in the move.
int Discrepancy() const
Returns the number of backtracks to any ancestor of the current leaf.
bool OnStack(int var) const
Returns true if this var has been set in this move already,.
bool Backtrack(int *var, int64_t *value, double *score)
double BestChildScore() const
void Push(int var, int64_t prev_value, double score)
bool TaskIsAvailable() final
~FeasibilityJumpSolver() override
If VLOG_IS_ON(1), it will export a bunch of statistics.
bool IsDone() final
Shall we delete this subsolver?
FeasibilityJumpSolver(const absl::string_view name, SubSolver::SubsolverType type, const LinearModel *linear_model, SatParameters params, std::shared_ptr< SharedLsStates > ls_states, ModelSharedTimeLimit *shared_time_limit, SharedResponseManager *shared_response, SharedBoundsManager *shared_bounds, SharedLsSolutionRepository *shared_hints, SharedStatistics *shared_stats, SharedStatTables *stat_tables)
void Synchronize() final
No synchronization needed for TaskIsAvailable().
std::function< void()> GenerateTask(int64_t) final
void Recompute(int var)
Recompute the jump for var when GetJump(var) is next called.
void RecomputeAll(int num_variables)
std::pair< int64_t, double > GetJump(int var)
Gets the current jump delta and score, recomputing if necessary.
absl::Span< const double > Scores() const
absl::Span< const int64_t > Deltas() const
double Score(int var) const
absl::Span< double > MutableScores()
void SetComputeFunction(absl::AnyInvocable< std::pair< int64_t, double >(int) const > compute_jump)
bool NeedRecomputation(int var) const
bool JumpIsUpToDate(int var) const
void SetJump(int var, int64_t delta, double score)
The model "singleton" shared time limit.
void CollectStatistics(const LsState &state)
Accumulate in the relevant bucket the counters of the given states.
SharedLsStates(absl::string_view name, const SatParameters ¶ms, SharedStatTables *stat_tables)
void ConfigureNextLubyRestart(LsState *state)
void Release(LsState *state)
Contains the table we display after the solver is done.
Simple class to add statistics by name and print them at the end.
SubSolver(absl::string_view name, SubsolverType type)
SubsolverType type() const
Returns the type of the subsolver.
std::string name() const
Returns the name of this SubSolver. Used in logs.
bool HasBetterObjectiveValue(int var) const
bool UpdateFromSharedBounds()
void resize(int num_vars)
VarDomainWrapper(SharedBoundsManager *shared_bounds)
void Set(int var, Domain d)
bool HasTwoValues(int var) const
void InitializeObjective(const CpModelProto &cp_model_proto)
absl::Span< const Domain > AsSpan() const
bool IsFixed(int var) const
Domain operator[](int var) const
void OnValueChange(int var, int64_t value)
Tricky: this must be called on solution value change or domains update.
absl::Span< const int > FixedVariables() const
int64_t num_perturbations
int64_t num_weight_updates
int64_t num_compound_moves
void AddFrom(const LsCounters &o)
int64_t num_general_moves
int64_t num_scores_computed
int64_t num_general_evals
The parameters used by the local search code.
void Randomize(const SatParameters ¶ms, ModelRandomGenerator *random)
bool operator==(const LsOptions &o) const
friend H AbslHashValue(H h, const LsOptions &o)
In order to collect statistics by options.
double perturbation_probability
These are randomized each restart by Randomize().
std::string name() const
Allows to identify which options worked well.
std::vector< double > compound_weights
std::vector< int64_t > solution
IntegerValue saved_inner_objective_lb
LsCounters counters
Counters for a "non-restarted" run.
IntegerValue saved_inner_objective_ub
int64_t num_solutions_imported
int compound_move_max_discrepancy
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > base_solution
std::vector< double > weights
int64_t last_solution_rank
Used by LS to know the rank of the starting solution for this state.
LsOptions options
Strategy.
std::unique_ptr< CompoundMoveBuilder > move
int64_t num_batches_before_change
When this reach zero, we restart / perturbate or trigger something.
int64_t num_restarts
Global counters, incremented across restart.
std::vector< int > compound_weight_changed
std::vector< bool > in_compound_weight_changed