Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
feasibility_jump.h
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#ifndef OR_TOOLS_SAT_FEASIBILITY_JUMP_H_
15#define OR_TOOLS_SAT_FEASIBILITY_JUMP_H_
16
17#include <algorithm>
18#include <atomic>
19#include <cstddef>
20#include <cstdint>
21#include <functional>
22#include <limits>
23#include <memory>
24#include <string>
25#include <utility>
26#include <vector>
27
28#include "absl/container/flat_hash_map.h"
29#include "absl/functional/any_invocable.h"
30#include "absl/log/check.h"
31#include "absl/random/distributions.h"
32#include "absl/strings/str_join.h"
33#include "absl/strings/string_view.h"
34#include "absl/synchronization/mutex.h"
35#include "absl/types/span.h"
39#include "ortools/sat/restart.h"
40#include "ortools/sat/sat_parameters.pb.h"
44#include "ortools/sat/util.h"
46
48
50
51// This class lazily caches the results of `compute_jump(var)` which returns a
52// <delta, score> pair.
53// Variables' scores can be manually modified using MutableScores (if the
54// optimal jump is known not to change), or marked for recomputation on the next
55// call to GetJump(var) by calling Recompute.
56class JumpTable {
57 public:
58 JumpTable() = default;
59
61 absl::AnyInvocable<std::pair<int64_t, double>(int) const> compute_jump);
62
63 void RecomputeAll(int num_variables);
64
65 // Gets the current jump delta and score, recomputing if necessary.
66 std::pair<int64_t, double> GetJump(int var);
67
68 // If the new optimum value and score is known, users can update it directly.
69 // e.g. after weight rescaling, or after changing a binary variable.
70 void SetJump(int var, int64_t delta, double score);
71
72 // Recompute the jump for `var` when `GetJump(var)` is next called.
73 void Recompute(int var);
74
75 bool NeedRecomputation(int var) const { return needs_recomputation_[var]; }
76
77 double Score(int var) const { return scores_[var]; }
78
79 // Advanced usage, allows users to read possibly stale deltas for incremental
80 // score updates.
81 absl::Span<const int64_t> Deltas() const {
82 return absl::MakeConstSpan(deltas_);
83 }
84 absl::Span<const double> Scores() const {
85 return absl::MakeConstSpan(scores_);
86 }
87
88 absl::Span<double> MutableScores() { return absl::MakeSpan(scores_); }
89
90 // For debugging and testing.
91 //
92 // Note if you have very high weights (e.g. when using decay), the tolerances
93 // in this function are likely too tight.
94 bool JumpIsUpToDate(int var) const;
95
96 private:
97 absl::AnyInvocable<std::pair<int64_t, double>(int) const> compute_jump_;
98
99 // For each variable, we store:
100 // - A jump delta which represents a change in variable value:
101 // new_value = old + delta, which is non-zero except for fixed variables.
102 // - The associated weighted feasibility violation change if we take this
103 // jump.
104 std::vector<int64_t> deltas_;
105 std::vector<double> scores_;
106 std::vector<bool> needs_recomputation_;
107};
108
109// Accessing Domain can be expensive, so we maintain vector of bool for the
110// hot spots.
112 public:
113 explicit VarDomainWrapper(SharedBoundsManager* shared_bounds)
114 : shared_bounds_id_(
115 shared_bounds == nullptr ? 0 : shared_bounds->RegisterNewId()),
116 shared_bounds_(shared_bounds) {}
117
118 Domain operator[](int var) const { return domains_[var]; }
119 bool HasTwoValues(int var) const { return has_two_values_[var]; }
120 size_t size() const { return domains_.size(); }
121
122 void resize(int num_vars) {
123 domains_.resize(num_vars);
124 has_two_values_.resize(num_vars);
125 is_fixed_.resize(num_vars, false);
126 objective_is_positive_.resize(num_vars, false);
127 objective_is_negative_.resize(num_vars, false);
128 has_better_objective_value_.resize(num_vars, false);
129 }
130
131 void Set(int var, Domain d) {
132 has_two_values_[var] = d.HasTwoValues();
133 if (is_fixed_[var]) {
134 // The code here assume that once fixed, a variable stays that way.
135 CHECK(d.IsFixed());
136 } else if (d.IsFixed()) {
137 is_fixed_[var] = true;
138 fixed_vars_.push_back(var);
139 }
140 domains_[var] = std::move(d);
141 }
142
143 // Return false if one of the domain becomes empty (UNSAT). This might happen
144 // while we are cleaning up all workers at the end of a search.
146 if (shared_bounds_ == nullptr) return true;
147 shared_bounds_->GetChangedBounds(shared_bounds_id_, &tmp_variables_,
148 &tmp_new_lower_bounds_,
149 &tmp_new_upper_bounds_);
150 for (int i = 0; i < tmp_variables_.size(); ++i) {
151 const int var = tmp_variables_[i];
152 const Domain new_domain = domains_[var].IntersectionWith(
153 Domain(tmp_new_lower_bounds_[i], tmp_new_upper_bounds_[i]));
154 if (new_domain.IsEmpty()) return false;
155 Set(var, new_domain);
156 }
157 return true;
158 }
159
160 absl::Span<const Domain> AsSpan() const { return domains_; }
161
162 void InitializeObjective(const CpModelProto& cp_model_proto) {
163 if (!cp_model_proto.has_objective()) return;
164 const int num_terms = cp_model_proto.objective().vars().size();
165 for (int i = 0; i < num_terms; ++i) {
166 const int var = cp_model_proto.objective().vars(i);
167 const int coeff = cp_model_proto.objective().coeffs(i);
168 objective_is_positive_[var] = coeff > 0;
169 objective_is_negative_[var] = coeff < 0;
170 }
171 }
172
173 bool IsFixed(int var) const { return is_fixed_[var]; }
174
175 bool HasBetterObjectiveValue(int var) const {
176 return has_better_objective_value_[var];
177 }
178
179 // Tricky: this must be called on solution value change or domains update.
180 void OnValueChange(int var, int64_t value) {
181 has_better_objective_value_[var] =
182 (objective_is_positive_[var] && value > domains_[var].Min()) ||
183 (objective_is_negative_[var] && value < domains_[var].Max());
184 }
185
186 absl::Span<const int> FixedVariables() const { return fixed_vars_; }
187
188 private:
189 const int shared_bounds_id_;
190 SharedBoundsManager* shared_bounds_;
191
192 // Basically fixed once and for all.
193 std::vector<bool> objective_is_positive_;
194 std::vector<bool> objective_is_negative_;
195
196 // Depends on domain updates.
197 std::vector<Domain> domains_;
198 std::vector<bool> has_two_values_;
199 std::vector<bool> is_fixed_;
200 std::vector<int> fixed_vars_;
201
202 // This is the only one that depends on the current solution value.
203 std::vector<bool> has_better_objective_value_;
204
205 // Temporary data for UpdateFromSharedBounds()
206 std::vector<int> tmp_variables_;
207 std::vector<int64_t> tmp_new_lower_bounds_;
208 std::vector<int64_t> tmp_new_upper_bounds_;
209};
210
211// Local search counters. This can either be the stats of one run without
212// restart or some aggregation of such runs.
238
239// The parameters used by the local search code.
240struct LsOptions {
241 // This one never changes.
242 // - If true, each restart is independent from the other. This is nice because
243 // it plays well with the theoretical Luby restart sequence.
244 // - If false, we always "restart" from the current state, but we perturb it
245 // or just reset the constraint weight. We currently use this one way less
246 // often.
247 bool use_restart = true;
248
249 // These are randomized each restart by Randomize().
251 bool use_decay = true;
253 bool use_objective = true; // No effect if there are no objective.
254
255 // Allows to identify which options worked well.
256 std::string name() const {
257 std::vector<absl::string_view> parts;
258 parts.reserve(5);
259 if (use_restart) parts.push_back("restart");
260 if (use_decay) parts.push_back("decay");
261 if (use_compound_moves) parts.push_back("compound");
262 if (perturbation_probability > 0) parts.push_back("perturb");
263 if (use_objective) parts.push_back("obj");
264 return absl::StrJoin(parts, "_");
265 }
266
267 // In order to collect statistics by options.
268 template <typename H>
269 friend H AbslHashValue(H h, const LsOptions& o) {
270 return H::combine(std::move(h), o.use_restart, o.perturbation_probability,
272 }
273
281
282 void Randomize(const SatParameters& params, ModelRandomGenerator* random) {
284 absl::Bernoulli(*random, 0.5)
285 ? 0.0
286 : params.feasibility_jump_var_randomization_probability();
287 use_decay = absl::Bernoulli(*random, 0.5);
288 use_compound_moves = absl::Bernoulli(*random, 0.5);
289 use_objective = absl::Bernoulli(*random, 0.5);
290 }
291};
292
293// Each FeasibilityJumpSolver work on many LsState in an interleaved parallel
294// fashion. Each "batch of moves" will update one of these state. Restart
295// heuristic are also on a per state basis.
296//
297// This allows to not use O(problem size) per state while having a more
298// diverse set of heuristics.
299struct LsState {
300 // The score of a solution is just the sum of infeasibility of each
301 // constraint weighted by these weights.
302 std::vector<int64_t> solution;
303 std::vector<double> weights;
304 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
306
307 // Depending on the options, we use an exponentially decaying constraint
308 // weight like for SAT activities.
309 double bump_value = 1.0;
310
311 // If using compound moves, these will be discounted on a new incumbent then
312 // re-converge to weights after some exploration. Search will repeatedly pick
313 // moves with negative WeightedViolationDelta using these weights.
314 //
315 // We limit the discrepancy in compound move search (i.e. limit the number of
316 // backtracks to any ancestor of the current leaf). This is set to 0 whenever
317 // a new incumbent is found or weights are updated, and increased at fixed
318 // point. Weights are only increased if no moves are found with discrepancy 2.
319 // Empirically we have seen very few moves applied with discrepancy > 2.
321 std::vector<double> compound_weights;
322 std::vector<bool> in_compound_weight_changed;
323 std::vector<int> compound_weight_changed;
324 std::unique_ptr<CompoundMoveBuilder> move;
325
326 // Counters for a "non-restarted" run.
328
329 // Strategy
331
332 // Global counters, incremented across restart.
333 int64_t num_restarts = 0;
335
336 // When this reach zero, we restart / perturbate or trigger something.
338
339 // Used by LS to know the rank of the starting solution for this state.
340 int64_t last_solution_rank = std::numeric_limits<int64_t>::max();
341
342 // Tricky: If this changed since last time, we need to recompute the
343 // compound moves as the objective constraint bound changed.
344 IntegerValue saved_inner_objective_lb = 0;
345 IntegerValue saved_inner_objective_ub = 0;
346};
347
348// Shared set of local search states that we work on.
350 public:
351 // Important: max_parallelism should be greater or equal than the actual
352 // number of thread sharing this class, otherwise the code will break.
353 SharedLsStates(absl::string_view name, const SatParameters& params,
354 SharedStatTables* stat_tables)
355 : name_(name), params_(params), stat_tables_(stat_tables) {
356 // We always start with at least 8 states.
357 // We will create more if there are more parallel workers as needed.
358 for (int i = 0; i < 8; ++i) CreateNewState();
359 }
360
362
364 const int index = states_.size();
365 states_.emplace_back(new LsState());
366 taken_.push_back(false);
367 num_selected_.push_back(0);
368
369 // We add one no-restart per 16 states and put it last.
370 states_.back()->options.use_restart = (index % 16 != 15);
371 }
372
373 // Returns the next available state in round-robin fashion.
374 // This is thread safe. If we respect the max_parallelism guarantee, then
375 // all states should be independent.
377 absl::MutexLock mutex_lock(&mutex_);
378 int next = -1;
379 const int num_states = states_.size();
380 for (int i = 0; i < num_states; ++i) {
381 const int index = round_robin_index_;
382 round_robin_index_ = (round_robin_index_ + 1) % num_states;
383 if (taken_[index]) continue;
384 if (next == -1 || num_selected_[index] < num_selected_[next]) {
385 next = index;
386 }
387 }
388
389 if (next == -1) {
390 // We need more parallelism and create a new state.
391 next = num_states;
393 }
394
395 --states_[next]->num_batches_before_change;
396 taken_[next] = true;
397 num_selected_[next]++;
398 return states_[next].get();
399 }
400
401 void Release(LsState* state) {
402 absl::MutexLock mutex_lock(&mutex_);
403 for (int i = 0; i < states_.size(); ++i) {
404 if (state == states_[i].get()) {
405 taken_[i] = false;
406 break;
407 }
408 }
409 }
410
412 absl::MutexLock mutex_lock(&mutex_);
413 luby_counter_ = 0;
414 }
415
416 // We share a global running Luby sequence for all the "restart" state.
417 // Note that we randomize the parameters on each restart.
418 //
419 // Hack: options.use_restart is constant, so we are free to inspect it.
420 // Also if options.use_restart, then num_batches_before_change is only
421 // modified under lock, so this code should be thread safe.
423 absl::MutexLock mutex_lock(&mutex_);
424 const int factor = std::max(1, params_.feasibility_jump_restart_factor());
425 CHECK(state->options.use_restart);
426 const int64_t next = factor * SUniv(++luby_counter_);
427 state->num_batches_before_change = next;
428 }
429
430 // Accumulate in the relevant bucket the counters of the given states.
431 void CollectStatistics(const LsState& state) {
432 if (state.counters.num_batches == 0) return;
433
434 absl::MutexLock mutex_lock(&mutex_);
435 options_to_stats_[state.options].AddFrom(state.counters);
436 options_to_num_restarts_[state.options]++;
437 }
438
439 private:
440 const std::string name_;
441 const SatParameters& params_;
442 SharedStatTables* stat_tables_;
443
444 mutable absl::Mutex mutex_;
445 int round_robin_index_ = 0;
446 std::vector<std::unique_ptr<LsState>> states_;
447 std::vector<bool> taken_;
448 std::vector<int> num_selected_;
449 int luby_counter_ = 0;
450
451 absl::flat_hash_map<LsOptions, LsCounters> options_to_stats_;
452 absl::flat_hash_map<LsOptions, int> options_to_num_restarts_;
453};
454
455// Implements and heuristic similar to the one described in the paper:
456// "Feasibility Jump: an LP-free Lagrangian MIP heuristic", Bjørnar
457// Luteberget, Giorgio Sartor, 2023, Mathematical Programming Computation.
458//
459// This is basically a Guided local search (GLS) with a nice algo to know what
460// value an integer variable should move to (its jump value). For binary, it
461// can only be swapped, so the situation is easier.
462//
463// TODO(user): If we have more than one of these solver, we might want to share
464// the evaluator memory between them. Right now we basically keep a copy of the
465// model and its transpose for each FeasibilityJumpSolver.
467 public:
468 FeasibilityJumpSolver(const absl::string_view name,
470 const LinearModel* linear_model, SatParameters params,
471 std::shared_ptr<SharedLsStates> ls_states,
472 ModelSharedTimeLimit* shared_time_limit,
473 SharedResponseManager* shared_response,
474 SharedBoundsManager* shared_bounds,
475 SharedLsSolutionRepository* shared_hints,
476 SharedStatistics* shared_stats,
477 SharedStatTables* stat_tables)
478 : SubSolver(name, type),
479 linear_model_(linear_model),
480 params_(params),
481 states_(std::move(ls_states)),
482 shared_time_limit_(shared_time_limit),
483 shared_response_(shared_response),
484 shared_hints_(shared_hints),
485 stat_tables_(stat_tables),
486 random_(params_),
487 var_domains_(shared_bounds) {}
488
489 // If VLOG_IS_ON(1), it will export a bunch of statistics.
490 ~FeasibilityJumpSolver() override;
491
492 // No synchronization needed for TaskIsAvailable().
493 void Synchronize() final {}
494
495 // Shall we delete this subsolver?
496 bool IsDone() final {
497 if (!model_is_supported_.load()) return true;
498
499 // We are done after the first task is done in the FIRST_SOLUTION mode.
500 return type() == SubSolver::FIRST_SOLUTION &&
501 shared_response_->first_solution_solvers_should_stop()->load();
502 }
503
504 bool TaskIsAvailable() final {
505 if (IsDone()) return false;
506 if (task_generated_.load()) return false;
507 if (shared_response_->ProblemIsSolved()) return false;
508 if (shared_time_limit_->LimitReached()) return false;
509
510 return (shared_response_->SolutionsRepository().NumSolutions() > 0) ==
512 }
513
514 std::function<void()> GenerateTask(int64_t /*task_id*/) final;
515
516 private:
517 void ImportState();
518 void ReleaseState();
519
520 void Initialize();
521 void ResetCurrentSolution(bool use_hint, bool use_objective,
522 double perturbation_probability);
523 void PerturbateCurrentSolution(double perturbation_probability);
524 std::string OneLineStats() const;
525
526 absl::Span<const double> ScanWeights() const {
527 return absl::MakeConstSpan(state_->options.use_compound_moves
528 ? state_->compound_weights
529 : state_->weights);
530 }
531
532 // Returns the weighted violation delta plus epsilon * the objective delta.
533 double ComputeScore(absl::Span<const double> weights, int var, int64_t delta,
534 bool linear_only);
535
536 // Computes the optimal value for variable v, considering only the violation
537 // of linear constraints.
538 std::pair<int64_t, double> ComputeLinearJump(int var);
539
540 // Computes the optimal value for variable v, considering all constraints
541 // (assuming violation functions are convex).
542 std::pair<int64_t, double> ComputeGeneralJump(int var);
543
544 // Marks all variables whose jump value may have changed due to the last
545 // update, except for `changed var`.
546 void MarkJumpsThatNeedToBeRecomputed(int changed_var);
547
548 // Moves.
549 bool DoSomeLinearIterations();
550 bool DoSomeGeneralIterations();
551
552 // Returns true if an improving move was found.
553 bool ScanRelevantVariables(int num_to_scan, int* var, int64_t* value,
554 double* score);
555
556 // Increases the weight of the currently infeasible constraints.
557 // Ensures jumps remains consistent.
558 void UpdateViolatedConstraintWeights();
559
560 // Returns true if it is possible that `var` may have value that reduces
561 // weighted violation or improve the objective.
562 // Note that this is independent of the actual weights used.
563 bool ShouldScan(int var) const;
564
565 void AddVarToScan(int var);
566 void RecomputeVarsToScan();
567
568 // Resets the weights used to find compound moves.
569 // Ensures the following invariant holds afterwards:
570 // compound_weights[c] = weights_[c] if c is violated, and epsilon *
571 // weights_[c] otherwise.
572 void ResetChangedCompoundWeights();
573
574 // Returns true if we should push this change to move_.
575 // `novelty` is the total discount applied to the score due to using
576 // `cumulative_weights_`, should always be positive (modulo floating-point
577 // errors).
578 bool ShouldExtendCompoundMove(double score, double novelty);
579
580 // Validates each element in num_violated_constraints_per_var_ against
581 // evaluator_->ViolatedConstraints.
582 bool SlowCheckNumViolatedConstraints() const;
583
584 double DeterministicTime() const {
585 return evaluator_->DeterministicTime() + num_ops_ * 1e-8;
586 }
587
588 const LinearModel* linear_model_;
589 SatParameters params_;
590 std::shared_ptr<SharedLsStates> states_;
591 ModelSharedTimeLimit* shared_time_limit_;
592 SharedResponseManager* shared_response_;
593 SharedLsSolutionRepository* shared_hints_;
594 SharedStatTables* stat_tables_;
595 ModelRandomGenerator random_;
596
597 VarDomainWrapper var_domains_;
598
599 // Synchronization Booleans.
600 //
601 // Note that we don't fully support all type of model, and we will abort by
602 // setting the model_is_supported_ bool to false when we detect this.
603 bool is_initialized_ = false;
604 std::atomic<bool> model_is_supported_ = true;
605 std::atomic<bool> task_generated_ = false;
606 bool time_limit_crossed_ = false;
607
608 std::unique_ptr<LsEvaluator> evaluator_;
609 std::vector<bool> var_occurs_in_non_linear_constraint_;
610
611 JumpTable jumps_;
612 std::vector<double> for_weight_update_;
613
614 // The current sate we work on.
615 LsState* state_;
616
617 // A list of variables that might be relevant to check for improving jumps.
618 std::vector<bool> in_vars_to_scan_;
619 FixedCapacityVector<int> vars_to_scan_;
620
621 std::vector<int64_t> tmp_breakpoints_;
622
623 // For counting the dtime. See DeterministicTime().
624 int64_t num_ops_ = 0;
625};
626
627// This class helps keep track of moves that change more than one variable.
628// Mainly this class keeps track of how to backtrack back to the local minimum
629// as you make a sequence of exploratory moves, so in order to commit a compound
630// move, you just need to call `Clear` instead of Backtracking over the changes.
632 public:
633 explicit CompoundMoveBuilder(int num_variables)
634 : var_on_stack_(num_variables, false) {}
635
636 // Adds an atomic move to the stack.
637 // `var` must not be on the stack (this is DCHECKed).
638 void Push(int var, int64_t prev_value, double score);
639
640 // Sets var, val and score to a move that will revert the most recent atomic
641 // move on the stack, and pops this move from the stack.
642 // Returns false if the stack is empty.
643 bool Backtrack(int* var, int64_t* value, double* score);
644
645 // Removes all moves on the stack.
646 void Clear();
647
648 // Returns the number of variables in the move.
649 int Size() const { return stack_.size(); }
650
651 // Returns true if this var has been set in this move already,
652 bool OnStack(int var) const;
653
654 // Returns the sum of scores of atomic moved pushed to this compound move.
655 double Score() const {
656 return stack_.empty() ? 0.0 : stack_.back().cumulative_score;
657 }
658
659 double BestChildScore() const {
660 return stack_.empty() ? 0.0 : stack_.back().best_child_score;
661 }
662
663 // Returns the number of backtracks to any ancestor of the current leaf.
664 int Discrepancy() const {
665 return stack_.empty() ? 0 : stack_.back().discrepancy;
666 }
667
668 // Returns true if all prev_values on the stack are in the appropriate domain.
669 bool StackValuesInDomains(absl::Span<const Domain> var_domains) const;
670
671 private:
672 struct UnitMove {
673 int var;
674 int64_t prev_value;
675 // Note that this stores the score of reverting to prev_value.
676 double score;
677 // Instead of tracking this on the compound move, we store the partial sums
678 // to avoid numerical issues causing negative scores after backtracking.
679 double cumulative_score;
680
681 // Used to avoid infinite loops, this tracks the best score of any immediate
682 // children (and not deeper descendants) to avoid re-exploring the same
683 // prefix.
684 double best_child_score = 0.0;
685 int discrepancy = 0;
686 };
687 std::vector<bool> var_on_stack_;
688 std::vector<UnitMove> stack_;
689};
690
691} // namespace operations_research::sat
692
693#endif // OR_TOOLS_SAT_FEASIBILITY_JUMP_H_
Domain IntersectionWith(const Domain &domain) const
bool StackValuesInDomains(absl::Span< const Domain > var_domains) const
Returns true if all prev_values on the stack are in the appropriate domain.
double Score() const
Returns the sum of scores of atomic moved pushed to this compound move.
void Clear()
Removes all moves on the stack.
int Size() const
Returns the number of variables in the move.
int Discrepancy() const
Returns the number of backtracks to any ancestor of the current leaf.
bool OnStack(int var) const
Returns true if this var has been set in this move already,.
bool Backtrack(int *var, int64_t *value, double *score)
void Push(int var, int64_t prev_value, double score)
~FeasibilityJumpSolver() override
If VLOG_IS_ON(1), it will export a bunch of statistics.
bool IsDone() final
Shall we delete this subsolver?
FeasibilityJumpSolver(const absl::string_view name, SubSolver::SubsolverType type, const LinearModel *linear_model, SatParameters params, std::shared_ptr< SharedLsStates > ls_states, ModelSharedTimeLimit *shared_time_limit, SharedResponseManager *shared_response, SharedBoundsManager *shared_bounds, SharedLsSolutionRepository *shared_hints, SharedStatistics *shared_stats, SharedStatTables *stat_tables)
void Synchronize() final
No synchronization needed for TaskIsAvailable().
std::function< void()> GenerateTask(int64_t) final
void Recompute(int var)
Recompute the jump for var when GetJump(var) is next called.
std::pair< int64_t, double > GetJump(int var)
Gets the current jump delta and score, recomputing if necessary.
absl::Span< const double > Scores() const
absl::Span< const int64_t > Deltas() const
void SetComputeFunction(absl::AnyInvocable< std::pair< int64_t, double >(int) const > compute_jump)
void SetJump(int var, int64_t delta, double score)
The model "singleton" shared time limit.
Definition util.h:345
void CollectStatistics(const LsState &state)
Accumulate in the relevant bucket the counters of the given states.
SharedLsStates(absl::string_view name, const SatParameters &params, SharedStatTables *stat_tables)
Contains the table we display after the solver is done.
Definition stat_tables.h:33
Simple class to add statistics by name and print them at the end.
SubSolver(absl::string_view name, SubsolverType type)
Definition subsolver.h:48
SubsolverType type() const
Returns the type of the subsolver.
Definition subsolver.h:98
std::string name() const
Returns the name of this SubSolver. Used in logs.
Definition subsolver.h:95
VarDomainWrapper(SharedBoundsManager *shared_bounds)
void InitializeObjective(const CpModelProto &cp_model_proto)
absl::Span< const Domain > AsSpan() const
void OnValueChange(int var, int64_t value)
Tricky: this must be called on solution value change or domains update.
absl::Span< const int > FixedVariables() const
STL namespace.
The parameters used by the local search code.
void Randomize(const SatParameters &params, ModelRandomGenerator *random)
bool operator==(const LsOptions &o) const
friend H AbslHashValue(H h, const LsOptions &o)
In order to collect statistics by options.
double perturbation_probability
These are randomized each restart by Randomize().
std::string name() const
Allows to identify which options worked well.
std::vector< double > compound_weights
LsCounters counters
Counters for a "non-restarted" run.
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > base_solution
int64_t last_solution_rank
Used by LS to know the rank of the starting solution for this state.
std::unique_ptr< CompoundMoveBuilder > move
int64_t num_batches_before_change
When this reach zero, we restart / perturbate or trigger something.
int64_t num_restarts
Global counters, incremented across restart.
std::vector< bool > in_compound_weight_changed