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/log/log.h"
35#include "absl/log/vlog_is_on.h"
36#include "absl/random/bit_gen_ref.h"
37#include "absl/random/distributions.h"
38#include "absl/strings/str_cat.h"
39#include "absl/types/span.h"
59constexpr double kCompoundDiscount = 1. / 1024;
63 absl::AnyInvocable<std::pair<int64_t, double>(
int)
const> compute_jump) {
64 compute_jump_ = std::move(compute_jump);
68 deltas_.resize(num_variables);
69 scores_.resize(num_variables);
70 needs_recomputation_.assign(num_variables,
true);
76 needs_recomputation_[var] =
false;
82 const auto& [delta, score] = compute_jump_(var);
83 if (delta != deltas_[var]) {
84 LOG(ERROR) <<
"Incorrect delta for var " << var <<
": " << deltas_[var]
85 <<
" (should be " << delta <<
")";
88 if (abs(score - scores_[var]) / std::max(abs(score), 1.0) > 1e-2) {
90 LOG(ERROR) <<
"Incorrect score for var " << var <<
": " << scores_[var]
91 <<
" (should be " << score <<
") "
92 <<
" delta = " << delta;
94 return delta == deltas_[var] && score_ok;
98 if (needs_recomputation_[var]) {
99 needs_recomputation_[var] =
false;
100 std::tie(deltas_[var], scores_[var]) = compute_jump_(var);
102 return std::make_pair(deltas_[var], scores_[var]);
107 for (
int i = 0;
i < states_.size(); ++
i) {
112 for (
const auto& [options, counters] : options_to_stats_) {
113 stat_tables_->AddLsStat(
114 absl::StrCat(name_,
"_", options.name()), counters.num_batches,
115 options_to_num_restarts_[options] + counters.num_perturbations,
116 counters.num_linear_moves, counters.num_general_moves,
117 counters.num_compound_moves, counters.num_backtracks,
118 counters.num_weight_updates, counters.num_scores_computed);
123 stat_tables_->AddTimingStat(*
this);
126void FeasibilityJumpSolver::ImportState() {
127 state_ = states_->GetNextState();
128 if (state_->
move ==
nullptr) {
129 const int num_variables = var_domains_.
size();
130 state_->
move = std::make_unique<CompoundMoveBuilder>(num_variables);
134void FeasibilityJumpSolver::ReleaseState() { states_->Release(state_); }
136bool FeasibilityJumpSolver::Initialize() {
139 if (params_.feasibility_jump_linearization_level() == 0) {
140 evaluator_ = std::make_unique<LsEvaluator>(linear_model_->model_proto(),
141 params_, &time_limit_);
143 evaluator_ = std::make_unique<LsEvaluator>(
144 linear_model_->model_proto(), params_,
145 linear_model_->ignored_constraints(),
146 linear_model_->additional_constraints(), &time_limit_);
149 if (time_limit_.LimitReached()) {
153 is_initialized_ =
true;
154 const int num_variables = linear_model_->model_proto().variables().size();
155 var_domains_.resize(num_variables);
156 for (
int v = 0; v < num_variables; ++v) {
160 var_domains_.InitializeObjective(linear_model_->model_proto());
162 vars_to_scan_.ClearAndReserve(num_variables);
163 var_occurs_in_non_linear_constraint_.resize(num_variables);
164 for (
int c = 0;
c < evaluator_->NumNonLinearConstraints(); ++
c) {
165 for (
int v : evaluator_->GeneralConstraintToVars(c)) {
166 var_occurs_in_non_linear_constraint_[v] =
true;
174int64_t ComputeRange(int64_t range,
double range_ratio) {
175 return static_cast<int64_t
>(
176 std::ceil(
static_cast<double>(range) * range_ratio));
181int64_t RandomValueNearMin(
const Domain& domain,
double range_ratio,
182 absl::BitGenRef random) {
183 if (domain.Size() == 1)
return domain.FixedValue();
184 if (domain.Size() == 2) {
185 return absl::Bernoulli(random, 1 - range_ratio) ? domain.
Min()
188 const int64_t range = ComputeRange(domain.
Max() - domain.
Min(), range_ratio);
189 return domain.ValueAtOrBefore(domain.
Min() +
190 absl::LogUniform<int64_t>(random, 0, range));
193int64_t RandomValueNearMax(
const Domain& domain,
double range_ratio,
194 absl::BitGenRef random) {
195 if (domain.Size() == 1)
return domain.FixedValue();
196 if (domain.Size() == 2) {
197 return absl::Bernoulli(random, 1 - range_ratio) ? domain.
Max()
200 const int64_t range = ComputeRange(domain.
Max() - domain.
Min(), range_ratio);
201 return domain.ValueAtOrAfter(domain.
Max() -
202 absl::LogUniform<int64_t>(random, 0, range));
205int64_t RandomValueNearValue(
const Domain& domain, int64_t value,
206 double range_ratio, absl::BitGenRef random) {
207 DCHECK(!domain.IsFixed());
209 if (domain.
Min() >= value) {
210 return RandomValueNearMin(domain, range_ratio, random);
213 if (domain.
Max() <= value) {
214 return RandomValueNearMax(domain, range_ratio, random);
218 const Domain greater_domain =
219 domain.IntersectionWith({value + 1, domain.
Max()});
220 const double choose_greater_probability =
221 static_cast<double>(greater_domain.Size()) /
222 static_cast<double>(domain.Size() - 1);
223 if (absl::Bernoulli(random, choose_greater_probability)) {
224 return RandomValueNearMin(greater_domain, range_ratio, random);
226 return RandomValueNearMax(
227 domain.IntersectionWith({domain.Min(), value - 1}), range_ratio,
234void FeasibilityJumpSolver::ResetCurrentSolution(
235 bool use_hint,
bool use_objective,
double perturbation_probability) {
236 const int num_variables = linear_model_->model_proto().variables().size();
237 const double default_value_probability = 1.0 - perturbation_probability;
238 const double range_ratio =
239 params_.feasibility_jump_var_perburbation_range_ratio();
240 std::vector<int64_t>&
solution = state_->solution;
241 state_->base_solution =
nullptr;
247 for (
int var = 0; var < num_variables; ++var) {
248 if (var_domains_[var].
IsFixed()) {
249 solution[var] = var_domains_[var].FixedValue();
253 if (absl::Bernoulli(random_, default_value_probability)) {
254 solution[var] = var_domains_[var].SmallestValue();
257 RandomValueNearValue(var_domains_[var], 0, range_ratio, random_);
262 if (use_objective && linear_model_->model_proto().has_objective()) {
263 const int num_terms =
264 linear_model_->model_proto().objective().vars().size();
265 for (
int i = 0;
i < num_terms; ++
i) {
266 const int var = linear_model_->model_proto().objective().vars(
i);
267 if (var_domains_[var].
IsFixed())
continue;
269 if (linear_model_->model_proto().objective().coeffs(
i) > 0) {
270 if (absl::Bernoulli(random_, default_value_probability)) {
271 solution[var] = var_domains_[var].Min();
274 RandomValueNearMin(var_domains_[var], range_ratio, random_);
277 if (absl::Bernoulli(random_, default_value_probability)) {
278 solution[var] = var_domains_[var].Max();
281 RandomValueNearMax(var_domains_[var], range_ratio, random_);
287 if (use_hint && linear_model_->model_proto().has_solution_hint()) {
288 const auto& hint = linear_model_->model_proto().solution_hint();
289 for (
int i = 0;
i < hint.vars().size(); ++
i) {
295void FeasibilityJumpSolver::PerturbateCurrentSolution(
296 double perturbation_probability) {
297 if (perturbation_probability == 0.0)
return;
298 const int num_variables = linear_model_->model_proto().variables().size();
299 const double perturbation_ratio =
300 params_.feasibility_jump_var_perburbation_range_ratio();
301 std::vector<int64_t>&
solution = state_->solution;
302 for (
int var = 0; var < num_variables; ++var) {
303 if (var_domains_[var].
IsFixed())
continue;
304 if (absl::Bernoulli(random_, perturbation_probability)) {
306 perturbation_ratio, random_);
311std::string FeasibilityJumpSolver::OneLineStats()
const {
313 const std::string general_str =
314 state_->counters.num_general_evals == 0 &&
315 state_->counters.num_general_moves == 0
318 " gen{mvs:",
FormatCounter(state_->counters.num_general_moves),
319 " evals:",
FormatCounter(state_->counters.num_general_evals),
321 const std::string compound_str =
322 state_->counters.num_compound_moves == 0 &&
323 state_->counters.num_backtracks == 0
325 : absl::StrCat(
" comp{mvs:",
331 const int num_infeasible_cts = evaluator_->NumInfeasibleConstraints();
332 const std::string non_solution_str =
333 num_infeasible_cts == 0
335 : absl::StrCat(
" #good_moves:",
FormatCounter(vars_to_scan_.size()),
340 "batch:", state_->counters.num_batches,
341 " lin{mvs:",
FormatCounter(state_->counters.num_linear_moves),
342 " evals:",
FormatCounter(state_->counters.num_linear_evals),
"}",
343 general_str, compound_str, non_solution_str,
344 " #w_updates:",
FormatCounter(state_->counters.num_weight_updates),
345 " #perturb:",
FormatCounter(state_->counters.num_perturbations));
349 task_generated_ =
true;
354 if (!is_initialized_) {
365 bool new_best_solution_was_found =
false;
368 shared_response_->SolutionsRepository().GetBestRank();
369 if (best < state_->last_solution_rank) {
370 states_->ResetLubyCounter();
371 new_best_solution_was_found =
true;
372 state_->last_solution_rank = best;
376 bool reset_weights =
false;
377 if (new_best_solution_was_found || state_->num_batches_before_change <= 0) {
378 reset_weights =
true;
379 if (state_->options.use_restart) {
380 states_->CollectStatistics(*state_);
381 state_->options.Randomize(params_, &random_);
384 state_->options.Randomize(params_, &random_);
389 state_->options.use_objective =
false;
392 const bool first_time = (state_->num_restarts == 0);
393 if (state_->options.use_restart || first_time ||
394 new_best_solution_was_found) {
397 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
398 solution = shared_response_->SolutionsRepository()
399 .GetRandomBiasedSolution(random_);
400 state_->solution =
solution->variable_values;
402 ++state_->num_solutions_imported;
406 const int num_violations = evaluator_->ViolatedConstraints().size();
407 shared_hints_->AddSolution(state_->solution, num_violations);
409 ResetCurrentSolution(first_time,
410 state_->options.use_objective,
411 state_->options.perturbation_probability);
414 PerturbateCurrentSolution(
415 params_.feasibility_jump_var_randomization_probability());
418 if (state_->options.use_restart) {
419 ++state_->num_restarts;
420 states_->ConfigureNextLubyRestart(state_);
423 ++state_->counters.num_perturbations;
424 state_->num_batches_before_change =
425 params_.violation_ls_perturbation_period();
430 bool recompute_compound_weights =
false;
431 if (linear_model_->model_proto().has_objective()) {
432 const IntegerValue lb = shared_response_->GetInnerObjectiveLowerBound();
433 const IntegerValue ub = shared_response_->GetInnerObjectiveUpperBound();
434 if (lb != state_->saved_inner_objective_lb ||
435 ub != state_->saved_inner_objective_ub) {
436 recompute_compound_weights =
true;
438 state_->saved_inner_objective_lb = lb;
439 state_->saved_inner_objective_ub = ub;
442 if (evaluator_->ReduceObjectiveBounds(lb.value(), ub.value())) {
443 recompute_compound_weights =
true;
448 if (!var_domains_.UpdateFromSharedBounds())
return;
454 const int num_vars = state_->solution.size();
455 for (
int var = 0; var < num_vars; ++var) {
456 const int64_t old_value = state_->solution[var];
457 const int64_t new_value = var_domains_[var].ClosestValue(old_value);
458 if (new_value != old_value) {
459 state_->solution[var] = new_value;
460 recompute_compound_weights =
true;
462 var_domains_.OnValueChange(var, new_value);
465 if (!state_->move->StackValuesInDomains(var_domains_.AsSpan())) {
466 recompute_compound_weights =
true;
473 evaluator_->ComputeAllViolations(state_->solution);
476 state_->bump_value = 1.0;
477 state_->weights.assign(evaluator_->NumEvaluatorConstraints(), 1.0);
478 recompute_compound_weights =
true;
480 if (recompute_compound_weights) {
481 state_->move->Clear();
482 if (state_->options.use_compound_moves) {
483 state_->compound_weights.assign(state_->weights.begin(),
484 state_->weights.end());
485 for (
int c = 0; c < state_->weights.size(); ++c) {
486 if (evaluator_->IsViolated(c))
continue;
487 state_->compound_weights[c] *= kCompoundDiscount;
489 state_->compound_weight_changed.clear();
490 state_->in_compound_weight_changed.assign(state_->weights.size(),
492 state_->compound_move_max_discrepancy = 0;
496 if (!state_->options.use_compound_moves) {
497 DCHECK_EQ(state_->move->Size(), 0);
500 ++state_->counters.num_batches;
501 if (DoSomeLinearIterations() && DoSomeGeneralIterations()) {
505 shared_response_, linear_model_->model_proto(), state_->solution,
506 absl::StrCat(
name(),
"_", state_->options.name(),
"(",
507 OneLineStats(),
")"),
508 state_->base_solution ==
nullptr
509 ? absl::Span<const int64_t>()
510 : state_->base_solution->variable_values,
514 state_->base_solution = pointers.pushed_solution;
516 shared_response_->LogMessage(
name(),
"infeasible solution. Aborting.");
517 model_is_supported_ =
false;
527 const double current_dtime = DeterministicTime();
535 shared_time_limit_->AdvanceDeterministicTime(delta);
540 task_generated_ =
false;
544double FeasibilityJumpSolver::ComputeScore(absl::Span<const double> weights,
545 int var, int64_t delta,
548 double score = evaluator_->WeightedViolationDelta(
549 linear_only, weights, var, delta, absl::MakeSpan(state_->
solution));
550 constexpr double kEpsilon = 1.0 / std::numeric_limits<int64_t>::max();
551 score += kEpsilon * delta * evaluator_->ObjectiveCoefficient(var);
555std::pair<int64_t, double> FeasibilityJumpSolver::ComputeLinearJump(
int var) {
556 DCHECK(!var_domains_[var].
IsFixed());
557 const int64_t current_value = state_->
solution[var];
560 const LinearIncrementalEvaluator& linear_evaluator =
561 evaluator_->LinearEvaluator();
564 const int64_t min_value = var_domains_[var].Min();
565 const int64_t max_value = var_domains_[var].Max();
566 const int64_t delta = current_value == min_value ? max_value - min_value
567 : min_value - max_value;
568 return std::make_pair(
569 delta, ComputeScore(ScanWeights(), var, delta,
true));
577 const int64_t p1 = var_domains_[var].ValueAtOrBefore(current_value - 1);
578 const int64_t p2 = var_domains_[var].ValueAtOrAfter(current_value + 1);
580 std::pair<int64_t, double> best_jump;
581 const double v1 = var_domains_[var].Contains(p1)
582 ? ComputeScore(ScanWeights(), var, p1 - current_value,
584 :
std::numeric_limits<double>::infinity();
589 const Domain dom = var_domains_[var].IntersectionWith(
590 Domain(std::numeric_limits<int64_t>::min(), p1 - 1));
592 best_jump = {p1, v1};
595 linear_evaluator.SlopeBreakpoints(var, current_value, dom);
597 true, {p1, v1}, tmp_breakpoints_,
598 [
this, var, current_value](int64_t jump_value) {
599 return ComputeScore(ScanWeights(), var, jump_value - current_value,
604 const double v2 = var_domains_[var].Contains(p2)
605 ? ComputeScore(ScanWeights(), var, p2 - current_value,
607 : std::numeric_limits<double>::infinity();
611 const Domain dom = var_domains_[var].IntersectionWith(
612 Domain(p2 + 1, std::numeric_limits<int64_t>::max()));
614 best_jump = {p2, v2};
617 linear_evaluator.SlopeBreakpoints(var, current_value, dom);
619 false, {p2, v2}, tmp_breakpoints_,
620 [
this, var, current_value](int64_t jump_value) {
621 return ComputeScore(ScanWeights(), var,
622 jump_value - current_value,
632 best_jump = {p1, v1};
634 best_jump = {p2, v2};
638 DCHECK_NE(best_jump.first, current_value);
639 return std::make_pair(best_jump.first - current_value, best_jump.second);
642std::pair<int64_t, double> FeasibilityJumpSolver::ComputeGeneralJump(
int var) {
643 if (!var_occurs_in_non_linear_constraint_[var]) {
644 return ComputeLinearJump(var);
646 Domain domain = var_domains_[var];
647 if (domain.IsFixed())
return std::make_pair(0, 0.0);
649 ++state_->counters.num_general_evals;
650 const int64_t current_value = state_->solution[var];
651 domain = domain.IntersectionWith(
652 Domain(current_value, current_value).Complement());
653 std::pair<int64_t, double> result;
654 for (
int i = 0;
i < domain.NumIntervals(); ++
i) {
655 const int64_t min_delta = domain[
i].start - current_value;
656 const int64_t max_delta = domain[
i].end - current_value;
658 min_delta, max_delta + 1, [&](int64_t delta) ->
double {
659 return ComputeScore(ScanWeights(), var, delta,
false);
661 if (
i == 0 || score < result.second) {
662 result = std::make_pair(delta, score);
665 DCHECK(domain.
Contains(current_value + result.first))
666 << current_value <<
"+" << result.first <<
" not in domain "
667 << domain.ToString();
671void FeasibilityJumpSolver::UpdateViolatedConstraintWeights() {
672 ++state_->counters.num_weight_updates;
678 const double kMaxWeight = 1e10;
679 const double kBumpFactor = 1.0 / params_.feasibility_jump_decay();
680 const int num_variables = var_domains_.size();
681 if (state_->options.use_decay) {
682 state_->bump_value *= kBumpFactor;
687 bool rescale =
false;
688 num_ops_ += evaluator_->ViolatedConstraints().size();
689 for (
const int c : evaluator_->ViolatedConstraints()) {
690 DCHECK(evaluator_->IsViolated(c));
691 if (state_->options.use_compound_moves) {
692 DCHECK_EQ(state_->compound_weights[c], state_->weights[c]);
694 state_->weights[
c] += state_->bump_value;
695 if (state_->options.use_compound_moves) {
696 state_->compound_weights[
c] = state_->weights[
c];
698 if (state_->weights[c] > kMaxWeight) {
704 const double factor = 1.0 / kMaxWeight;
705 state_->bump_value *= factor;
706 for (
int c = 0;
c < state_->weights.size(); ++
c) {
707 state_->weights[
c] *= factor;
708 if (state_->options.use_compound_moves) {
709 state_->compound_weights[
c] *= factor;
712 jumps_.RecomputeAll(num_variables);
722 LinearIncrementalEvaluator* linear_evaluator =
723 evaluator_->MutableLinearEvaluator();
724 linear_evaluator->ClearAffectedVariables();
725 for_weight_update_.resize(num_variables);
726 num_ops_ += evaluator_->ViolatedConstraints().size();
727 for (
const int c : evaluator_->ViolatedConstraints()) {
728 if (c < evaluator_->NumLinearConstraints()) {
729 linear_evaluator->UpdateScoreOnWeightUpdate(
730 c, jumps_.Deltas(), absl::MakeSpan(for_weight_update_));
732 for (
const int v : evaluator_->ConstraintToVars(c)) {
741 absl::Span<double> scores = jumps_.MutableScores();
742 for (
const int var : linear_evaluator->VariablesAffectedByLastUpdate()) {
748 if (!var_domains_.HasTwoValues(var)) {
749 jumps_.Recompute(var);
752 scores[var] += state_->bump_value * for_weight_update_[var];
758bool FeasibilityJumpSolver::DoSomeLinearIterations() {
760 shared_response_->LogMessageWithThrottling(
name(), OneLineStats());
765 if (state_->options.use_compound_moves)
return true;
767 evaluator_->RecomputeViolatedList(
true);
768 jumps_.SetComputeFunction(
769 absl::bind_front(&FeasibilityJumpSolver::ComputeLinearJump,
this));
770 RecomputeVarsToScan();
774 const double dtime_threshold =
775 DeterministicTime() + params_.feasibility_jump_batch_dtime();
776 while (DeterministicTime() < dtime_threshold) {
778 while (DeterministicTime() < dtime_threshold) {
784 if (!ScanRelevantVariables(5, &best_var, &best_value,
790 ++state_->counters.num_linear_moves;
791 const int64_t prev_value = state_->solution[best_var];
792 state_->solution[best_var] = best_value;
793 evaluator_->UpdateLinearScores(best_var, prev_value, best_value,
794 state_->weights, jumps_.Deltas(),
795 jumps_.MutableScores());
796 evaluator_->UpdateViolatedList();
797 var_domains_.OnValueChange(best_var, best_value);
799 MarkJumpsThatNeedToBeRecomputed(best_var);
800 if (var_domains_.HasTwoValues(best_var)) {
803 jumps_.SetJump(best_var, prev_value - best_value, -best_score);
806 if (time_limit_crossed_)
return false;
809 if (vars_to_scan_.empty()) {
811 if (evaluator_->ViolatedConstraints().empty())
return true;
812 UpdateViolatedConstraintWeights();
831void FeasibilityJumpSolver::MarkJumpsThatNeedToBeRecomputed(
int changed_var) {
834 jumps_.Recompute(changed_var);
840 num_ops_ += evaluator_->VarToGeneralConstraints(changed_var).size();
841 for (
const int c : evaluator_->VarToGeneralConstraints(changed_var)) {
842 num_ops_ += evaluator_->GeneralConstraintToVars(c).size();
843 for (
const int var : evaluator_->GeneralConstraintToVars(c)) {
844 jumps_.Recompute(var);
850 num_ops_ += evaluator_->VariablesAffectedByLastLinearUpdate().size();
851 for (
const int var : evaluator_->VariablesAffectedByLastLinearUpdate()) {
852 if (!var_domains_.HasTwoValues(var)) {
853 jumps_.Recompute(var);
859bool FeasibilityJumpSolver::DoSomeGeneralIterations() {
860 if (!state_->options.use_compound_moves &&
861 evaluator_->NumNonLinearConstraints() == 0) {
865 evaluator_->ComputeAllNonLinearViolations(state_->solution);
866 evaluator_->RecomputeViolatedList(
false);
867 if (evaluator_->NumNonLinearConstraints() == 0) {
868 jumps_.SetComputeFunction(
869 absl::bind_front(&FeasibilityJumpSolver::ComputeLinearJump,
this));
871 jumps_.SetComputeFunction(
872 absl::bind_front(&FeasibilityJumpSolver::ComputeGeneralJump,
this));
874 RecomputeVarsToScan();
876 const double dtime_threshold =
877 DeterministicTime() + params_.feasibility_jump_batch_dtime();
878 while (DeterministicTime() < dtime_threshold) {
882 const bool found_move = ScanRelevantVariables(
883 3, &var, &new_value, &score);
884 const bool backtrack =
885 !found_move && state_->move->Backtrack(&var, &new_value, &score);
886 if (found_move || backtrack) {
887 if (backtrack) ++state_->counters.num_backtracks;
888 DCHECK_NE(var, -1) << var <<
" " << found_move <<
" " << backtrack;
891 ++state_->counters.num_general_moves;
892 const int64_t prev_value = state_->solution[var];
893 DCHECK_NE(prev_value, new_value);
894 state_->solution[var] = new_value;
897 evaluator_->UpdateLinearScores(var, prev_value, new_value, ScanWeights(),
898 jumps_.Deltas(), jumps_.MutableScores());
899 evaluator_->UpdateNonLinearViolations(var, prev_value, state_->solution);
900 evaluator_->UpdateViolatedList();
901 var_domains_.OnValueChange(var, new_value);
903 if (state_->options.use_compound_moves && !backtrack) {
907 for (
const auto& c : evaluator_->last_update_violation_changes()) {
908 if (evaluator_->IsViolated(c) &&
909 state_->compound_weights[c] != state_->weights[c]) {
910 state_->compound_weights[
c] = state_->weights[
c];
911 if (!state_->in_compound_weight_changed[c]) {
912 state_->in_compound_weight_changed[
c] =
true;
913 state_->compound_weight_changed.push_back(c);
915 for (
const int v : evaluator_->ConstraintToVars(c)) {
919 }
else if (!evaluator_->IsViolated(c) &&
920 !state_->in_compound_weight_changed[c] &&
921 state_->compound_weights[c] == state_->weights[c]) {
922 state_->in_compound_weight_changed[
c] =
true;
923 state_->compound_weight_changed.push_back(c);
932 if (
false && !state_->options.use_decay) {
933 DCHECK_EQ(-score, ComputeScore(state_->weights, var,
934 prev_value - new_value,
false));
935 DCHECK_EQ(-score, ComputeScore(ScanWeights(), var,
936 prev_value - new_value,
false));
939 MarkJumpsThatNeedToBeRecomputed(var);
940 if (var_domains_.HasTwoValues(var)) {
943 jumps_.SetJump(var, prev_value - new_value, -score);
946 if (state_->options.use_compound_moves && !backtrack) {
948 DCHECK_NE(prev_value, state_->solution[var]);
949 state_->move->Push(var, prev_value, score);
950 if (state_->move->Score() < 0) {
951 state_->counters.num_compound_moves += state_->move->Size();
952 state_->move->Clear();
953 state_->compound_move_max_discrepancy = 0;
957 }
else if (time_limit_crossed_) {
961 DCHECK_EQ(state_->move->Size(), 0);
962 if (evaluator_->ViolatedConstraints().empty())
return true;
963 if (state_->options.use_compound_moves) {
964 ResetChangedCompoundWeights();
966 if (!state_->options.use_compound_moves ||
967 ++state_->compound_move_max_discrepancy > 2) {
968 state_->compound_move_max_discrepancy = 0;
969 UpdateViolatedConstraintWeights();
975void FeasibilityJumpSolver::ResetChangedCompoundWeights() {
976 if (!state_->options.use_compound_moves)
return;
977 DCHECK_EQ(state_->move->Size(), 0);
978 num_ops_ += state_->compound_weight_changed.size();
979 for (
const int c : state_->compound_weight_changed) {
980 state_->in_compound_weight_changed[
c] =
false;
981 const double expected_weight =
982 (evaluator_->IsViolated(c) ? 1.0 : kCompoundDiscount) *
984 if (state_->compound_weights[c] == expected_weight)
continue;
985 state_->compound_weights[
c] = expected_weight;
986 num_ops_ += evaluator_->ConstraintToVars(c).size();
987 for (
const int var : evaluator_->ConstraintToVars(c)) {
988 jumps_.Recompute(var);
992 state_->compound_weight_changed.clear();
995bool FeasibilityJumpSolver::ShouldExtendCompoundMove(
double score,
997 if (state_->move->Score() + score - std::max(novelty, 0.0) < 0) {
1000 return score < state_->move->BestChildScore();
1003bool FeasibilityJumpSolver::ScanRelevantVariables(
int num_to_scan,
1005 int64_t* best_value,
1006 double* best_score) {
1007 if (time_limit_crossed_)
return false;
1008 if (state_->move->Discrepancy() > state_->compound_move_max_discrepancy) {
1011 double best_scan_score = 0.0;
1013 int best_index = -1;
1017 auto remove_var_to_scan_at_index = [&](
int index) {
1018 in_vars_to_scan_[vars_to_scan_[index]] =
false;
1019 vars_to_scan_[index] = vars_to_scan_.back();
1020 vars_to_scan_.pop_back();
1021 if (best_index == vars_to_scan_.size()) {
1025 while (!vars_to_scan_.empty() && num_good < num_to_scan) {
1027 const int index = absl::Uniform<int>(random_, 0, vars_to_scan_.size());
1028 const int var = vars_to_scan_[index];
1030 DCHECK(in_vars_to_scan_[var]);
1032 if (!ShouldScan(var)) {
1033 remove_var_to_scan_at_index(index);
1036 const auto [delta, scan_score] = jumps_.GetJump(var);
1037 if ((state_->counters.num_general_evals +
1038 state_->counters.num_linear_evals) %
1041 shared_time_limit_ !=
nullptr && shared_time_limit_->LimitReached()) {
1042 time_limit_crossed_ =
true;
1045 const int64_t current_value = state_->solution[var];
1046 DCHECK(var_domains_[var].Contains(current_value + delta))
1047 << var <<
" " << current_value <<
"+" << delta <<
" not in "
1048 << var_domains_[var].ToString();
1049 DCHECK(!var_domains_[var].
IsFixed());
1050 if (scan_score >= 0) {
1051 remove_var_to_scan_at_index(index);
1054 double score = scan_score;
1055 if (state_->options.use_compound_moves) {
1057 score = ComputeScore(
1058 state_->weights, var, delta,
1059 !var_occurs_in_non_linear_constraint_[var]);
1060 if (!ShouldExtendCompoundMove(score, score - scan_score)) {
1061 remove_var_to_scan_at_index(index);
1067 if (scan_score < best_scan_score) {
1068 DCHECK_NE(delta, 0) << score;
1070 *best_value = current_value + delta;
1071 *best_score = score;
1072 best_scan_score = scan_score;
1076 if (best_index != -1) {
1077 DCHECK_GT(num_good, 0);
1078 DCHECK_GE(*best_var, 0);
1079 DCHECK_EQ(*best_var, vars_to_scan_[best_index]);
1080 remove_var_to_scan_at_index(best_index);
1086void FeasibilityJumpSolver::AddVarToScan(
int var) {
1088 if (in_vars_to_scan_[var])
return;
1089 if (!ShouldScan(var))
return;
1090 vars_to_scan_.push_back(var);
1091 in_vars_to_scan_[var] =
true;
1094bool FeasibilityJumpSolver::ShouldScan(
int var)
const {
1097 if (state_->move->OnStack(var))
return false;
1099 if (!jumps_.NeedRecomputation(var)) {
1101 const double score = jumps_.Score(var);
1106 DCHECK(!var_domains_.IsFixed(var));
1109 if (var_domains_.HasBetterObjectiveValue(var))
return true;
1121 return evaluator_->NumViolatedConstraintsForVarIgnoringObjective(var) > 0;
1124void FeasibilityJumpSolver::RecomputeVarsToScan() {
1125 const int num_variables = var_domains_.size();
1126 jumps_.RecomputeAll(num_variables);
1127 DCHECK(SlowCheckNumViolatedConstraints());
1129 in_vars_to_scan_.assign(num_variables,
false);
1130 vars_to_scan_.clear();
1135 for (
const int var : var_domains_.FixedVariables()) {
1136 in_vars_to_scan_[var] =
true;
1139 num_ops_ += evaluator_->ViolatedConstraints().size();
1140 for (
const int c : evaluator_->ViolatedConstraints()) {
1141 num_ops_ += evaluator_->ConstraintToVars(c).size();
1142 for (
const int v : evaluator_->ConstraintToVars(c)) {
1148bool FeasibilityJumpSolver::SlowCheckNumViolatedConstraints()
const {
1149 std::vector<int> result;
1150 result.assign(var_domains_.size(), 0);
1151 for (
const int c : evaluator_->ViolatedConstraints()) {
1152 if (evaluator_->IsObjectiveConstraint(c))
continue;
1153 for (
const int v : evaluator_->ConstraintToVars(c)) {
1157 for (
int v = 0; v < result.size(); ++v) {
1159 evaluator_->NumViolatedConstraintsForVarIgnoringObjective(v));
1165 for (
const UnitMove& move : stack_) {
1166 var_on_stack_[move.var] =
false;
1172 return !stack_.empty() && var_on_stack_[var];
1176 if (stack_.empty())
return false;
1177 *var = stack_.back().var;
1178 *value = stack_.back().prev_value;
1179 *score = stack_.back().score;
1180 var_on_stack_[*var] =
false;
1182 if (!stack_.empty()) {
1183 ++stack_.back().discrepancy;
1189 DCHECK(!var_on_stack_[var]);
1190 if (!stack_.empty()) {
1191 stack_.back().best_child_score =
1192 std::min(stack_.back().best_child_score, score);
1194 var_on_stack_[var] =
true;
1197 .prev_value = prev_value,
1199 .cumulative_score =
Score() + score,
1205 absl::Span<const Domain> var_domains)
const {
1206 for (
const UnitMove& move : stack_) {
1207 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