30#include "absl/functional/any_invocable.h"
31#include "absl/functional/bind_front.h"
32#include "absl/functional/function_ref.h"
33#include "absl/log/check.h"
34#include "absl/random/bit_gen_ref.h"
35#include "absl/random/distributions.h"
36#include "absl/strings/str_cat.h"
37#include "absl/types/span.h"
42#include "ortools/sat/cp_model.pb.h"
47#include "ortools/sat/sat_parameters.pb.h"
58constexpr double kCompoundDiscount = 1. / 1024;
62 absl::AnyInvocable<std::pair<int64_t, double>(
int)
const> compute_jump) {
63 compute_jump_ = std::move(compute_jump);
67 deltas_.resize(num_variables);
68 scores_.resize(num_variables);
69 needs_recomputation_.assign(num_variables,
true);
75 needs_recomputation_[var] =
false;
81 const auto& [delta, score] = compute_jump_(var);
82 if (delta != deltas_[var]) {
83 LOG(ERROR) <<
"Incorrect delta for var " << var <<
": " << deltas_[var]
84 <<
" (should be " << delta <<
")";
87 if (abs(score - scores_[var]) / std::max(abs(score), 1.0) > 1e-2) {
89 LOG(ERROR) <<
"Incorrect score for var " << var <<
": " << scores_[var]
90 <<
" (should be " << score <<
") "
91 <<
" delta = " << delta;
93 return delta == deltas_[var] && score_ok;
97 if (needs_recomputation_[var]) {
98 needs_recomputation_[var] =
false;
99 std::tie(deltas_[var], scores_[var]) = compute_jump_(var);
101 return std::make_pair(deltas_[var], scores_[var]);
106 for (
int i = 0;
i < states_.size(); ++
i) {
111 for (
const auto& [options, counters] : options_to_stats_) {
112 stat_tables_->AddLsStat(
113 absl::StrCat(name_,
"_", options.name()), counters.num_batches,
114 options_to_num_restarts_[options] + counters.num_perturbations,
115 counters.num_linear_moves, counters.num_general_moves,
116 counters.num_compound_moves, counters.num_backtracks,
117 counters.num_weight_updates, counters.num_scores_computed);
122 stat_tables_->AddTimingStat(*
this);
125void FeasibilityJumpSolver::ImportState() {
126 state_ = states_->GetNextState();
127 if (state_->
move ==
nullptr) {
128 const int num_variables = var_domains_.
size();
129 state_->
move = std::make_unique<CompoundMoveBuilder>(num_variables);
133void FeasibilityJumpSolver::ReleaseState() { states_->Release(state_); }
135void FeasibilityJumpSolver::Initialize() {
136 is_initialized_ =
true;
140 if (params_.feasibility_jump_linearization_level() == 0) {
142 std::make_unique<LsEvaluator>(linear_model_->model_proto(), params_);
145 std::make_unique<LsEvaluator>(linear_model_->model_proto(), params_,
146 linear_model_->ignored_constraints(),
147 linear_model_->additional_constraints());
150 const int num_variables = linear_model_->model_proto().variables().size();
151 var_domains_.resize(num_variables);
152 for (
int v = 0; v < num_variables; ++v) {
156 var_domains_.InitializeObjective(linear_model_->model_proto());
158 vars_to_scan_.ClearAndReserve(num_variables);
159 var_occurs_in_non_linear_constraint_.resize(num_variables);
160 for (
int c = 0;
c < evaluator_->NumNonLinearConstraints(); ++
c) {
161 for (
int v : evaluator_->GeneralConstraintToVars(c)) {
162 var_occurs_in_non_linear_constraint_[v] =
true;
169int64_t ComputeRange(int64_t range,
double range_ratio) {
170 return static_cast<int64_t
>(
171 std::ceil(
static_cast<double>(range) * range_ratio));
176int64_t RandomValueNearMin(
const Domain& domain,
double range_ratio,
177 absl::BitGenRef random) {
178 if (domain.Size() == 1)
return domain.FixedValue();
179 if (domain.Size() == 2) {
180 return absl::Bernoulli(random, 1 - range_ratio) ? domain.
Min()
183 const int64_t range = ComputeRange(domain.
Max() - domain.
Min(), range_ratio);
184 return domain.ValueAtOrBefore(domain.
Min() +
185 absl::LogUniform<int64_t>(random, 0, range));
188int64_t RandomValueNearMax(
const Domain& domain,
double range_ratio,
189 absl::BitGenRef random) {
190 if (domain.Size() == 1)
return domain.FixedValue();
191 if (domain.Size() == 2) {
192 return absl::Bernoulli(random, 1 - range_ratio) ? domain.
Max()
195 const int64_t range = ComputeRange(domain.
Max() - domain.
Min(), range_ratio);
196 return domain.ValueAtOrAfter(domain.
Max() -
197 absl::LogUniform<int64_t>(random, 0, range));
200int64_t RandomValueNearValue(
const Domain& domain, int64_t value,
201 double range_ratio, absl::BitGenRef random) {
202 DCHECK(!domain.IsFixed());
204 if (domain.
Min() >= value) {
205 return RandomValueNearMin(domain, range_ratio, random);
208 if (domain.
Max() <= value) {
209 return RandomValueNearMax(domain, range_ratio, random);
213 const Domain greater_domain =
214 domain.IntersectionWith({value + 1, domain.
Max()});
215 const double choose_greater_probability =
216 static_cast<double>(greater_domain.Size()) /
217 static_cast<double>(domain.Size() - 1);
218 if (absl::Bernoulli(random, choose_greater_probability)) {
219 return RandomValueNearMin(greater_domain, range_ratio, random);
221 return RandomValueNearMax(
222 domain.IntersectionWith({domain.Min(), value - 1}), range_ratio,
229void FeasibilityJumpSolver::ResetCurrentSolution(
230 bool use_hint,
bool use_objective,
double perturbation_probability) {
231 const int num_variables = linear_model_->model_proto().variables().size();
232 const double default_value_probability = 1.0 - perturbation_probability;
233 const double range_ratio =
234 params_.feasibility_jump_var_perburbation_range_ratio();
235 std::vector<int64_t>&
solution = state_->solution;
236 state_->base_solution =
nullptr;
242 for (
int var = 0; var < num_variables; ++var) {
243 if (var_domains_[var].
IsFixed()) {
244 solution[var] = var_domains_[var].FixedValue();
248 if (absl::Bernoulli(random_, default_value_probability)) {
249 solution[var] = var_domains_[var].SmallestValue();
252 RandomValueNearValue(var_domains_[var], 0, range_ratio, random_);
257 if (use_objective && linear_model_->model_proto().has_objective()) {
258 const int num_terms =
259 linear_model_->model_proto().objective().vars().size();
260 for (
int i = 0;
i < num_terms; ++
i) {
261 const int var = linear_model_->model_proto().objective().vars(
i);
262 if (var_domains_[var].
IsFixed())
continue;
264 if (linear_model_->model_proto().objective().coeffs(
i) > 0) {
265 if (absl::Bernoulli(random_, default_value_probability)) {
266 solution[var] = var_domains_[var].Min();
269 RandomValueNearMin(var_domains_[var], range_ratio, random_);
272 if (absl::Bernoulli(random_, default_value_probability)) {
273 solution[var] = var_domains_[var].Max();
276 RandomValueNearMax(var_domains_[var], range_ratio, random_);
282 if (use_hint && linear_model_->model_proto().has_solution_hint()) {
283 const auto& hint = linear_model_->model_proto().solution_hint();
284 for (
int i = 0;
i < hint.vars().size(); ++
i) {
290void FeasibilityJumpSolver::PerturbateCurrentSolution(
291 double perturbation_probability) {
292 if (perturbation_probability == 0.0)
return;
293 const int num_variables = linear_model_->model_proto().variables().size();
294 const double perturbation_ratio =
295 params_.feasibility_jump_var_perburbation_range_ratio();
296 std::vector<int64_t>&
solution = state_->solution;
297 for (
int var = 0; var < num_variables; ++var) {
298 if (var_domains_[var].
IsFixed())
continue;
299 if (absl::Bernoulli(random_, perturbation_probability)) {
301 perturbation_ratio, random_);
306std::string FeasibilityJumpSolver::OneLineStats()
const {
308 const std::string general_str =
309 state_->counters.num_general_evals == 0 &&
310 state_->counters.num_general_moves == 0
313 " gen{mvs:",
FormatCounter(state_->counters.num_general_moves),
314 " evals:",
FormatCounter(state_->counters.num_general_evals),
316 const std::string compound_str =
317 state_->counters.num_compound_moves == 0 &&
318 state_->counters.num_backtracks == 0
320 : absl::StrCat(
" comp{mvs:",
326 const int num_infeasible_cts = evaluator_->NumInfeasibleConstraints();
327 const std::string non_solution_str =
328 num_infeasible_cts == 0
330 : absl::StrCat(
" #good_moves:",
FormatCounter(vars_to_scan_.size()),
335 "batch:", state_->counters.num_batches,
336 " lin{mvs:",
FormatCounter(state_->counters.num_linear_moves),
337 " evals:",
FormatCounter(state_->counters.num_linear_evals),
"}",
338 general_str, compound_str, non_solution_str,
339 " #w_updates:",
FormatCounter(state_->counters.num_weight_updates),
340 " #perturb:",
FormatCounter(state_->counters.num_perturbations));
344 task_generated_ =
true;
349 if (!is_initialized_) Initialize();
356 bool new_best_solution_was_found =
false;
359 shared_response_->SolutionsRepository().GetBestRank();
360 if (best < state_->last_solution_rank) {
361 states_->ResetLubyCounter();
362 new_best_solution_was_found =
true;
363 state_->last_solution_rank = best;
367 bool reset_weights =
false;
368 if (new_best_solution_was_found || state_->num_batches_before_change <= 0) {
369 reset_weights =
true;
370 if (state_->options.use_restart) {
371 states_->CollectStatistics(*state_);
372 state_->options.Randomize(params_, &random_);
375 state_->options.Randomize(params_, &random_);
380 state_->options.use_objective =
false;
383 const bool first_time = (state_->num_restarts == 0);
384 if (state_->options.use_restart || first_time ||
385 new_best_solution_was_found) {
388 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
389 solution = shared_response_->SolutionsRepository()
390 .GetRandomBiasedSolution(random_);
391 state_->solution =
solution->variable_values;
393 ++state_->num_solutions_imported;
397 const int num_violations = evaluator_->ViolatedConstraints().size();
398 shared_hints_->AddSolution(state_->solution, num_violations);
400 ResetCurrentSolution(first_time,
401 state_->options.use_objective,
402 state_->options.perturbation_probability);
405 PerturbateCurrentSolution(
406 params_.feasibility_jump_var_randomization_probability());
409 if (state_->options.use_restart) {
410 ++state_->num_restarts;
411 states_->ConfigureNextLubyRestart(state_);
414 ++state_->counters.num_perturbations;
415 state_->num_batches_before_change =
416 params_.violation_ls_perturbation_period();
421 bool recompute_compound_weights =
false;
422 if (linear_model_->model_proto().has_objective()) {
423 const IntegerValue lb = shared_response_->GetInnerObjectiveLowerBound();
424 const IntegerValue ub = shared_response_->GetInnerObjectiveUpperBound();
425 if (lb != state_->saved_inner_objective_lb ||
426 ub != state_->saved_inner_objective_ub) {
427 recompute_compound_weights =
true;
429 state_->saved_inner_objective_lb = lb;
430 state_->saved_inner_objective_ub = ub;
433 if (evaluator_->ReduceObjectiveBounds(lb.value(), ub.value())) {
434 recompute_compound_weights =
true;
439 if (!var_domains_.UpdateFromSharedBounds())
return;
445 const int num_vars = state_->solution.size();
446 for (
int var = 0; var < num_vars; ++var) {
447 const int64_t old_value = state_->solution[var];
448 const int64_t new_value = var_domains_[var].ClosestValue(old_value);
449 if (new_value != old_value) {
450 state_->solution[var] = new_value;
451 recompute_compound_weights =
true;
453 var_domains_.OnValueChange(var, new_value);
456 if (!state_->move->StackValuesInDomains(var_domains_.AsSpan())) {
457 recompute_compound_weights =
true;
464 evaluator_->ComputeAllViolations(state_->solution);
467 state_->bump_value = 1.0;
468 state_->weights.assign(evaluator_->NumEvaluatorConstraints(), 1.0);
469 recompute_compound_weights =
true;
471 if (recompute_compound_weights) {
472 state_->move->Clear();
473 if (state_->options.use_compound_moves) {
474 state_->compound_weights.assign(state_->weights.begin(),
475 state_->weights.end());
476 for (
int c = 0; c < state_->weights.size(); ++c) {
477 if (evaluator_->IsViolated(c))
continue;
478 state_->compound_weights[c] *= kCompoundDiscount;
480 state_->compound_weight_changed.clear();
481 state_->in_compound_weight_changed.assign(state_->weights.size(),
483 state_->compound_move_max_discrepancy = 0;
487 if (!state_->options.use_compound_moves) {
488 DCHECK_EQ(state_->move->Size(), 0);
491 ++state_->counters.num_batches;
492 if (DoSomeLinearIterations() && DoSomeGeneralIterations()) {
496 shared_response_, linear_model_->model_proto(), state_->solution,
497 absl::StrCat(
name(),
"_", state_->options.name(),
"(",
498 OneLineStats(),
")"),
499 state_->base_solution ==
nullptr
500 ? absl::Span<const int64_t>()
501 : state_->base_solution->variable_values,
505 state_->base_solution = pointers.pushed_solution;
507 shared_response_->LogMessage(
name(),
"infeasible solution. Aborting.");
508 model_is_supported_ =
false;
518 const double current_dtime = DeterministicTime();
526 shared_time_limit_->AdvanceDeterministicTime(delta);
531 task_generated_ =
false;
535double FeasibilityJumpSolver::ComputeScore(absl::Span<const double> weights,
536 int var, int64_t delta,
539 double score = evaluator_->WeightedViolationDelta(
540 linear_only, weights, var, delta, absl::MakeSpan(state_->
solution));
541 constexpr double kEpsilon = 1.0 / std::numeric_limits<int64_t>::max();
542 score += kEpsilon * delta * evaluator_->ObjectiveCoefficient(var);
546std::pair<int64_t, double> FeasibilityJumpSolver::ComputeLinearJump(
int var) {
547 DCHECK(!var_domains_[var].
IsFixed());
548 const int64_t current_value = state_->
solution[var];
551 const LinearIncrementalEvaluator& linear_evaluator =
552 evaluator_->LinearEvaluator();
555 const int64_t min_value = var_domains_[var].Min();
556 const int64_t max_value = var_domains_[var].Max();
557 const int64_t delta = current_value == min_value ? max_value - min_value
558 : min_value - max_value;
559 return std::make_pair(
560 delta, ComputeScore(ScanWeights(), var, delta,
true));
568 const int64_t p1 = var_domains_[var].ValueAtOrBefore(current_value - 1);
569 const int64_t p2 = var_domains_[var].ValueAtOrAfter(current_value + 1);
571 std::pair<int64_t, double> best_jump;
572 const double v1 = var_domains_[var].Contains(p1)
573 ? ComputeScore(ScanWeights(), var, p1 - current_value,
575 :
std::numeric_limits<double>::infinity();
580 const Domain dom = var_domains_[var].IntersectionWith(
581 Domain(std::numeric_limits<int64_t>::min(), p1 - 1));
583 best_jump = {p1, v1};
586 linear_evaluator.SlopeBreakpoints(var, current_value, dom);
588 true, {p1, v1}, tmp_breakpoints_,
589 [
this, var, current_value](int64_t jump_value) {
590 return ComputeScore(ScanWeights(), var, jump_value - current_value,
595 const double v2 = var_domains_[var].Contains(p2)
596 ? ComputeScore(ScanWeights(), var, p2 - current_value,
598 : std::numeric_limits<double>::infinity();
602 const Domain dom = var_domains_[var].IntersectionWith(
603 Domain(p2 + 1, std::numeric_limits<int64_t>::max()));
605 best_jump = {p2, v2};
608 linear_evaluator.SlopeBreakpoints(var, current_value, dom);
610 false, {p2, v2}, tmp_breakpoints_,
611 [
this, var, current_value](int64_t jump_value) {
612 return ComputeScore(ScanWeights(), var,
613 jump_value - current_value,
623 best_jump = {p1, v1};
625 best_jump = {p2, v2};
629 DCHECK_NE(best_jump.first, current_value);
630 return std::make_pair(best_jump.first - current_value, best_jump.second);
633std::pair<int64_t, double> FeasibilityJumpSolver::ComputeGeneralJump(
int var) {
634 if (!var_occurs_in_non_linear_constraint_[var]) {
635 return ComputeLinearJump(var);
637 Domain domain = var_domains_[var];
638 if (domain.IsFixed())
return std::make_pair(0, 0.0);
640 ++state_->counters.num_general_evals;
641 const int64_t current_value = state_->solution[var];
642 domain = domain.IntersectionWith(
643 Domain(current_value, current_value).Complement());
644 std::pair<int64_t, double> result;
645 for (
int i = 0;
i < domain.NumIntervals(); ++
i) {
646 const int64_t min_delta = domain[
i].start - current_value;
647 const int64_t max_delta = domain[
i].end - current_value;
649 min_delta, max_delta + 1, [&](int64_t delta) ->
double {
650 return ComputeScore(ScanWeights(), var, delta,
false);
652 if (
i == 0 || score < result.second) {
653 result = std::make_pair(delta, score);
656 DCHECK(domain.
Contains(current_value + result.first))
657 << current_value <<
"+" << result.first <<
" not in domain "
658 << domain.ToString();
662void FeasibilityJumpSolver::UpdateViolatedConstraintWeights() {
663 ++state_->counters.num_weight_updates;
669 const double kMaxWeight = 1e10;
670 const double kBumpFactor = 1.0 / params_.feasibility_jump_decay();
671 const int num_variables = var_domains_.size();
672 if (state_->options.use_decay) {
673 state_->bump_value *= kBumpFactor;
678 bool rescale =
false;
679 num_ops_ += evaluator_->ViolatedConstraints().size();
680 for (
const int c : evaluator_->ViolatedConstraints()) {
681 DCHECK(evaluator_->IsViolated(c));
682 if (state_->options.use_compound_moves) {
683 DCHECK_EQ(state_->compound_weights[c], state_->weights[c]);
685 state_->weights[
c] += state_->bump_value;
686 if (state_->options.use_compound_moves) {
687 state_->compound_weights[
c] = state_->weights[
c];
689 if (state_->weights[c] > kMaxWeight) {
695 const double factor = 1.0 / kMaxWeight;
696 state_->bump_value *= factor;
697 for (
int c = 0;
c < state_->weights.size(); ++
c) {
698 state_->weights[
c] *= factor;
699 if (state_->options.use_compound_moves) {
700 state_->compound_weights[
c] *= factor;
703 jumps_.RecomputeAll(num_variables);
713 LinearIncrementalEvaluator* linear_evaluator =
714 evaluator_->MutableLinearEvaluator();
715 linear_evaluator->ClearAffectedVariables();
716 for_weight_update_.resize(num_variables);
717 num_ops_ += evaluator_->ViolatedConstraints().size();
718 for (
const int c : evaluator_->ViolatedConstraints()) {
719 if (c < evaluator_->NumLinearConstraints()) {
720 linear_evaluator->UpdateScoreOnWeightUpdate(
721 c, jumps_.Deltas(), absl::MakeSpan(for_weight_update_));
723 for (
const int v : evaluator_->ConstraintToVars(c)) {
732 absl::Span<double> scores = jumps_.MutableScores();
733 for (
const int var : linear_evaluator->VariablesAffectedByLastUpdate()) {
739 if (!var_domains_.HasTwoValues(var)) {
740 jumps_.Recompute(var);
743 scores[var] += state_->bump_value * for_weight_update_[var];
749bool FeasibilityJumpSolver::DoSomeLinearIterations() {
751 shared_response_->LogMessageWithThrottling(
name(), OneLineStats());
756 if (state_->options.use_compound_moves)
return true;
758 evaluator_->RecomputeViolatedList(
true);
759 jumps_.SetComputeFunction(
760 absl::bind_front(&FeasibilityJumpSolver::ComputeLinearJump,
this));
761 RecomputeVarsToScan();
765 const double dtime_threshold =
766 DeterministicTime() + params_.feasibility_jump_batch_dtime();
767 while (DeterministicTime() < dtime_threshold) {
769 while (DeterministicTime() < dtime_threshold) {
775 if (!ScanRelevantVariables(5, &best_var, &best_value,
781 ++state_->counters.num_linear_moves;
782 const int64_t prev_value = state_->solution[best_var];
783 state_->solution[best_var] = best_value;
784 evaluator_->UpdateLinearScores(best_var, prev_value, best_value,
785 state_->weights, jumps_.Deltas(),
786 jumps_.MutableScores());
787 evaluator_->UpdateViolatedList();
788 var_domains_.OnValueChange(best_var, best_value);
790 MarkJumpsThatNeedToBeRecomputed(best_var);
791 if (var_domains_.HasTwoValues(best_var)) {
794 jumps_.SetJump(best_var, prev_value - best_value, -best_score);
797 if (time_limit_crossed_)
return false;
800 if (vars_to_scan_.empty()) {
802 if (evaluator_->ViolatedConstraints().empty())
return true;
803 UpdateViolatedConstraintWeights();
822void FeasibilityJumpSolver::MarkJumpsThatNeedToBeRecomputed(
int changed_var) {
825 jumps_.Recompute(changed_var);
831 num_ops_ += evaluator_->VarToGeneralConstraints(changed_var).size();
832 for (
const int c : evaluator_->VarToGeneralConstraints(changed_var)) {
833 num_ops_ += evaluator_->GeneralConstraintToVars(c).size();
834 for (
const int var : evaluator_->GeneralConstraintToVars(c)) {
835 jumps_.Recompute(var);
841 num_ops_ += evaluator_->VariablesAffectedByLastLinearUpdate().size();
842 for (
const int var : evaluator_->VariablesAffectedByLastLinearUpdate()) {
843 if (!var_domains_.HasTwoValues(var)) {
844 jumps_.Recompute(var);
850bool FeasibilityJumpSolver::DoSomeGeneralIterations() {
851 if (!state_->options.use_compound_moves &&
852 evaluator_->NumNonLinearConstraints() == 0) {
856 evaluator_->ComputeAllNonLinearViolations(state_->solution);
857 evaluator_->RecomputeViolatedList(
false);
858 if (evaluator_->NumNonLinearConstraints() == 0) {
859 jumps_.SetComputeFunction(
860 absl::bind_front(&FeasibilityJumpSolver::ComputeLinearJump,
this));
862 jumps_.SetComputeFunction(
863 absl::bind_front(&FeasibilityJumpSolver::ComputeGeneralJump,
this));
865 RecomputeVarsToScan();
867 const double dtime_threshold =
868 DeterministicTime() + params_.feasibility_jump_batch_dtime();
869 while (DeterministicTime() < dtime_threshold) {
873 const bool found_move = ScanRelevantVariables(
874 3, &var, &new_value, &score);
875 const bool backtrack =
876 !found_move && state_->move->Backtrack(&var, &new_value, &score);
877 if (found_move || backtrack) {
878 if (backtrack) ++state_->counters.num_backtracks;
879 DCHECK_NE(var, -1) << var <<
" " << found_move <<
" " << backtrack;
882 ++state_->counters.num_general_moves;
883 const int64_t prev_value = state_->solution[var];
884 DCHECK_NE(prev_value, new_value);
885 state_->solution[var] = new_value;
888 evaluator_->UpdateLinearScores(var, prev_value, new_value, ScanWeights(),
889 jumps_.Deltas(), jumps_.MutableScores());
890 evaluator_->UpdateNonLinearViolations(var, prev_value, state_->solution);
891 evaluator_->UpdateViolatedList();
892 var_domains_.OnValueChange(var, new_value);
894 if (state_->options.use_compound_moves && !backtrack) {
898 for (
const auto& c : evaluator_->last_update_violation_changes()) {
899 if (evaluator_->IsViolated(c) &&
900 state_->compound_weights[c] != state_->weights[c]) {
901 state_->compound_weights[
c] = state_->weights[
c];
902 if (!state_->in_compound_weight_changed[c]) {
903 state_->in_compound_weight_changed[
c] =
true;
904 state_->compound_weight_changed.push_back(c);
906 for (
const int v : evaluator_->ConstraintToVars(c)) {
910 }
else if (!evaluator_->IsViolated(c) &&
911 !state_->in_compound_weight_changed[c] &&
912 state_->compound_weights[c] == state_->weights[c]) {
913 state_->in_compound_weight_changed[
c] =
true;
914 state_->compound_weight_changed.push_back(c);
923 if (
false && !state_->options.use_decay) {
924 DCHECK_EQ(-score, ComputeScore(state_->weights, var,
925 prev_value - new_value,
false));
926 DCHECK_EQ(-score, ComputeScore(ScanWeights(), var,
927 prev_value - new_value,
false));
930 MarkJumpsThatNeedToBeRecomputed(var);
931 if (var_domains_.HasTwoValues(var)) {
934 jumps_.SetJump(var, prev_value - new_value, -score);
937 if (state_->options.use_compound_moves && !backtrack) {
939 DCHECK_NE(prev_value, state_->solution[var]);
940 state_->move->Push(var, prev_value, score);
941 if (state_->move->Score() < 0) {
942 state_->counters.num_compound_moves += state_->move->Size();
943 state_->move->Clear();
944 state_->compound_move_max_discrepancy = 0;
948 }
else if (time_limit_crossed_) {
952 DCHECK_EQ(state_->move->Size(), 0);
953 if (evaluator_->ViolatedConstraints().empty())
return true;
954 if (state_->options.use_compound_moves) {
955 ResetChangedCompoundWeights();
957 if (!state_->options.use_compound_moves ||
958 ++state_->compound_move_max_discrepancy > 2) {
959 state_->compound_move_max_discrepancy = 0;
960 UpdateViolatedConstraintWeights();
966void FeasibilityJumpSolver::ResetChangedCompoundWeights() {
967 if (!state_->options.use_compound_moves)
return;
968 DCHECK_EQ(state_->move->Size(), 0);
969 num_ops_ += state_->compound_weight_changed.size();
970 for (
const int c : state_->compound_weight_changed) {
971 state_->in_compound_weight_changed[
c] =
false;
972 const double expected_weight =
973 (evaluator_->IsViolated(c) ? 1.0 : kCompoundDiscount) *
975 if (state_->compound_weights[c] == expected_weight)
continue;
976 state_->compound_weights[
c] = expected_weight;
977 num_ops_ += evaluator_->ConstraintToVars(c).size();
978 for (
const int var : evaluator_->ConstraintToVars(c)) {
979 jumps_.Recompute(var);
983 state_->compound_weight_changed.clear();
986bool FeasibilityJumpSolver::ShouldExtendCompoundMove(
double score,
988 if (state_->move->Score() + score - std::max(novelty, 0.0) < 0) {
991 return score < state_->move->BestChildScore();
994bool FeasibilityJumpSolver::ScanRelevantVariables(
int num_to_scan,
997 double* best_score) {
998 if (time_limit_crossed_)
return false;
999 if (state_->move->Discrepancy() > state_->compound_move_max_discrepancy) {
1002 double best_scan_score = 0.0;
1004 int best_index = -1;
1008 auto remove_var_to_scan_at_index = [&](
int index) {
1009 in_vars_to_scan_[vars_to_scan_[index]] =
false;
1010 vars_to_scan_[index] = vars_to_scan_.back();
1011 vars_to_scan_.pop_back();
1012 if (best_index == vars_to_scan_.size()) {
1016 while (!vars_to_scan_.empty() && num_good < num_to_scan) {
1018 const int index = absl::Uniform<int>(random_, 0, vars_to_scan_.size());
1019 const int var = vars_to_scan_[index];
1021 DCHECK(in_vars_to_scan_[var]);
1023 if (!ShouldScan(var)) {
1024 remove_var_to_scan_at_index(index);
1027 const auto [delta, scan_score] = jumps_.GetJump(var);
1028 if ((state_->counters.num_general_evals +
1029 state_->counters.num_linear_evals) %
1032 shared_time_limit_ !=
nullptr && shared_time_limit_->LimitReached()) {
1033 time_limit_crossed_ =
true;
1036 const int64_t current_value = state_->solution[var];
1037 DCHECK(var_domains_[var].Contains(current_value + delta))
1038 << var <<
" " << current_value <<
"+" << delta <<
" not in "
1039 << var_domains_[var].ToString();
1040 DCHECK(!var_domains_[var].
IsFixed());
1041 if (scan_score >= 0) {
1042 remove_var_to_scan_at_index(index);
1045 double score = scan_score;
1046 if (state_->options.use_compound_moves) {
1048 score = ComputeScore(
1049 state_->weights, var, delta,
1050 !var_occurs_in_non_linear_constraint_[var]);
1051 if (!ShouldExtendCompoundMove(score, score - scan_score)) {
1052 remove_var_to_scan_at_index(index);
1058 if (scan_score < best_scan_score) {
1059 DCHECK_NE(delta, 0) << score;
1061 *best_value = current_value + delta;
1062 *best_score = score;
1063 best_scan_score = scan_score;
1067 if (best_index != -1) {
1068 DCHECK_GT(num_good, 0);
1069 DCHECK_GE(*best_var, 0);
1070 DCHECK_EQ(*best_var, vars_to_scan_[best_index]);
1071 remove_var_to_scan_at_index(best_index);
1077void FeasibilityJumpSolver::AddVarToScan(
int var) {
1079 if (in_vars_to_scan_[var])
return;
1080 if (!ShouldScan(var))
return;
1081 vars_to_scan_.push_back(var);
1082 in_vars_to_scan_[var] =
true;
1085bool FeasibilityJumpSolver::ShouldScan(
int var)
const {
1088 if (state_->move->OnStack(var))
return false;
1090 if (!jumps_.NeedRecomputation(var)) {
1092 const double score = jumps_.Score(var);
1097 DCHECK(!var_domains_.IsFixed(var));
1100 if (var_domains_.HasBetterObjectiveValue(var))
return true;
1112 return evaluator_->NumViolatedConstraintsForVarIgnoringObjective(var) > 0;
1115void FeasibilityJumpSolver::RecomputeVarsToScan() {
1116 const int num_variables = var_domains_.size();
1117 jumps_.RecomputeAll(num_variables);
1118 DCHECK(SlowCheckNumViolatedConstraints());
1120 in_vars_to_scan_.assign(num_variables,
false);
1121 vars_to_scan_.clear();
1126 for (
const int var : var_domains_.FixedVariables()) {
1127 in_vars_to_scan_[var] =
true;
1130 num_ops_ += evaluator_->ViolatedConstraints().size();
1131 for (
const int c : evaluator_->ViolatedConstraints()) {
1132 num_ops_ += evaluator_->ConstraintToVars(c).size();
1133 for (
const int v : evaluator_->ConstraintToVars(c)) {
1139bool FeasibilityJumpSolver::SlowCheckNumViolatedConstraints()
const {
1140 std::vector<int> result;
1141 result.assign(var_domains_.size(), 0);
1142 for (
const int c : evaluator_->ViolatedConstraints()) {
1143 if (evaluator_->IsObjectiveConstraint(c))
continue;
1144 for (
const int v : evaluator_->ConstraintToVars(c)) {
1148 for (
int v = 0; v < result.size(); ++v) {
1150 evaluator_->NumViolatedConstraintsForVarIgnoringObjective(v));
1156 for (
const UnitMove& move : stack_) {
1157 var_on_stack_[move.var] =
false;
1163 return !stack_.empty() && var_on_stack_[var];
1167 if (stack_.empty())
return false;
1168 *var = stack_.back().var;
1169 *value = stack_.back().prev_value;
1170 *score = stack_.back().score;
1171 var_on_stack_[*var] =
false;
1173 if (!stack_.empty()) {
1174 ++stack_.back().discrepancy;
1180 DCHECK(!var_on_stack_[var]);
1181 if (!stack_.empty()) {
1182 stack_.back().best_child_score =
1183 std::min(stack_.back().best_child_score, score);
1185 var_on_stack_[var] =
true;
1188 .prev_value = prev_value,
1190 .cumulative_score =
Score() + score,
1196 absl::Span<const Domain> var_domains)
const {
1197 for (
const UnitMove& move : stack_) {
1198 if (!var_domains[move.var].Contains(move.prev_value))
return false;
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 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)
void Push(int var, int64_t prev_value, double score)
~FeasibilityJumpSolver() override
If VLOG_IS_ON(1), it will export a bunch of statistics.
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.
void SetComputeFunction(absl::AnyInvocable< std::pair< int64_t, double >(int) const > compute_jump)
bool JumpIsUpToDate(int var) const
void SetJump(int var, int64_t delta, double score)
void CollectStatistics(const LsState &state)
Accumulate in the relevant bucket the counters of the given states.
void AddTaskDeterministicDuration(double deterministic_duration)
SubsolverType type() const
Returns the type of the subsolver.
double deterministic_time() const
std::string name() const
Returns the name of this SubSolver. Used in logs.
bool HasTwoValues(int var) const
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
PushedSolutionPointers PushAndMaybeCombineSolution(SharedResponseManager *response_manager, const CpModelProto &model_proto, absl::Span< const int64_t > new_solution, const std::string &solution_info, absl::Span< const int64_t > base_solution, Model *model)
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
std::pair< Point, Value > RangeConvexMinimum(Point begin, Point end, absl::FunctionRef< Value(Point)> f)
Searches in the range [begin, end), where Point supports basic arithmetic.
std::pair< Point, Value > ConvexMinimum(absl::Span< const Point > sorted_points, std::function< Value(Point)> f)
int64_t Max() const
Returns the max of the domain.
int64_t Min() const
Returns the min of the domain.
bool Contains(int64_t value) const
Various inclusion tests on a domain.
int64_t num_scores_computed
std::vector< int64_t > solution
LsCounters counters
Counters for a "non-restarted" run.
std::unique_ptr< CompoundMoveBuilder > move