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;
367 const int64_t best = shared_response_->GetBestSolutionObjective().value();
368 if (best < state_->last_solution_rank) {
369 states_->ResetLubyCounter();
370 new_best_solution_was_found =
true;
371 state_->last_solution_rank = best;
375 bool reset_weights =
false;
376 if (new_best_solution_was_found || state_->num_batches_before_change <= 0) {
377 reset_weights =
true;
378 if (state_->options.use_restart) {
379 states_->CollectStatistics(*state_);
380 state_->options.Randomize(params_, &random_);
383 state_->options.Randomize(params_, &random_);
388 state_->options.use_objective =
false;
391 const bool first_time = (state_->num_restarts == 0);
392 if (state_->options.use_restart || first_time ||
393 new_best_solution_was_found) {
396 state_->base_solution =
397 shared_response_->SolutionPool().GetSolutionToImprove(random_);
398 state_->solution = state_->base_solution->variable_values;
399 ++state_->num_solutions_imported;
403 const int num_violations = evaluator_->ViolatedConstraints().size();
404 shared_hints_->AddSolution(state_->solution, num_violations);
406 ResetCurrentSolution(first_time,
407 state_->options.use_objective,
408 state_->options.perturbation_probability);
411 PerturbateCurrentSolution(
412 params_.feasibility_jump_var_randomization_probability());
415 if (state_->options.use_restart) {
416 ++state_->num_restarts;
417 states_->ConfigureNextLubyRestart(state_);
420 ++state_->counters.num_perturbations;
421 state_->num_batches_before_change =
422 params_.violation_ls_perturbation_period();
431 bool recompute_compound_weights =
false;
432 if (linear_model_->model_proto().has_objective()) {
433 const IntegerValue lb = shared_response_->GetInnerObjectiveLowerBound();
434 const IntegerValue ub = shared_response_->GetInnerObjectiveUpperBound();
435 if (lb != state_->saved_inner_objective_lb ||
436 ub != state_->saved_inner_objective_ub) {
437 recompute_compound_weights =
true;
439 state_->saved_inner_objective_lb = lb;
440 state_->saved_inner_objective_ub = ub;
443 if (evaluator_->ReduceObjectiveBounds(lb.value(), ub.value())) {
444 recompute_compound_weights =
true;
449 if (!var_domains_.UpdateFromSharedBounds())
return;
455 const int num_vars = state_->solution.size();
456 for (
int var = 0; var < num_vars; ++var) {
457 const int64_t old_value = state_->solution[var];
458 const int64_t new_value = var_domains_[var].ClosestValue(old_value);
459 if (new_value != old_value) {
460 state_->solution[var] = new_value;
461 recompute_compound_weights =
true;
463 var_domains_.OnValueChange(var, new_value);
466 if (!state_->move->StackValuesInDomains(var_domains_.AsSpan())) {
467 recompute_compound_weights =
true;
474 evaluator_->ComputeAllViolations(state_->solution);
477 state_->bump_value = 1.0;
478 state_->weights.assign(evaluator_->NumEvaluatorConstraints(), 1.0);
479 recompute_compound_weights =
true;
481 if (recompute_compound_weights) {
482 state_->move->Clear();
483 if (state_->options.use_compound_moves) {
484 state_->compound_weights.assign(state_->weights.begin(),
485 state_->weights.end());
486 for (
int c = 0; c < state_->weights.size(); ++c) {
487 if (evaluator_->IsViolated(c))
continue;
488 state_->compound_weights[c] *= kCompoundDiscount;
490 state_->compound_weight_changed.clear();
491 state_->in_compound_weight_changed.assign(state_->weights.size(),
493 state_->compound_move_max_discrepancy = 0;
497 if (!state_->options.use_compound_moves) {
498 DCHECK_EQ(state_->move->Size(), 0);
501 ++state_->counters.num_batches;
502 if (DoSomeLinearIterations() && DoSomeGeneralIterations()) {
509 shared_response_, linear_model_->model_proto(), state_->solution,
510 absl::StrCat(
name(),
"_", state_->options.name(),
"(",
511 OneLineStats(),
")"),
512 state_->base_solution);
515 state_->base_solution = pointers.pushed_solution;
517 shared_response_->LogMessage(
name(),
"infeasible solution. Aborting.");
518 model_is_supported_ =
false;
528 const double current_dtime = DeterministicTime();
536 shared_time_limit_->AdvanceDeterministicTime(delta);
541 task_generated_ =
false;
545double FeasibilityJumpSolver::ComputeScore(absl::Span<const double> weights,
546 int var, int64_t delta,
549 double score = evaluator_->WeightedViolationDelta(
550 linear_only, weights, var, delta, absl::MakeSpan(state_->
solution));
551 constexpr double kEpsilon = 1.0 / std::numeric_limits<int64_t>::max();
552 score += kEpsilon * delta * evaluator_->ObjectiveCoefficient(var);
556std::pair<int64_t, double> FeasibilityJumpSolver::ComputeLinearJump(
int var) {
557 DCHECK(!var_domains_[var].
IsFixed());
558 const int64_t current_value = state_->
solution[var];
561 const LinearIncrementalEvaluator& linear_evaluator =
562 evaluator_->LinearEvaluator();
565 const int64_t min_value = var_domains_[var].Min();
566 const int64_t max_value = var_domains_[var].Max();
567 const int64_t delta = current_value == min_value ? max_value - min_value
568 : min_value - max_value;
569 return std::make_pair(
570 delta, ComputeScore(ScanWeights(), var, delta,
true));
578 const int64_t p1 = var_domains_[var].ValueAtOrBefore(current_value - 1);
579 const int64_t p2 = var_domains_[var].ValueAtOrAfter(current_value + 1);
581 std::pair<int64_t, double> best_jump;
582 const double v1 = var_domains_[var].Contains(p1)
583 ? ComputeScore(ScanWeights(), var, p1 - current_value,
585 :
std::numeric_limits<double>::infinity();
590 const Domain dom = var_domains_[var].IntersectionWith(
591 Domain(std::numeric_limits<int64_t>::min(), p1 - 1));
593 best_jump = {p1, v1};
596 linear_evaluator.SlopeBreakpoints(var, current_value, dom);
598 true, {p1, v1}, tmp_breakpoints_,
599 [
this, var, current_value](int64_t jump_value) {
600 return ComputeScore(ScanWeights(), var, jump_value - current_value,
605 const double v2 = var_domains_[var].Contains(p2)
606 ? ComputeScore(ScanWeights(), var, p2 - current_value,
608 : std::numeric_limits<double>::infinity();
612 const Domain dom = var_domains_[var].IntersectionWith(
613 Domain(p2 + 1, std::numeric_limits<int64_t>::max()));
615 best_jump = {p2, v2};
618 linear_evaluator.SlopeBreakpoints(var, current_value, dom);
620 false, {p2, v2}, tmp_breakpoints_,
621 [
this, var, current_value](int64_t jump_value) {
622 return ComputeScore(ScanWeights(), var,
623 jump_value - current_value,
633 best_jump = {p1, v1};
635 best_jump = {p2, v2};
639 DCHECK_NE(best_jump.first, current_value);
640 return std::make_pair(best_jump.first - current_value, best_jump.second);
643std::pair<int64_t, double> FeasibilityJumpSolver::ComputeGeneralJump(
int var) {
644 if (!var_occurs_in_non_linear_constraint_[var]) {
645 return ComputeLinearJump(var);
647 Domain domain = var_domains_[var];
648 if (domain.IsFixed())
return std::make_pair(0, 0.0);
650 ++state_->counters.num_general_evals;
651 const int64_t current_value = state_->solution[var];
652 domain = domain.IntersectionWith(
653 Domain(current_value, current_value).Complement());
654 std::pair<int64_t, double> result;
655 for (
int i = 0;
i < domain.NumIntervals(); ++
i) {
656 const int64_t min_delta = domain[
i].start - current_value;
657 const int64_t max_delta = domain[
i].end - current_value;
659 min_delta, max_delta + 1, [&](int64_t delta) ->
double {
660 return ComputeScore(ScanWeights(), var, delta,
false);
662 if (
i == 0 || score < result.second) {
663 result = std::make_pair(delta, score);
666 DCHECK(domain.
Contains(current_value + result.first))
667 << current_value <<
"+" << result.first <<
" not in domain "
668 << domain.ToString();
672void FeasibilityJumpSolver::UpdateViolatedConstraintWeights() {
673 ++state_->counters.num_weight_updates;
679 const double kMaxWeight = 1e10;
680 const double kBumpFactor = 1.0 / params_.feasibility_jump_decay();
681 const int num_variables = var_domains_.size();
682 if (state_->options.use_decay) {
683 state_->bump_value *= kBumpFactor;
688 bool rescale =
false;
689 num_ops_ += evaluator_->ViolatedConstraints().size();
690 for (
const int c : evaluator_->ViolatedConstraints()) {
691 DCHECK(evaluator_->IsViolated(c));
692 if (state_->options.use_compound_moves) {
693 DCHECK_EQ(state_->compound_weights[c], state_->weights[c]);
695 state_->weights[
c] += state_->bump_value;
696 if (state_->options.use_compound_moves) {
697 state_->compound_weights[
c] = state_->weights[
c];
699 if (state_->weights[c] > kMaxWeight) {
705 const double factor = 1.0 / kMaxWeight;
706 state_->bump_value *= factor;
707 for (
int c = 0;
c < state_->weights.size(); ++
c) {
708 state_->weights[
c] *= factor;
709 if (state_->options.use_compound_moves) {
710 state_->compound_weights[
c] *= factor;
713 jumps_.RecomputeAll(num_variables);
723 LinearIncrementalEvaluator* linear_evaluator =
724 evaluator_->MutableLinearEvaluator();
725 linear_evaluator->ClearAffectedVariables();
726 for_weight_update_.resize(num_variables);
727 num_ops_ += evaluator_->ViolatedConstraints().size();
728 for (
const int c : evaluator_->ViolatedConstraints()) {
729 if (c < evaluator_->NumLinearConstraints()) {
730 linear_evaluator->UpdateScoreOnWeightUpdate(
731 c, jumps_.Deltas(), absl::MakeSpan(for_weight_update_));
733 for (
const int v : evaluator_->ConstraintToVars(c)) {
742 absl::Span<double> scores = jumps_.MutableScores();
743 for (
const int var : linear_evaluator->VariablesAffectedByLastUpdate()) {
749 if (!var_domains_.HasTwoValues(var)) {
750 jumps_.Recompute(var);
753 scores[var] += state_->bump_value * for_weight_update_[var];
759bool FeasibilityJumpSolver::DoSomeLinearIterations() {
761 shared_response_->LogMessageWithThrottling(
name(), OneLineStats());
766 if (state_->options.use_compound_moves)
return true;
768 evaluator_->RecomputeViolatedList(
true);
769 jumps_.SetComputeFunction(
770 absl::bind_front(&FeasibilityJumpSolver::ComputeLinearJump,
this));
771 RecomputeVarsToScan();
775 const double dtime_threshold =
776 DeterministicTime() + params_.feasibility_jump_batch_dtime();
777 while (DeterministicTime() < dtime_threshold) {
779 while (DeterministicTime() < dtime_threshold) {
785 if (!ScanRelevantVariables(5, &best_var, &best_value,
791 ++state_->counters.num_linear_moves;
792 const int64_t prev_value = state_->solution[best_var];
793 state_->solution[best_var] = best_value;
794 evaluator_->UpdateLinearScores(best_var, prev_value, best_value,
795 state_->weights, jumps_.Deltas(),
796 jumps_.MutableScores());
797 evaluator_->UpdateViolatedList();
798 var_domains_.OnValueChange(best_var, best_value);
800 MarkJumpsThatNeedToBeRecomputed(best_var);
801 if (var_domains_.HasTwoValues(best_var)) {
804 jumps_.SetJump(best_var, prev_value - best_value, -best_score);
807 if (time_limit_crossed_)
return false;
810 if (vars_to_scan_.empty()) {
812 if (evaluator_->ViolatedConstraints().empty())
return true;
813 UpdateViolatedConstraintWeights();
832void FeasibilityJumpSolver::MarkJumpsThatNeedToBeRecomputed(
int changed_var) {
835 jumps_.Recompute(changed_var);
841 num_ops_ += evaluator_->VarToGeneralConstraints(changed_var).size();
842 for (
const int c : evaluator_->VarToGeneralConstraints(changed_var)) {
843 num_ops_ += evaluator_->GeneralConstraintToVars(c).size();
844 for (
const int var : evaluator_->GeneralConstraintToVars(c)) {
845 jumps_.Recompute(var);
851 num_ops_ += evaluator_->VariablesAffectedByLastLinearUpdate().size();
852 for (
const int var : evaluator_->VariablesAffectedByLastLinearUpdate()) {
853 if (!var_domains_.HasTwoValues(var)) {
854 jumps_.Recompute(var);
860bool FeasibilityJumpSolver::DoSomeGeneralIterations() {
861 if (!state_->options.use_compound_moves &&
862 evaluator_->NumNonLinearConstraints() == 0) {
866 evaluator_->ComputeAllNonLinearViolations(state_->solution);
867 evaluator_->RecomputeViolatedList(
false);
868 if (evaluator_->NumNonLinearConstraints() == 0) {
869 jumps_.SetComputeFunction(
870 absl::bind_front(&FeasibilityJumpSolver::ComputeLinearJump,
this));
872 jumps_.SetComputeFunction(
873 absl::bind_front(&FeasibilityJumpSolver::ComputeGeneralJump,
this));
875 RecomputeVarsToScan();
877 const double dtime_threshold =
878 DeterministicTime() + params_.feasibility_jump_batch_dtime();
879 while (DeterministicTime() < dtime_threshold) {
883 const bool found_move = ScanRelevantVariables(
884 3, &var, &new_value, &score);
885 const bool backtrack =
886 !found_move && state_->move->Backtrack(&var, &new_value, &score);
887 if (found_move || backtrack) {
888 if (backtrack) ++state_->counters.num_backtracks;
889 DCHECK_NE(var, -1) << var <<
" " << found_move <<
" " << backtrack;
892 ++state_->counters.num_general_moves;
893 const int64_t prev_value = state_->solution[var];
894 DCHECK_NE(prev_value, new_value);
895 state_->solution[var] = new_value;
898 evaluator_->UpdateLinearScores(var, prev_value, new_value, ScanWeights(),
899 jumps_.Deltas(), jumps_.MutableScores());
900 evaluator_->UpdateNonLinearViolations(var, prev_value, state_->solution);
901 evaluator_->UpdateViolatedList();
902 var_domains_.OnValueChange(var, new_value);
904 if (state_->options.use_compound_moves && !backtrack) {
908 for (
const auto& c : evaluator_->last_update_violation_changes()) {
909 if (evaluator_->IsViolated(c) &&
910 state_->compound_weights[c] != state_->weights[c]) {
911 state_->compound_weights[
c] = state_->weights[
c];
912 if (!state_->in_compound_weight_changed[c]) {
913 state_->in_compound_weight_changed[
c] =
true;
914 state_->compound_weight_changed.push_back(c);
916 for (
const int v : evaluator_->ConstraintToVars(c)) {
920 }
else if (!evaluator_->IsViolated(c) &&
921 !state_->in_compound_weight_changed[c] &&
922 state_->compound_weights[c] == state_->weights[c]) {
923 state_->in_compound_weight_changed[
c] =
true;
924 state_->compound_weight_changed.push_back(c);
933 if (
false && !state_->options.use_decay) {
934 DCHECK_EQ(-score, ComputeScore(state_->weights, var,
935 prev_value - new_value,
false));
936 DCHECK_EQ(-score, ComputeScore(ScanWeights(), var,
937 prev_value - new_value,
false));
940 MarkJumpsThatNeedToBeRecomputed(var);
941 if (var_domains_.HasTwoValues(var)) {
944 jumps_.SetJump(var, prev_value - new_value, -score);
947 if (state_->options.use_compound_moves && !backtrack) {
949 DCHECK_NE(prev_value, state_->solution[var]);
950 state_->move->Push(var, prev_value, score);
951 if (state_->move->Score() < 0) {
952 state_->counters.num_compound_moves += state_->move->Size();
953 state_->move->Clear();
954 state_->compound_move_max_discrepancy = 0;
958 }
else if (time_limit_crossed_) {
962 DCHECK_EQ(state_->move->Size(), 0);
963 if (evaluator_->ViolatedConstraints().empty())
return true;
964 if (state_->options.use_compound_moves) {
965 ResetChangedCompoundWeights();
967 if (!state_->options.use_compound_moves ||
968 ++state_->compound_move_max_discrepancy > 2) {
969 state_->compound_move_max_discrepancy = 0;
970 UpdateViolatedConstraintWeights();
976void FeasibilityJumpSolver::ResetChangedCompoundWeights() {
977 if (!state_->options.use_compound_moves)
return;
978 DCHECK_EQ(state_->move->Size(), 0);
979 num_ops_ += state_->compound_weight_changed.size();
980 for (
const int c : state_->compound_weight_changed) {
981 state_->in_compound_weight_changed[
c] =
false;
982 const double expected_weight =
983 (evaluator_->IsViolated(c) ? 1.0 : kCompoundDiscount) *
985 if (state_->compound_weights[c] == expected_weight)
continue;
986 state_->compound_weights[
c] = expected_weight;
987 num_ops_ += evaluator_->ConstraintToVars(c).size();
988 for (
const int var : evaluator_->ConstraintToVars(c)) {
989 jumps_.Recompute(var);
993 state_->compound_weight_changed.clear();
996bool FeasibilityJumpSolver::ShouldExtendCompoundMove(
double score,
998 if (state_->move->Score() + score - std::max(novelty, 0.0) < 0) {
1001 return score < state_->move->BestChildScore();
1004bool FeasibilityJumpSolver::ScanRelevantVariables(
int num_to_scan,
1006 int64_t* best_value,
1007 double* best_score) {
1008 if (time_limit_crossed_)
return false;
1009 if (state_->move->Discrepancy() > state_->compound_move_max_discrepancy) {
1012 double best_scan_score = 0.0;
1014 int best_index = -1;
1018 auto remove_var_to_scan_at_index = [&](
int index) {
1019 in_vars_to_scan_[vars_to_scan_[index]] =
false;
1020 vars_to_scan_[index] = vars_to_scan_.back();
1021 vars_to_scan_.pop_back();
1022 if (best_index == vars_to_scan_.size()) {
1026 while (!vars_to_scan_.empty() && num_good < num_to_scan) {
1028 const int index = absl::Uniform<int>(random_, 0, vars_to_scan_.size());
1029 const int var = vars_to_scan_[index];
1031 DCHECK(in_vars_to_scan_[var]);
1033 if (!ShouldScan(var)) {
1034 remove_var_to_scan_at_index(index);
1037 const auto [delta, scan_score] = jumps_.GetJump(var);
1038 if ((state_->counters.num_general_evals +
1039 state_->counters.num_linear_evals) %
1042 shared_time_limit_ !=
nullptr && shared_time_limit_->LimitReached()) {
1043 time_limit_crossed_ =
true;
1046 const int64_t current_value = state_->solution[var];
1047 DCHECK(var_domains_[var].Contains(current_value + delta))
1048 << var <<
" " << current_value <<
"+" << delta <<
" not in "
1049 << var_domains_[var].ToString();
1050 DCHECK(!var_domains_[var].
IsFixed());
1051 if (scan_score >= 0) {
1052 remove_var_to_scan_at_index(index);
1055 double score = scan_score;
1056 if (state_->options.use_compound_moves) {
1058 score = ComputeScore(
1059 state_->weights, var, delta,
1060 !var_occurs_in_non_linear_constraint_[var]);
1061 if (!ShouldExtendCompoundMove(score, score - scan_score)) {
1062 remove_var_to_scan_at_index(index);
1068 if (scan_score < best_scan_score) {
1069 DCHECK_NE(delta, 0) << score;
1071 *best_value = current_value + delta;
1072 *best_score = score;
1073 best_scan_score = scan_score;
1077 if (best_index != -1) {
1078 DCHECK_GT(num_good, 0);
1079 DCHECK_GE(*best_var, 0);
1080 DCHECK_EQ(*best_var, vars_to_scan_[best_index]);
1081 remove_var_to_scan_at_index(best_index);
1087void FeasibilityJumpSolver::AddVarToScan(
int var) {
1089 if (in_vars_to_scan_[var])
return;
1090 if (!ShouldScan(var))
return;
1091 vars_to_scan_.push_back(var);
1092 in_vars_to_scan_[var] =
true;
1095bool FeasibilityJumpSolver::ShouldScan(
int var)
const {
1098 if (state_->move->OnStack(var))
return false;
1100 if (!jumps_.NeedRecomputation(var)) {
1102 const double score = jumps_.Score(var);
1107 DCHECK(!var_domains_.IsFixed(var));
1110 if (var_domains_.HasBetterObjectiveValue(var))
return true;
1122 return evaluator_->NumViolatedConstraintsForVarIgnoringObjective(var) > 0;
1125void FeasibilityJumpSolver::RecomputeVarsToScan() {
1126 const int num_variables = var_domains_.size();
1127 jumps_.RecomputeAll(num_variables);
1128 DCHECK(SlowCheckNumViolatedConstraints());
1130 in_vars_to_scan_.assign(num_variables,
false);
1131 vars_to_scan_.clear();
1136 for (
const int var : var_domains_.FixedVariables()) {
1137 in_vars_to_scan_[var] =
true;
1140 num_ops_ += evaluator_->ViolatedConstraints().size();
1141 for (
const int c : evaluator_->ViolatedConstraints()) {
1142 num_ops_ += evaluator_->ConstraintToVars(c).size();
1143 for (
const int v : evaluator_->ConstraintToVars(c)) {
1149bool FeasibilityJumpSolver::SlowCheckNumViolatedConstraints()
const {
1150 std::vector<int> result;
1151 result.assign(var_domains_.size(), 0);
1152 for (
const int c : evaluator_->ViolatedConstraints()) {
1153 if (evaluator_->IsObjectiveConstraint(c))
continue;
1154 for (
const int v : evaluator_->ConstraintToVars(c)) {
1158 for (
int v = 0; v < result.size(); ++v) {
1160 evaluator_->NumViolatedConstraintsForVarIgnoringObjective(v));
1166 for (
const UnitMove& move : stack_) {
1167 var_on_stack_[move.var] =
false;
1173 return !stack_.empty() && var_on_stack_[var];
1177 if (stack_.empty())
return false;
1178 *var = stack_.back().var;
1179 *value = stack_.back().prev_value;
1180 *score = stack_.back().score;
1181 var_on_stack_[*var] =
false;
1183 if (!stack_.empty()) {
1184 ++stack_.back().discrepancy;
1190 DCHECK(!var_on_stack_[var]);
1191 if (!stack_.empty()) {
1192 stack_.back().best_child_score =
1193 std::min(stack_.back().best_child_score, score);
1195 var_on_stack_[var] =
true;
1198 .prev_value = prev_value,
1200 .cumulative_score =
Score() + score,
1206 absl::Span<const Domain> var_domains)
const {
1207 for (
const UnitMove& move : stack_) {
1208 if (!var_domains[move.var].Contains(move.prev_value))
return false;
bool StackValuesInDomains(absl::Span< const Domain > var_domains) const
bool OnStack(int var) const
bool Backtrack(int *var, int64_t *value, double *score)
void Push(int var, int64_t prev_value, double score)
~FeasibilityJumpSolver() override
std::function< void()> GenerateTask(int64_t) final
void RecomputeAll(int num_variables)
std::pair< int64_t, double > GetJump(int var)
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)
void AddTaskDeterministicDuration(double deterministic_duration)
SubsolverType type() const
double deterministic_time() const
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)
PushedSolutionPointers PushAndMaybeCombineSolution(SharedResponseManager *response_manager, const CpModelProto &model_proto, absl::Span< const int64_t > new_solution, absl::string_view solution_info, std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > base_solution)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
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)
std::string FormatCounter(int64_t num)
std::pair< Point, Value > ConvexMinimum(absl::Span< const Point > sorted_points, std::function< Value(Point)> f)
bool Contains(int64_t value) const
int64_t num_scores_computed
std::vector< int64_t > solution
std::unique_ptr< CompoundMoveBuilder > move