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