Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_lns.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_CP_MODEL_LNS_H_
15#define OR_TOOLS_SAT_CP_MODEL_LNS_H_
16
17#include <cmath>
18#include <cstddef>
19#include <cstdint>
20#include <functional>
21#include <memory>
22#include <string>
23#include <tuple>
24#include <utility>
25#include <vector>
26
27#include "absl/base/thread_annotations.h"
28#include "absl/container/flat_hash_set.h"
29#include "absl/log/check.h"
30#include "absl/random/bit_gen_ref.h"
31#include "absl/strings/string_view.h"
32#include "absl/synchronization/mutex.h"
33#include "absl/types/span.h"
34#include "google/protobuf/arena.h"
35#include "ortools/sat/cp_model.pb.h"
38#include "ortools/sat/sat_parameters.pb.h"
41#include "ortools/sat/util.h"
43#include "ortools/util/bitset.h"
44
45namespace operations_research {
46namespace sat {
47
48// Neighborhood returned by Neighborhood generators.
50 static constexpr int kDefaultArenaSizePerVariable = 128;
51
52 explicit Neighborhood(int num_variables_hint = 10)
53 : arena(std::make_unique<google::protobuf::Arena>(
54 google::protobuf::ArenaOptions(
55 {.start_block_size = static_cast<size_t>(
56 kDefaultArenaSizePerVariable * num_variables_hint)}))),
57 delta(*google::protobuf::Arena::Create<CpModelProto>(arena.get())) {}
58
59 // True if neighborhood generator was able to generate a neighborhood.
60 bool is_generated = false;
61
62 // True if an optimal solution to the neighborhood is also an optimal solution
63 // to the original model.
64 bool is_reduced = false;
65
66 // True if this neighborhood was just obtained by fixing some variables.
67 bool is_simple = false;
68
69 // Specification of the delta between the initial model and the lns fragment.
70 // The delta will contains all variables from the initial model, potentially
71 // with updated domains.
72 // It can contains new variables and new constraints, and solution hinting.
73 std::unique_ptr<google::protobuf::Arena> arena;
74 CpModelProto& delta;
75
76 // Neighborhood Id. Used to identify the neighborhood by a generator.
77 // Currently only used by WeightedRandomRelaxationNeighborhoodGenerator.
78 // TODO(user): Make sure that the id is unique for each generated
79 // neighborhood for each generator.
80 int64_t id = 0;
81
82 // Overwrites the name of the neighborhood generator in the logs.
83 std::string source_info = "";
84
85 // Statistic, only filled when is_simple is true.
88
89 // Only filled when is_simple is true. If we solve the fragment to optimality,
90 // then we can just fix the variable listed here to that optimal solution.
91 //
92 // This can happen if the neighborhood fully cover some part that are
93 // completely independent from the rest of the model. Like for instance an
94 // unused but not yet fixed variable.
95 //
96 // WARNING: all such variables should be fixed at once in a lock-like manner,
97 // because they can be multiple optimal solutions on these variables.
99};
100
101// Contains pre-computed information about a given CpModelProto that is meant
102// to be used to generate LNS neighborhood. This class can be shared between
103// more than one generator in order to reduce memory usage.
104//
105// Note that its implement the SubSolver interface to be able to Synchronize()
106// the bounds of the base problem with the external world.
108 public:
109 NeighborhoodGeneratorHelper(CpModelProto const* model_proto,
110 SatParameters const* parameters,
112 SharedBoundsManager* shared_bounds = nullptr);
113
114 // SubSolver interface.
115 bool TaskIsAvailable() override { return false; }
116 std::function<void()> GenerateTask(int64_t /*task_id*/) override {
117 return {};
118 }
119 void Synchronize() override;
120
121 int NumVariables() const { return model_proto_.variables().size(); }
122
123 // Returns the LNS fragment where the given variables are fixed to the value
124 // they take in the given solution.
125 Neighborhood FixGivenVariables(const CpSolverResponse& base_solution,
126 const Bitset64<int>& variables_to_fix) const;
127
128 // Returns the LNS fragment which will relax all inactive variables and all
129 // variables in relaxed_variables.
131 const CpSolverResponse& initial_solution,
132 absl::Span<const int> relaxed_variables) const;
133
134 // Returns a trivial model by fixing all active variables to the initial
135 // solution values.
136 Neighborhood FixAllVariables(const CpSolverResponse& initial_solution) const;
137
138 // Returns a neighborhood that correspond to the full problem.
140
141 // Returns a neighborhood that will just be skipped.
142 // It usually indicate that the generator failed to generated a neighborhood.
144
145 // Adds solution hinting to the neighborhood from the value of the initial
146 // solution.
147 void AddSolutionHinting(const CpSolverResponse& initial_solution,
148 CpModelProto* model_proto) const;
149
150 // Indicates if the variable can be frozen. It happens if the variable is non
151 // constant, and if it is a decision variable, or if
152 // focus_on_decision_variables is false.
153 bool IsActive(int var) const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_);
154
155 // Returns the list of "active" variables.
156 std::vector<int> ActiveVariables() const {
157 std::vector<int> result;
158 absl::ReaderMutexLock lock(&graph_mutex_);
159 result = active_variables_;
160 return result;
161 }
162
163 int NumActiveVariables() const {
164 absl::ReaderMutexLock lock(&graph_mutex_);
165 return active_variables_.size();
166 }
167
168 std::vector<int> ActiveObjectiveVariables() const {
169 std::vector<int> result;
170 absl::ReaderMutexLock lock(&graph_mutex_);
171 result = active_objective_variables_;
172 return result;
173 }
174
175 bool DifficultyMeansFullNeighborhood(double difficulty) const {
176 absl::ReaderMutexLock lock(&graph_mutex_);
177 const int target_size =
178 static_cast<int>(std::ceil(difficulty * active_variables_.size()));
179 return target_size == active_variables_.size();
180 }
181
182 // Returns the vector of active variables. The graph_mutex_ must be
183 // locked before calling this method.
184 const std::vector<int>& ActiveVariablesWhileHoldingLock() const
185 ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) {
186 return active_variables_;
187 }
188
189 // Returns the vector of active objective variables. The graph_mutex_ must be
190 // locked before calling this method.
192 ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) {
193 std::vector<int> result;
194 result = active_objective_variables_;
195 return result;
196 }
197
198 // Constraints <-> Variables graph.
199 // Important:
200 // - The constraint index is NOT related to the one in the cp_model.
201 // - Only non-constant var are listed in ConstraintToVar().
203 ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) {
204 return constraint_to_var_;
205 }
207 ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) {
208 return var_to_constraint_;
209 }
210
211 // Returns all the constraints indices of a given type.
212 absl::Span<const int> TypeToConstraints(
213 ConstraintProto::ConstraintCase type) const {
214 if (type >= type_to_constraints_.size()) return {};
215 return absl::MakeSpan(type_to_constraints_[type]);
216 }
217
218 // Filters a vector of intervals against the initial_solution, and returns
219 // only the active intervals.
220 std::vector<int> KeepActiveIntervals(
221 absl::Span<const int> unfiltered_intervals,
222 const CpSolverResponse& initial_solution) const;
223
224 // Returns the list of indices of active interval constraints according
225 // to the initial_solution and the parameter lns_focus_on_performed_intervals.
226 // If true, this method returns the list of performed intervals in the
227 // solution. If false, it returns all intervals of the model.
228 std::vector<int> GetActiveIntervals(
229 const CpSolverResponse& initial_solution) const;
230
231 // Returns the list of active rectangles appearing in no_overlap_2d
232 // constraints according to the initial_solution and the parameter
233 // lns_focus_on_performed_intervals. If true, this method returns the list of
234 // performed rectangles in the solution. If false, it returns all rectangles
235 // of the model.
239 // The set of no_overlap_2d constraints that both x_interval and y_interval
240 // are participating in.
241 absl::flat_hash_set<int> no_overlap_2d_constraints;
242 };
243 std::vector<ActiveRectangle> GetActiveRectangles(
244 const CpSolverResponse& initial_solution) const;
245
246 // Returns the set of unique intervals list appearing in a no_overlap,
247 // cumulative, or as a dimension of a no_overlap_2d constraint.
248 std::vector<std::vector<int>> GetUniqueIntervalSets() const;
249
250 // Returns one sub-vector per circuit or per individual vehicle circuit in a
251 // routes constraints. Each circuit is non empty, and does not contain any
252 // self-looping arcs. Path are sorted, starting from the arc with the lowest
253 // tail index, and going in sequence up to the last arc before the circuit is
254 // closed. Each entry correspond to the Boolean variable of the arc literal on
255 // the circuit.
256 std::vector<std::vector<int>> GetRoutingPathBooleanVariables(
257 const CpSolverResponse& initial_solution) const;
258
259 // Returns all precedences extracted from the scheduling constraint and the
260 // initial solution. The precedences will be sorted by the natural order
261 // the pairs of integers.
262 std::vector<std::pair<int, int>> GetSchedulingPrecedences(
263 const absl::flat_hash_set<int>& ignored_intervals,
264 const CpSolverResponse& initial_solution, absl::BitGenRef random) const;
265
266 // Returns a copy of the problem proto with updated domains.
267 CpModelProto UpdatedModelProtoCopy() const;
268
269 // The initial problem.
270 // Note that the domain of the variables are not updated here.
271 const CpModelProto& ModelProto() const { return model_proto_; }
272 const SatParameters& Parameters() const { return parameters_; }
273
275 return *shared_response_;
276 }
277
278 // TODO(user): Refactor the class to be thread-safe instead, it should be
279 // safer and more easily maintainable. Some complication with accessing the
280 // variable<->constraint graph efficiently though.
281
282 // Note: This mutex needs to be public for thread annotations.
283 mutable absl::Mutex graph_mutex_;
284
285 // TODO(user): Display LNS statistics through the StatisticsString()
286 // method.
287
288 private:
289 // Precompute stuff that will never change. During the execution, only the
290 // domain of the variable will change, so data that only depends on the
291 // constraints need to be computed just once.
292 void InitializeHelperData();
293
294 // Recompute most of the class member. This needs to be called when the
295 // domains of the variables are updated.
296 void RecomputeHelperData();
297
298 // Indicates if a variable is fixed in the model.
299 bool IsConstant(int var) const ABSL_SHARED_LOCKS_REQUIRED(domain_mutex_);
300
301 // Returns true if the domain on the objective is constraining and we might
302 // get a lower objective value at optimum without it.
303 bool ObjectiveDomainIsConstraining() const
304 ABSL_SHARED_LOCKS_REQUIRED(domain_mutex_);
305
306 // Checks if an interval is active w.r.t. the initial_solution.
307 // An interval is inactive if and only if it is either unperformed in the
308 // solution or constant in the model.
309 bool IntervalIsActive(int index,
310 const CpSolverResponse& initial_solution) const
311 ABSL_SHARED_LOCKS_REQUIRED(domain_mutex_);
312
313 const SatParameters& parameters_;
314 const CpModelProto& model_proto_;
315 int shared_bounds_id_;
316 SharedBoundsManager* shared_bounds_;
317 SharedResponseManager* shared_response_;
318
319 // Arena holding the memory of the CpModelProto* of this class. This saves the
320 // destruction cost that can take time on problem with millions of
321 // variables/constraints.
322 std::vector<char> local_arena_storage_;
323 std::unique_ptr<google::protobuf::Arena> local_arena_;
324
325 // This proto will only contain the field variables() with an updated version
326 // of the domains compared to model_proto_.variables(). We do it like this to
327 // reduce the memory footprint of the helper when the model is large.
328 //
329 // TODO(user): Use custom domain repository rather than a proto?
330 CpModelProto model_proto_with_only_variables_ ABSL_GUARDED_BY(domain_mutex_);
331
332 // Constraints by types. This never changes.
333 std::vector<std::vector<int>> type_to_constraints_;
334
335 // Whether a model_proto_ variable appear in the objective. This never
336 // changes.
337 std::vector<bool> is_in_objective_;
338
339 // A copy of CpModelProto where we did some basic presolving to remove all
340 // constraint that are always true. The Variable-Constraint graph is based on
341 // this model. Note that only the constraints field is present here.
342 CpModelProto* simplified_model_proto_ ABSL_GUARDED_BY(graph_mutex_);
343
344 // Variable-Constraint graph.
345 // We replace an interval by its variables in the scheduling constraints.
346 //
347 // TODO(user): Note that the objective is not considered here. Which is fine
348 // except if the objective domain is constraining.
349 CompactVectorVector<int, int> constraint_to_var_
350 ABSL_GUARDED_BY(graph_mutex_);
351 CompactVectorVector<int, int> var_to_constraint_
352 ABSL_GUARDED_BY(graph_mutex_);
353
354 // Connected components of the variable-constraint graph. If a variable is
355 // constant, it will not appear in any component and
356 // var_to_component_index_[var] will be -1.
357 std::vector<std::vector<int>> components_ ABSL_GUARDED_BY(graph_mutex_);
358 std::vector<int> var_to_component_index_ ABSL_GUARDED_BY(graph_mutex_);
359
360 // The set of active variables which is currently the list of non-constant
361 // variables. It is stored both as a list and as a set (using a Boolean
362 // vector).
363 std::vector<bool> active_variables_set_ ABSL_GUARDED_BY(graph_mutex_);
364 std::vector<int> active_variables_ ABSL_GUARDED_BY(graph_mutex_);
365
366 // The list of non constant variables appearing in the objective.
367 std::vector<int> active_objective_variables_ ABSL_GUARDED_BY(graph_mutex_);
368
369 std::vector<int> tmp_row_;
370
371 mutable absl::Mutex domain_mutex_;
372};
373
374// Base class for a CpModelProto neighborhood generator.
376 public:
377 NeighborhoodGenerator(absl::string_view name,
378 NeighborhoodGeneratorHelper const* helper)
379 : name_(name),
380 helper_(*helper),
382 helper->Parameters().lns_initial_deterministic_limit()),
383 difficulty_(helper->Parameters().lns_initial_difficulty()) {}
384 virtual ~NeighborhoodGenerator() = default;
385
387
388 // Adds solve data about one "solved" neighborhood.
389 struct SolveData {
390 // The status of the sub-solve.
391 CpSolverStatus status = CpSolverStatus::UNKNOWN;
392
393 // The difficulty when this neighborhood was generated.
394 double difficulty = 0.0;
395
396 // The deterministic time limit given to the solver for this neighborhood.
398
399 // The time it took to solve this neighborhood.
400 double deterministic_time = 0.0;
401
402 // Objective information. These only refer to the "internal" objective
403 // without scaling or offset so we are exact and it is always in the
404 // minimization direction.
405 // - The initial best objective is the one of the best known solution at the
406 // time the neighborhood was generated.
407 // - The base objective is the one of the base solution from which this
408 // neighborhood was generated.
409 // - The new objective is the objective of the best solution found by
410 // solving the neighborhood.
411 IntegerValue initial_best_objective = IntegerValue(0);
412 IntegerValue base_objective = IntegerValue(0);
413 IntegerValue new_objective = IntegerValue(0);
414
415 // For debugging.
416 int task_id = 0;
417
418 // This is just used to construct a deterministic order for the updates.
427 };
428
429 // Generates a "local" subproblem for the given seed.
430 //
431 // The data,difficulty will be in [0, 1] and is related to the asked
432 // neighborhood size (and thus local problem difficulty). A difficulty of 0.0
433 // means empty neighborhood and a difficulty of 1.0 means the full problem.
434 // The algorithm should try to generate a neighborhood according to this
435 // difficulty which will be dynamically adjusted depending on whether or not
436 // we can solve the subproblem in a given time limit.
437 //
438 // The given initial_solution should contain a feasible solution to the
439 // initial CpModelProto given to this class. Any solution to the returned
440 // CPModelProto should also be valid solution to the same initial model.
441 //
442 // This function should be thread-safe.
443 virtual Neighborhood Generate(const CpSolverResponse& initial_solution,
444 SolveData& data, absl::BitGenRef random) = 0;
445
446 // Returns true if the neighborhood generator can generate a neighborhood.
447 virtual bool ReadyToGenerate() const;
448
449 // Uses UCB1 algorithm to compute the score (Multi armed bandit problem).
450 // Details are at
451 // https://lilianweng.github.io/lil-log/2018/01/23/the-multi-armed-bandit-problem-and-its-solutions.html.
452 // 'total_num_calls' should be the sum of calls across all generators part of
453 // the multi armed bandit problem.
454 // If the generator is called less than 10 times then the method returns
455 // infinity as score in order to get more data about the generator
456 // performance.
457 double GetUCBScore(int64_t total_num_calls) const;
458
460 absl::MutexLock mutex_lock(&generator_mutex_);
461 solve_data_.push_back(data);
462 }
463
464 // Process all the recently added solve data and update this generator
465 // score and difficulty. This returns the sum of the deterministic time of
466 // each SolveData.
467 double Synchronize();
468
469 // Returns a short description of the generator.
470 std::string name() const { return name_; }
471
472 // Number of times this generator was called.
473 int64_t num_calls() const {
474 absl::MutexLock mutex_lock(&generator_mutex_);
475 return num_calls_;
476 }
477
478 // Number of time the neighborhood was fully solved (OPTIMAL/INFEASIBLE).
479 int64_t num_fully_solved_calls() const {
480 absl::MutexLock mutex_lock(&generator_mutex_);
481 return num_fully_solved_calls_;
482 }
483
484 // Out of num_calls(), how many improved the given solution.
485 int64_t num_improving_calls() const {
486 absl::MutexLock mutex_lock(&generator_mutex_);
487 return num_improving_calls_;
488 }
489
490 // Returns the number of the last calls to this generator that didn't improve
491 // the best solution. Note that this count improvement to the best known
492 // solution not the base one used to generate one neighborhood.
494 absl::MutexLock mutex_lock(&generator_mutex_);
495 return num_consecutive_non_improving_calls_;
496 }
497
498 // The current difficulty of this generator
499 double difficulty() const {
500 absl::MutexLock mutex_lock(&generator_mutex_);
501 return difficulty_.value();
502 }
503
504 // The current time limit that the sub-solve should use on this generator.
505 double deterministic_limit() const {
506 absl::MutexLock mutex_lock(&generator_mutex_);
508 }
509
510 protected:
511 const std::string name_;
513 mutable absl::Mutex generator_mutex_;
515
516 private:
517 std::vector<SolveData> solve_data_;
518
519 // Current parameters to be used when generating/solving a neighborhood with
520 // this generator. Only updated on Synchronize().
521 AdaptiveParameterValue difficulty_;
522
523 // Current statistics of the last solved neighborhood.
524 // Only updated on Synchronize().
525 int64_t num_calls_ = 0;
526 int64_t num_fully_solved_calls_ = 0;
527 int64_t num_improving_calls_ = 0;
528 int64_t num_consecutive_non_improving_calls_ = 0;
529 int64_t next_time_limit_bump_ = 50;
530 double current_average_ = 0.0;
531};
532
533// Pick a random subset of variables.
534//
535// TODO(user): In the presence of connected components, this should just work
536// on one of them.
538 public:
540 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
541 : NeighborhoodGenerator(name, helper) {}
542 Neighborhood Generate(const CpSolverResponse& initial_solution,
543 SolveData& data, absl::BitGenRef random) final;
544};
545
546// Pick a random subset of constraints and relax all the variables of these
547// constraints. Note that to satisfy the difficulty, we might not relax all the
548// variable of the "last" constraint.
549//
550// TODO(user): In the presence of connected components, this should just work
551// on one of them.
553 public:
555 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
556 : NeighborhoodGenerator(name, helper) {}
557 Neighborhood Generate(const CpSolverResponse& initial_solution,
558 SolveData& data, absl::BitGenRef random) final;
559};
560
561// Pick a random subset of variables that are constructed by a BFS in the
562// variable <-> constraint graph. That is, pick a random variable, then all the
563// variable connected by some constraint to the first one, and so on. The
564// variable of the last "level" are selected randomly.
565//
566// Note that in the presence of connected component, this works correctly
567// already.
569 public:
571 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
572 : NeighborhoodGenerator(name, helper) {}
573 Neighborhood Generate(const CpSolverResponse& initial_solution,
574 SolveData& data, absl::BitGenRef random) final;
575};
576
577// This randomly extend a working set of variable by one variable directly
578// connected to that set.
580 public:
582 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
583 : NeighborhoodGenerator(name, helper) {}
584 Neighborhood Generate(const CpSolverResponse& initial_solution,
585 SolveData& data, absl::BitGenRef random) final;
586};
587
588// Pick a random subset of constraint and relax all of their variables. We are a
589// bit smarter than this because after the first constraint is selected, we only
590// select constraints that share at least one variable with the already selected
591// constraints. The variable from the "last" constraint are selected randomly.
593 public:
595 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
596 : NeighborhoodGenerator(name, helper) {}
597 Neighborhood Generate(const CpSolverResponse& initial_solution,
598 SolveData& data, absl::BitGenRef random) final;
599};
600
601// The idea here is to try to generate a random neighborhood incrementally in
602// such a way that we have at various point a "minimum connection" in term of
603// constraints or variable to the outside world.
604//
605// This is inspired by what would be a good neighborhood if one where to use
606// a tree decomposition of the constraint-variable graph with small treewidth.
607//
608// TODO(user): Doing the full heuristic treewidth decomposition is probably
609// better because when we grow the current neighborhood, just using local
610// connection to the current candidate is probably not enough to orient the
611// search towards a good final neighborhood.
613 public:
615 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
616 : NeighborhoodGenerator(name, helper) {}
617 Neighborhood Generate(const CpSolverResponse& initial_solution,
618 SolveData& data, absl::BitGenRef random) final;
619};
620
621// Solves a local branching LP and greedily picks a set of variables with the
622// largest differences between the initial and local branching LP solutions,
623// breaking ties uniformly at random.
624//
625// This is based on Huang et al., "Local Branching Relaxation Heuristics for
626// Integer Linear Programs", 2023.
628 : public NeighborhoodGenerator {
629 public:
631 NeighborhoodGeneratorHelper const* helper, absl::string_view name,
632 ModelSharedTimeLimit* const global_time_limit, SharedClasses* shared)
633 : NeighborhoodGenerator(name, helper),
634 global_time_limit_(global_time_limit),
635 shared_(shared) {
636 // Given that we spend time generating a good neighborhood it sounds
637 // reasonable to spend a bit more time solving it too.
639 }
640
641 Neighborhood Generate(const CpSolverResponse& initial_solution,
642 SolveData& data, absl::BitGenRef random) final;
643
644 private:
645 ModelSharedTimeLimit* const global_time_limit_;
646 SharedClasses* const shared_;
647};
648
649// Helper method for the scheduling neighborhood generators. Returns a
650// neighborhood defined from the given set of intervals to relax. For each
651// scheduling constraint, it adds strict relation order between the non-relaxed
652// intervals.
654 absl::Span<const int> intervals_to_relax,
655 absl::Span<const int> variables_to_fix,
656 const CpSolverResponse& initial_solution, absl::BitGenRef random,
657 const NeighborhoodGeneratorHelper& helper);
658
659// Helper method for the scheduling neighborhood generators. Returns a
660// full neighborhood enriched with the set or precedences passed to the generate
661// method.
663 absl::Span<const std::pair<int, int>> precedences,
664 const CpSolverResponse& initial_solution,
665 const NeighborhoodGeneratorHelper& helper);
666
667// Only make sense for scheduling problem. This select a random set of interval
668// of the problem according to the difficulty. Then, for each scheduling
669// constraints, it adds strict relation order between the non-relaxed intervals.
671 : public NeighborhoodGenerator {
672 public:
674 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
675 : NeighborhoodGenerator(name, helper) {}
676
677 Neighborhood Generate(const CpSolverResponse& initial_solution,
678 SolveData& data, absl::BitGenRef random) final;
679};
680
681// Only make sense for scheduling problem. This select a random set of
682// precedences between intervals of the problem according to the difficulty.
683// These precedences are extracted from the scheduling constraints and their
684// configuration in the current solution. Then it adds the kept precedences to
685// the model.
687 : public NeighborhoodGenerator {
688 public:
690 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
691 : NeighborhoodGenerator(name, helper) {}
692
693 Neighborhood Generate(const CpSolverResponse& initial_solution,
694 SolveData& data, absl::BitGenRef random) final;
695};
696
697// Similar to SchedulingNeighborhoodGenerator except the set of intervals that
698// are relaxed are from a specific random time interval.
700 public:
702 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
703 : NeighborhoodGenerator(name, helper) {}
704
705 Neighborhood Generate(const CpSolverResponse& initial_solution,
706 SolveData& data, absl::BitGenRef random) final;
707};
708
709// Similar to SchedulingTimeWindowNeighborhoodGenerator except that it relaxes
710// one independent time window per resource (1 for each dimension in the
711// no_overlap_2d case).
713 : public NeighborhoodGenerator {
714 public:
716 NeighborhoodGeneratorHelper const* helper,
717 const std::vector<std::vector<int>>& intervals_in_constraints,
718 absl::string_view name)
719 : NeighborhoodGenerator(name, helper),
720 intervals_in_constraints_(intervals_in_constraints) {}
721
722 Neighborhood Generate(const CpSolverResponse& initial_solution,
723 SolveData& data, absl::BitGenRef random) final;
724
725 private:
726 const std::vector<std::vector<int>> intervals_in_constraints_;
727};
728
729// Only make sense for problems with no_overlap_2d constraints. This select a
730// random set of rectangles (i.e. a pair of intervals) of the problem according
731// to the difficulty. Then fix all variables in the selected intervals.
733 : public NeighborhoodGenerator {
734 public:
736 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
737 : NeighborhoodGenerator(name, helper) {}
738
739 Neighborhood Generate(const CpSolverResponse& initial_solution,
740 SolveData& data, absl::BitGenRef random) final;
741};
742
743// Only make sense for problems with no_overlap_2d constraints. This selects one
744// random rectangles and relax the closest rectangles to it.
746 : public NeighborhoodGenerator {
747 public:
749 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
750 : NeighborhoodGenerator(name, helper) {}
751
752 Neighborhood Generate(const CpSolverResponse& initial_solution,
753 SolveData& data, absl::BitGenRef random) final;
754};
755
756// Only make sense for problems with no_overlap_2d constraints. This selects two
757// random rectangles and relax them alongside the closest rectangles to each one
758// of them. The idea is that this will find a better solution when there is a
759// cost function that would be improved by swapping the two rectangles.
761 : public NeighborhoodGenerator {
762 public:
764 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
765 : NeighborhoodGenerator(name, helper) {}
766
767 Neighborhood Generate(const CpSolverResponse& initial_solution,
768 SolveData& data, absl::BitGenRef random) final;
769};
770
771// Only make sense for problems with no_overlap_2d constraints. This select a
772// random set of rectangles (i.e. a pair of intervals) of the problem according
773// to the difficulty. Then add all implied precedences from the current
774// positions of the rectangles in this selected subset.
776 : public NeighborhoodGenerator {
777 public:
779 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
780 : NeighborhoodGenerator(name, helper) {}
781
782 Neighborhood Generate(const CpSolverResponse& initial_solution,
783 SolveData& data, absl::BitGenRef random) final;
784};
785
786// Only make sense for problems with no_overlap_2d constraints. This select a
787// slice on one dimension, and fix the variables of all rectangles not strictly
788// included in this slice.
790 public:
792 NeighborhoodGeneratorHelper const* helper, absl::string_view name)
793 : NeighborhoodGenerator(name, helper) {}
794
795 Neighborhood Generate(const CpSolverResponse& initial_solution,
796 SolveData& data, absl::BitGenRef random) final;
797};
798
799// This routing based LNS generator will relax random arcs in all the paths of
800// the circuit or routes constraints.
802 public:
806
807 Neighborhood Generate(const CpSolverResponse& initial_solution,
808 SolveData& data, absl::BitGenRef random) final;
809};
810
811// This routing based LNS generator will relax small sequences of arcs randomly
812// chosen in all the paths of the circuit or routes constraints.
814 public:
816 absl::string_view name)
817 : NeighborhoodGenerator(name, helper) {}
818
819 Neighborhood Generate(const CpSolverResponse& initial_solution,
820 SolveData& data, absl::BitGenRef random) final;
821};
822
823// This routing based LNS generator aims at relaxing one full path, and make
824// some room on the other paths to absorb the nodes of the relaxed path.
825//
826// In order to do so, it will relax the first and the last arc of each path in
827// the circuit or routes constraints. Then it will relax all arc literals in one
828// random path. Then it will relax random arcs in the remaining paths until it
829// reaches the given difficulty.
831 public:
835
836 Neighborhood Generate(const CpSolverResponse& initial_solution,
837 SolveData& data, absl::BitGenRef random) final;
838};
839
840// Generates a neighborhood by fixing the variables to solutions reported in
841// various repositories. This is inspired from RINS published in "Exploring
842// relaxation induced neighborhoods to improve MIP solutions" 2004 by E. Danna
843// et.
844//
845// If incomplete_solutions is provided, this generates a neighborhood by fixing
846// the variable values to a solution in the SharedIncompleteSolutionManager and
847// ignores the other repositories.
848//
849// Otherwise, if response_manager is not provided, this generates a neighborhood
850// using only the linear/general relaxation values. The domain of the variables
851// are reduced to the integer values around their lp solution/relaxation
852// solution values. This was published in "RENS – The Relaxation Enforced
853// Neighborhood" 2009 by Timo Berthold.
855 public:
857 NeighborhoodGeneratorHelper const* helper,
858 const SharedResponseManager* response_manager,
859 const SharedLPSolutionRepository* lp_solutions,
860 SharedIncompleteSolutionManager* incomplete_solutions,
861 absl::string_view name)
862 : NeighborhoodGenerator(name, helper),
863 response_manager_(response_manager),
864 lp_solutions_(lp_solutions),
865 incomplete_solutions_(incomplete_solutions) {
866 CHECK(lp_solutions_ != nullptr);
867 CHECK(incomplete_solutions != nullptr);
868 }
869
870 // Both initial solution and difficulty values are ignored.
871 Neighborhood Generate(const CpSolverResponse& initial_solution,
872 SolveData& data, absl::BitGenRef random) final;
873
874 // Returns true if the required solutions are available.
875 bool ReadyToGenerate() const override;
876
877 private:
878 const SharedResponseManager* response_manager_;
879 const SharedLPSolutionRepository* lp_solutions_;
880 SharedIncompleteSolutionManager* incomplete_solutions_;
881};
882
883} // namespace sat
884} // namespace operations_research
885
886#endif // OR_TOOLS_SAT_CP_MODEL_LNS_H_
ArcGraphNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
ConstraintGraphNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
DecompositionGraphNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
LocalBranchingLpBasedNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name, ModelSharedTimeLimit *const global_time_limit, SharedClasses *shared)
The model "singleton" shared time limit.
Definition util.h:345
Neighborhood FullNeighborhood() const
Returns a neighborhood that correspond to the full problem.
bool IsActive(int var) const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
CpModelProto UpdatedModelProtoCopy() const
Returns a copy of the problem proto with updated domains.
std::vector< std::vector< int > > GetRoutingPathBooleanVariables(const CpSolverResponse &initial_solution) const
Neighborhood FixAllVariables(const CpSolverResponse &initial_solution) const
Neighborhood FixGivenVariables(const CpSolverResponse &base_solution, const Bitset64< int > &variables_to_fix) const
const std::vector< int > & ActiveVariablesWhileHoldingLock() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
const CompactVectorVector< int, int > & VarToConstraint() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
const SharedResponseManager & shared_response() const
Neighborhood RelaxGivenVariables(const CpSolverResponse &initial_solution, absl::Span< const int > relaxed_variables) const
NeighborhoodGeneratorHelper(CpModelProto const *model_proto, SatParameters const *parameters, SharedResponseManager *shared_response, SharedBoundsManager *shared_bounds=nullptr)
std::vector< int > ActiveVariables() const
Returns the list of "active" variables.
std::vector< std::vector< int > > GetUniqueIntervalSets() const
const CompactVectorVector< int, int > & ConstraintToVar() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
std::vector< ActiveRectangle > GetActiveRectangles(const CpSolverResponse &initial_solution) const
bool DifficultyMeansFullNeighborhood(double difficulty) const
void AddSolutionHinting(const CpSolverResponse &initial_solution, CpModelProto *model_proto) const
std::vector< int > ActiveObjectiveVariablesWhileHoldingLock() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
std::function< void()> GenerateTask(int64_t) override
std::vector< int > GetActiveIntervals(const CpSolverResponse &initial_solution) const
std::vector< std::pair< int, int > > GetSchedulingPrecedences(const absl::flat_hash_set< int > &ignored_intervals, const CpSolverResponse &initial_solution, absl::BitGenRef random) const
absl::Span< const int > TypeToConstraints(ConstraintProto::ConstraintCase type) const
Returns all the constraints indices of a given type.
bool TaskIsAvailable() override
SubSolver interface.
std::vector< int > KeepActiveIntervals(absl::Span< const int > unfiltered_intervals, const CpSolverResponse &initial_solution) const
NeighborhoodGeneratorHelper::ActiveRectangle ActiveRectangle
virtual bool ReadyToGenerate() const
Returns true if the neighborhood generator can generate a neighborhood.
double deterministic_limit() const
The current time limit that the sub-solve should use on this generator.
int64_t num_improving_calls() const
Out of num_calls(), how many improved the given solution.
std::string name() const
Returns a short description of the generator.
virtual Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random)=0
double difficulty() const
The current difficulty of this generator.
double GetUCBScore(int64_t total_num_calls) const
int64_t num_calls() const
Number of times this generator was called.
const NeighborhoodGeneratorHelper & helper_
int64_t num_fully_solved_calls() const
Number of time the neighborhood was fully solved (OPTIMAL/INFEASIBLE).
NeighborhoodGenerator(absl::string_view name, NeighborhoodGeneratorHelper const *helper)
RandomIntervalSchedulingNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RandomPrecedenceSchedulingNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RandomPrecedencesPackingNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RandomRectanglesPackingNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RectanglesPackingRelaxOneNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RectanglesPackingRelaxTwoNeighborhoodsGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RelaxRandomConstraintsGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RelaxRandomVariablesGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RelaxationInducedNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, const SharedResponseManager *response_manager, const SharedLPSolutionRepository *lp_solutions, SharedIncompleteSolutionManager *incomplete_solutions, absl::string_view name)
RoutingFullPathNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RoutingPathNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
RoutingRandomNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
SchedulingResourceWindowsNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, const std::vector< std::vector< int > > &intervals_in_constraints, absl::string_view name)
SchedulingTimeWindowNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
SlicePackingNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
SubSolver(absl::string_view name, SubsolverType type)
Definition subsolver.h:48
SubsolverType type() const
Returns the type of the subsolver.
Definition subsolver.h:98
VariableGraphNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, absl::string_view name)
Neighborhood GenerateSchedulingNeighborhoodFromIntervalPrecedences(const absl::Span< const std::pair< int, int > > precedences, const CpSolverResponse &initial_solution, const NeighborhoodGeneratorHelper &helper)
Neighborhood GenerateSchedulingNeighborhoodFromRelaxedIntervals(absl::Span< const int > intervals_to_relax, absl::Span< const int > variables_to_fix, const CpSolverResponse &initial_solution, absl::BitGenRef random, const NeighborhoodGeneratorHelper &helper)
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
Adds solve data about one "solved" neighborhood.
double deterministic_time
The time it took to solve this neighborhood.
double difficulty
The difficulty when this neighborhood was generated.
double deterministic_limit
The deterministic time limit given to the solver for this neighborhood.
CpSolverStatus status
The status of the sub-solve.
bool operator<(const SolveData &o) const
This is just used to construct a deterministic order for the updates.
Neighborhood returned by Neighborhood generators.
std::string source_info
Overwrites the name of the neighborhood generator in the logs.
bool is_simple
True if this neighborhood was just obtained by fixing some variables.
int num_relaxed_variables
Statistic, only filled when is_simple is true.
Neighborhood(int num_variables_hint=10)
std::unique_ptr< google::protobuf::Arena > arena
bool is_generated
True if neighborhood generator was able to generate a neighborhood.
std::vector< int > variables_that_can_be_fixed_to_local_optimum
static constexpr int kDefaultArenaSizePerVariable