Google OR-Tools v9.15
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 ORTOOLS_SAT_FEASIBILITY_JUMP_H_
15#define ORTOOLS_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_->HasFeasibleSolution() ==
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 // ORTOOLS_SAT_FEASIBILITY_JUMP_H_
Domain IntersectionWith(const Domain &domain) const
bool StackValuesInDomains(absl::Span< const Domain > var_domains) const
bool Backtrack(int *var, int64_t *value, double *score)
void Push(int var, int64_t prev_value, double score)
const ::operations_research::sat::CpObjectiveProto & objective() const
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)
std::function< void()> GenerateTask(int64_t) final
std::pair< int64_t, double > GetJump(int var)
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)
void CollectStatistics(const LsState &state)
SharedLsStates(absl::string_view name, const SatParameters &params, SharedStatTables *stat_tables)
SubSolver(absl::string_view name, SubsolverType type)
Definition subsolver.h:48
SubsolverType type() const
Definition subsolver.h:98
VarDomainWrapper(SharedBoundsManager *shared_bounds)
void InitializeObjective(const CpModelProto &cp_model_proto)
absl::Span< const Domain > AsSpan() const
void OnValueChange(int var, int64_t value)
absl::Span< const int > FixedVariables() const
STL namespace.
void Randomize(const SatParameters &params, ModelRandomGenerator *random)
bool operator==(const LsOptions &o) const
friend H AbslHashValue(H h, const LsOptions &o)
std::vector< double > compound_weights
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > base_solution
std::unique_ptr< CompoundMoveBuilder > move
std::vector< bool > in_compound_weight_changed