1 // Copyright 2010-2021 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 <cstdint> 18 #include <vector> 19 20 #include "absl/container/flat_hash_map.h" 21 #include "absl/random/bit_gen_ref.h" 22 #include "absl/synchronization/mutex.h" 23 #include "absl/types/span.h" 24 #include "ortools/base/integral_types.h" 25 #include "ortools/sat/cp_model.pb.h" 26 #include "ortools/sat/cp_model_presolve.h" 27 #include "ortools/sat/model.h" 28 #include "ortools/sat/subsolver.h" 29 #include "ortools/sat/synchronization.h" 30 #include "ortools/util/adaptative_parameter_value.h" 31 #include "ortools/util/logging.h" 32 33 namespace operations_research { 34 namespace sat { 35 36 // Neighborhood returned by Neighborhood generators. 37 struct Neighborhood { 38 // True if neighborhood generator was able to generate a neighborhood. 39 bool is_generated = false; 40 41 // True if an optimal solution to the neighborhood is also an optimal solution 42 // to the original model. 43 bool is_reduced = false; 44 45 // True if this neighborhood was just obtained by fixing some variables. 46 bool is_simple = false; 47 48 // Specification of the delta between the initial model and the lns fragment. 49 // The delta will contains all variables from the initial model, potentially 50 // with updated domains. 51 // It can contains new variables and new constraints, and solution hinting. 52 CpModelProto delta; 53 std::vector<int> constraints_to_ignore; 54 55 // Neighborhood Id. Used to identify the neighborhood by a generator. 56 // Currently only used by WeightedRandomRelaxationNeighborhoodGenerator. 57 // TODO(user): Make sure that the id is unique for each generated 58 // neighborhood for each generator. 59 int64_t id = 0; 60 61 // Used for identifying the source of the neighborhood if it is generated 62 // using solution repositories. 63 std::string source_info = ""; 64 65 // Statistic, only filled when is_simple is true. 66 int num_relaxed_variables = 0; 67 int num_relaxed_variables_in_objective = 0; 68 69 // Only filled when is_simple is true. If we solve the fragment to optimality, 70 // then we can just fix the variable listed here to that optimal solution. 71 // 72 // This can happen if the neighborhood fully cover some part that are 73 // completely independent from the rest of the model. Like for instance an 74 // unused but not yet fixed variable. 75 // 76 // WARNING: all such variables should be fixed at once in a lock-like manner, 77 // because they can be multiple optimal solutions on these variables. 78 std::vector<int> variables_that_can_be_fixed_to_local_optimum; 79 }; 80 81 // Contains pre-computed information about a given CpModelProto that is meant 82 // to be used to generate LNS neighborhood. This class can be shared between 83 // more than one generator in order to reduce memory usage. 84 // 85 // Note that its implement the SubSolver interface to be able to Synchronize() 86 // the bounds of the base problem with the external world. 87 class NeighborhoodGeneratorHelper : public SubSolver { 88 public: 89 NeighborhoodGeneratorHelper(CpModelProto const* model_proto, 90 SatParameters const* parameters, 91 SharedResponseManager* shared_response, 92 SharedTimeLimit* shared_time_limit = nullptr, 93 SharedBoundsManager* shared_bounds = nullptr); 94 95 // SubSolver interface. TaskIsAvailable()96 bool TaskIsAvailable() override { return false; } GenerateTask(int64_t task_id)97 std::function<void()> GenerateTask(int64_t task_id) override { return {}; } 98 void Synchronize() override; 99 100 // Returns the LNS fragment where the given variables are fixed to the value 101 // they take in the given solution. 102 Neighborhood FixGivenVariables( 103 const CpSolverResponse& base_solution, 104 const absl::flat_hash_set<int>& variables_to_fix) const; 105 106 // Returns the neighborhood where the given constraints are removed. 107 Neighborhood RemoveMarkedConstraints( 108 const std::vector<int>& constraints_to_remove) const; 109 110 // Returns the LNS fragment which will relax all inactive variables and all 111 // variables in relaxed_variables. 112 Neighborhood RelaxGivenVariables( 113 const CpSolverResponse& initial_solution, 114 const std::vector<int>& relaxed_variables) const; 115 116 // Returns a trivial model by fixing all active variables to the initial 117 // solution values. 118 Neighborhood FixAllVariables(const CpSolverResponse& initial_solution) const; 119 120 // Returns a neighborhood that correspond to the full problem. 121 Neighborhood FullNeighborhood() const; 122 123 // Returns a neighborhood that will just be skipped. 124 // It usually indicate that the generator failed to generated a neighborhood. 125 Neighborhood NoNeighborhood() const; 126 127 // Adds solution hinting to the neighborhood from the value of the initial 128 // solution. 129 void AddSolutionHinting(const CpSolverResponse& initial_solution, 130 CpModelProto* model_proto) const; 131 132 // Indicates if the variable can be frozen. It happens if the variable is non 133 // constant, and if it is a decision variable, or if 134 // focus_on_decision_variables is false. 135 bool IsActive(int var) const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_); 136 137 // Returns the list of "active" variables. ActiveVariables()138 std::vector<int> ActiveVariables() const { 139 std::vector<int> result; 140 absl::ReaderMutexLock lock(&graph_mutex_); 141 result = active_variables_; 142 return result; 143 } 144 NumActiveVariables()145 int NumActiveVariables() const { 146 absl::ReaderMutexLock lock(&graph_mutex_); 147 return active_variables_.size(); 148 } 149 DifficultyMeansFullNeighborhood(double difficulty)150 bool DifficultyMeansFullNeighborhood(double difficulty) const { 151 absl::ReaderMutexLock lock(&graph_mutex_); 152 const int target_size = std::ceil(difficulty * active_variables_.size()); 153 return target_size == active_variables_.size(); 154 } 155 156 // Returns the vector of active variables. The graph_mutex_ must be 157 // locked before calling this method. ActiveVariablesWhileHoldingLock()158 const std::vector<int>& ActiveVariablesWhileHoldingLock() const 159 ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) { 160 return active_variables_; 161 } 162 163 // Constraints <-> Variables graph. 164 // Note that only non-constant variable are listed here. ConstraintToVar()165 const std::vector<std::vector<int>>& ConstraintToVar() const 166 ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) { 167 return constraint_to_var_; 168 } VarToConstraint()169 const std::vector<std::vector<int>>& VarToConstraint() const 170 ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) { 171 return var_to_constraint_; 172 } 173 174 // Returns all the constraints indices of a given type. TypeToConstraints(ConstraintProto::ConstraintCase type)175 const absl::Span<const int> TypeToConstraints( 176 ConstraintProto::ConstraintCase type) const { 177 if (type >= type_to_constraints_.size()) return {}; 178 return absl::MakeSpan(type_to_constraints_[type]); 179 } 180 181 // Returns the list of indices of active interval constraints according 182 // to the initial_solution and the parameter lns_focus_on_performed_intervals. 183 // If true, this method returns the list of performed intervals in the 184 // solution. If false, it returns all intervals of the model. 185 std::vector<int> GetActiveIntervals( 186 const CpSolverResponse& initial_solution) const; 187 188 // Returns one sub-vector per circuit or per single vehicle ciruit in a routes 189 // constraints. Each circuit is non empty, and does not contain any 190 // self-looping arcs. Path are sorted, starting from the arc with the lowest 191 // tail index, and going in sequence up to the last arc before the circuit is 192 // closed. Each entry correspond to the arc literal on the circuit. 193 std::vector<std::vector<int>> GetRoutingPaths( 194 const CpSolverResponse& initial_solution) const; 195 196 // The initial problem. 197 // Note that the domain of the variables are not updated here. ModelProto()198 const CpModelProto& ModelProto() const { return model_proto_; } Parameters()199 const SatParameters& Parameters() const { return parameters_; } 200 shared_response()201 const SharedResponseManager& shared_response() const { 202 return *shared_response_; 203 } 204 205 // TODO(user): Refactor the class to be thread-safe instead, it should be 206 // safer and more easily maintenable. Some complication with accessing the 207 // variable<->constraint graph efficiently though. 208 209 // Note: This mutex needs to be public for thread annotations. 210 mutable absl::Mutex graph_mutex_; 211 212 // TODO(user): Display LNS statistics through the StatisticsString() 213 // method. 214 215 private: 216 // Precompute stuff that will never change. During the execution, only the 217 // domain of the variable will change, so data that only depends on the 218 // constraints need to be computed just once. 219 void InitializeHelperData(); 220 221 // Recompute most of the class member. This needs to be called when the 222 // domains of the variables are updated. 223 void RecomputeHelperData(); 224 225 // Indicates if a variable is fixed in the model. 226 bool IsConstant(int var) const ABSL_SHARED_LOCKS_REQUIRED(domain_mutex_); 227 228 // Returns true if the domain on the objective is constraining and we might 229 // get a lower objective value at optimum without it. 230 bool ObjectiveDomainIsConstraining() const 231 ABSL_SHARED_LOCKS_REQUIRED(domain_mutex_); 232 233 const SatParameters& parameters_; 234 const CpModelProto& model_proto_; 235 int shared_bounds_id_; 236 SharedTimeLimit* shared_time_limit_; 237 SharedBoundsManager* shared_bounds_; 238 SharedResponseManager* shared_response_; 239 SharedRelaxationSolutionRepository* shared_relaxation_solutions_; 240 241 // This proto will only contain the field variables() with an updated version 242 // of the domains compared to model_proto_.variables(). We do it like this to 243 // reduce the memory footprint of the helper when the model is large. 244 // 245 // TODO(user): Use custom domain repository rather than a proto? 246 CpModelProto model_proto_with_only_variables_ ABSL_GUARDED_BY(domain_mutex_); 247 248 // Constraints by types. This never changes. 249 std::vector<std::vector<int>> type_to_constraints_; 250 251 // Whether a model_proto_ variable appear in the objective. This never 252 // changes. 253 std::vector<bool> is_in_objective_; 254 255 // A copy of CpModelProto where we did some basic presolving to remove all 256 // constraint that are always true. The Variable-Constraint graph is based on 257 // this model. Note that only the constraints field is present here. 258 CpModelProto simplied_model_proto_ ABSL_GUARDED_BY(graph_mutex_); 259 260 // Variable-Constraint graph. 261 // We replace an interval by its variables in the scheduling constraints. 262 // 263 // TODO(user): Note that the objective is not considered here. Which is fine 264 // except if the objective domain is constraining. 265 std::vector<std::vector<int>> constraint_to_var_ 266 ABSL_GUARDED_BY(graph_mutex_); 267 std::vector<std::vector<int>> var_to_constraint_ 268 ABSL_GUARDED_BY(graph_mutex_); 269 270 // Connected components of the variable-constraint graph. If a variable is 271 // constant, it will not appear in any component and 272 // var_to_component_index_[var] will be -1. 273 std::vector<std::vector<int>> components_ ABSL_GUARDED_BY(graph_mutex_); 274 std::vector<int> var_to_component_index_ ABSL_GUARDED_BY(graph_mutex_); 275 276 // The set of active variables which is currently the list of non-constant 277 // variables. It is stored both as a list and as a set (using a Boolean 278 // vector). 279 std::vector<bool> active_variables_set_ ABSL_GUARDED_BY(graph_mutex_); 280 std::vector<int> active_variables_ ABSL_GUARDED_BY(graph_mutex_); 281 282 mutable absl::Mutex domain_mutex_; 283 }; 284 285 // Base class for a CpModelProto neighborhood generator. 286 class NeighborhoodGenerator { 287 public: NeighborhoodGenerator(const std::string & name,NeighborhoodGeneratorHelper const * helper)288 NeighborhoodGenerator(const std::string& name, 289 NeighborhoodGeneratorHelper const* helper) 290 : name_(name), helper_(*helper), difficulty_(0.5) {} ~NeighborhoodGenerator()291 virtual ~NeighborhoodGenerator() {} 292 293 // Generates a "local" subproblem for the given seed. 294 // 295 // The difficulty will be in [0, 1] and is related to the asked neighborhood 296 // size (and thus local problem difficulty). A difficulty of 0.0 means empty 297 // neighborhood and a difficulty of 1.0 means the full problem. The algorithm 298 // should try to generate a neighborhood according to this difficulty which 299 // will be dynamically adjusted depending on whether or not we can solve the 300 // subproblem in a given time limit. 301 // 302 // The given initial_solution should contain a feasible solution to the 303 // initial CpModelProto given to this class. Any solution to the returned 304 // CPModelProto should also be valid solution to the same initial model. 305 // 306 // This function should be thread-safe. 307 virtual Neighborhood Generate(const CpSolverResponse& initial_solution, 308 double difficulty, absl::BitGenRef random) = 0; 309 310 // Returns true if the neighborhood generator can generate a neighborhood. 311 virtual bool ReadyToGenerate() const; 312 313 // Returns true if the neighborhood generator generates relaxation of the 314 // given problem. IsRelaxationGenerator()315 virtual bool IsRelaxationGenerator() const { return false; } 316 317 // Uses UCB1 algorithm to compute the score (Multi armed bandit problem). 318 // Details are at 319 // https://lilianweng.github.io/lil-log/2018/01/23/the-multi-armed-bandit-problem-and-its-solutions.html. 320 // 'total_num_calls' should be the sum of calls across all generators part of 321 // the multi armed bandit problem. 322 // If the generator is called less than 10 times then the method returns 323 // infinity as score in order to get more data about the generator 324 // performance. 325 double GetUCBScore(int64_t total_num_calls) const; 326 327 // Adds solve data about one "solved" neighborhood. 328 struct SolveData { 329 // Neighborhood Id. Used to identify the neighborhood by a generator. 330 // Currently only used by WeightedRandomRelaxationNeighborhoodGenerator. 331 int64_t neighborhood_id = 0; 332 333 // The status of the sub-solve. 334 CpSolverStatus status = CpSolverStatus::UNKNOWN; 335 336 // The difficulty when this neighborhood was generated. 337 double difficulty = 0.0; 338 339 // The determinitic time limit given to the solver for this neighborhood. 340 double deterministic_limit = 0.0; 341 342 // The time it took to solve this neighborhood. 343 double deterministic_time = 0.0; 344 345 // Objective information. These only refer to the "internal" objective 346 // without scaling or offset so we are exact and it is always in the 347 // minimization direction. 348 // - The initial best objective is the one of the best known solution at the 349 // time the neighborhood was generated. 350 // - The base objective is the one of the base solution from which this 351 // neighborhood was generated. 352 // - The new objective is the objective of the best solution found by 353 // solving the neighborhood. 354 IntegerValue initial_best_objective = IntegerValue(0); 355 IntegerValue base_objective = IntegerValue(0); 356 IntegerValue new_objective = IntegerValue(0); 357 358 // Bounds data is only used by relaxation neighborhoods. 359 IntegerValue initial_best_objective_bound = IntegerValue(0); 360 IntegerValue new_objective_bound = IntegerValue(0); 361 362 // This is just used to construct a deterministic order for the updates. 363 bool operator<(const SolveData& o) const { 364 return std::tie(status, difficulty, deterministic_limit, 365 deterministic_time, initial_best_objective, 366 base_objective, new_objective, 367 initial_best_objective_bound, new_objective_bound, 368 neighborhood_id) < 369 std::tie(o.status, o.difficulty, o.deterministic_limit, 370 o.deterministic_time, o.initial_best_objective, 371 o.base_objective, o.new_objective, 372 o.initial_best_objective_bound, o.new_objective_bound, 373 o.neighborhood_id); 374 } 375 }; AddSolveData(SolveData data)376 void AddSolveData(SolveData data) { 377 absl::MutexLock mutex_lock(&generator_mutex_); 378 solve_data_.push_back(data); 379 } 380 381 // Process all the recently added solve data and update this generator 382 // score and difficulty. 383 void Synchronize(); 384 385 // Returns a short description of the generator. name()386 std::string name() const { return name_; } 387 388 // Number of times this generator was called. num_calls()389 int64_t num_calls() const { 390 absl::MutexLock mutex_lock(&generator_mutex_); 391 return num_calls_; 392 } 393 394 // Number of time the neighborhood was fully solved (OPTIMAL/INFEASIBLE). num_fully_solved_calls()395 int64_t num_fully_solved_calls() const { 396 absl::MutexLock mutex_lock(&generator_mutex_); 397 return num_fully_solved_calls_; 398 } 399 400 // The current difficulty of this generator difficulty()401 double difficulty() const { 402 absl::MutexLock mutex_lock(&generator_mutex_); 403 return difficulty_.value(); 404 } 405 406 // The current time limit that the sub-solve should use on this generator. deterministic_limit()407 double deterministic_limit() const { 408 absl::MutexLock mutex_lock(&generator_mutex_); 409 return deterministic_limit_; 410 } 411 412 // The sum of the deterministic time spent in this generator. deterministic_time()413 double deterministic_time() const { 414 absl::MutexLock mutex_lock(&generator_mutex_); 415 return deterministic_time_; 416 } 417 418 protected: 419 // Triggered with each call to Synchronize() for each recently added 420 // SolveData. This is meant to be used for processing feedbacks by specific 421 // neighborhood generators to adjust the neighborhood generation process. AdditionalProcessingOnSynchronize(const SolveData & solve_data)422 virtual void AdditionalProcessingOnSynchronize(const SolveData& solve_data) {} 423 424 const std::string name_; 425 const NeighborhoodGeneratorHelper& helper_; 426 mutable absl::Mutex generator_mutex_; 427 428 private: 429 std::vector<SolveData> solve_data_; 430 431 // Current parameters to be used when generating/solving a neighborhood with 432 // this generator. Only updated on Synchronize(). 433 AdaptiveParameterValue difficulty_; 434 double deterministic_limit_ = 0.1; 435 436 // Current statistics of the last solved neighborhood. 437 // Only updated on Synchronize(). 438 int64_t num_calls_ = 0; 439 int64_t num_fully_solved_calls_ = 0; 440 int64_t num_consecutive_non_improving_calls_ = 0; 441 double deterministic_time_ = 0.0; 442 double current_average_ = 0.0; 443 }; 444 445 // Pick a random subset of variables. 446 // 447 // TODO(user): In the presence of connected components, this should just work 448 // on one of them. 449 class RelaxRandomVariablesGenerator : public NeighborhoodGenerator { 450 public: RelaxRandomVariablesGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)451 explicit RelaxRandomVariablesGenerator( 452 NeighborhoodGeneratorHelper const* helper, const std::string& name) 453 : NeighborhoodGenerator(name, helper) {} 454 Neighborhood Generate(const CpSolverResponse& initial_solution, 455 double difficulty, absl::BitGenRef random) final; 456 }; 457 458 // Pick a random subset of constraints and relax all the variables of these 459 // constraints. Note that to satisfy the difficulty, we might not relax all the 460 // variable of the "last" constraint. 461 // 462 // TODO(user): In the presence of connected components, this should just work 463 // on one of them. 464 class RelaxRandomConstraintsGenerator : public NeighborhoodGenerator { 465 public: RelaxRandomConstraintsGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)466 explicit RelaxRandomConstraintsGenerator( 467 NeighborhoodGeneratorHelper const* helper, const std::string& name) 468 : NeighborhoodGenerator(name, helper) {} 469 Neighborhood Generate(const CpSolverResponse& initial_solution, 470 double difficulty, absl::BitGenRef random) final; 471 }; 472 473 // Pick a random subset of variables that are constructed by a BFS in the 474 // variable <-> constraint graph. That is, pick a random variable, then all the 475 // variable connected by some constraint to the first one, and so on. The 476 // variable of the last "level" are selected randomly. 477 // 478 // Note that in the presence of connected component, this works correctly 479 // already. 480 class VariableGraphNeighborhoodGenerator : public NeighborhoodGenerator { 481 public: VariableGraphNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)482 explicit VariableGraphNeighborhoodGenerator( 483 NeighborhoodGeneratorHelper const* helper, const std::string& name) 484 : NeighborhoodGenerator(name, helper) {} 485 Neighborhood Generate(const CpSolverResponse& initial_solution, 486 double difficulty, absl::BitGenRef random) final; 487 }; 488 489 // Pick a random subset of constraint and relax all of their variables. We are a 490 // bit smarter than this because after the first constraint is selected, we only 491 // select constraints that share at least one variable with the already selected 492 // constraints. The variable from the "last" constraint are selected randomly. 493 class ConstraintGraphNeighborhoodGenerator : public NeighborhoodGenerator { 494 public: ConstraintGraphNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)495 explicit ConstraintGraphNeighborhoodGenerator( 496 NeighborhoodGeneratorHelper const* helper, const std::string& name) 497 : NeighborhoodGenerator(name, helper) {} 498 Neighborhood Generate(const CpSolverResponse& initial_solution, 499 double difficulty, absl::BitGenRef random) final; 500 }; 501 502 // Helper method for the scheduling neighborhood generators. Returns the model 503 // as neighborhood for the given set of intervals to relax. For each no_overlap 504 // constraints, it adds strict relation order between the non-relaxed intervals. 505 Neighborhood GenerateSchedulingNeighborhoodForRelaxation( 506 const absl::Span<const int> intervals_to_relax, 507 const CpSolverResponse& initial_solution, 508 const NeighborhoodGeneratorHelper& helper); 509 510 // Only make sense for scheduling problem. This select a random set of interval 511 // of the problem according to the difficulty. Then, for each no_overlap 512 // constraints, it adds strict relation order between the non-relaxed intervals. 513 // 514 // TODO(user): Also deal with cumulative constraint. 515 class SchedulingNeighborhoodGenerator : public NeighborhoodGenerator { 516 public: SchedulingNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)517 explicit SchedulingNeighborhoodGenerator( 518 NeighborhoodGeneratorHelper const* helper, const std::string& name) 519 : NeighborhoodGenerator(name, helper) {} 520 521 Neighborhood Generate(const CpSolverResponse& initial_solution, 522 double difficulty, absl::BitGenRef random) final; 523 }; 524 525 // Similar to SchedulingNeighborhoodGenerator except the set of intervals that 526 // are relaxed are from a specific random time interval. 527 class SchedulingTimeWindowNeighborhoodGenerator : public NeighborhoodGenerator { 528 public: SchedulingTimeWindowNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)529 explicit SchedulingTimeWindowNeighborhoodGenerator( 530 NeighborhoodGeneratorHelper const* helper, const std::string& name) 531 : NeighborhoodGenerator(name, helper) {} 532 533 Neighborhood Generate(const CpSolverResponse& initial_solution, 534 double difficulty, absl::BitGenRef random) final; 535 }; 536 537 // This routing based LNS generator will relax random arcs in all the paths of 538 // the circuit or routes constraints. 539 class RoutingRandomNeighborhoodGenerator : public NeighborhoodGenerator { 540 public: RoutingRandomNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)541 RoutingRandomNeighborhoodGenerator(NeighborhoodGeneratorHelper const* helper, 542 const std::string& name) 543 : NeighborhoodGenerator(name, helper) {} 544 545 Neighborhood Generate(const CpSolverResponse& initial_solution, 546 double difficulty, absl::BitGenRef random) final; 547 }; 548 549 // This routing based LNS generator will relax small sequences of arcs randomly 550 // chosen in all the paths of the circuit or routes constraints. 551 class RoutingPathNeighborhoodGenerator : public NeighborhoodGenerator { 552 public: RoutingPathNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)553 RoutingPathNeighborhoodGenerator(NeighborhoodGeneratorHelper const* helper, 554 const std::string& name) 555 : NeighborhoodGenerator(name, helper) {} 556 557 Neighborhood Generate(const CpSolverResponse& initial_solution, 558 double difficulty, absl::BitGenRef random) final; 559 }; 560 561 // This routing based LNS generator aims are relaxing one full path, and make 562 // some room on the other paths to absorb the nodes of the relaxed path. 563 // 564 // In order to do so, it will relax the first and the last arc of each path in 565 // the circuit or routes constraints. Then it will relax all arc literals in one 566 // random path. Then it will relax random arcs in the remaining paths until it 567 // reaches the given difficulty. 568 class RoutingFullPathNeighborhoodGenerator : public NeighborhoodGenerator { 569 public: RoutingFullPathNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)570 RoutingFullPathNeighborhoodGenerator( 571 NeighborhoodGeneratorHelper const* helper, const std::string& name) 572 : NeighborhoodGenerator(name, helper) {} 573 574 Neighborhood Generate(const CpSolverResponse& initial_solution, 575 double difficulty, absl::BitGenRef random) final; 576 }; 577 578 // Generates a neighborhood by fixing the variables to solutions reported in 579 // various repositories. This is inspired from RINS published in "Exploring 580 // relaxation induced neighborhoods to improve MIP solutions" 2004 by E. Danna 581 // et. 582 // 583 // If incomplete_solutions is provided, this generates a neighborhood by fixing 584 // the variable values to a solution in the SharedIncompleteSolutionManager and 585 // ignores the other repositories. 586 // 587 // Otherwise, if response_manager is not provided, this generates a neighborhood 588 // using only the linear/general relaxation values. The domain of the variables 589 // are reduced to the integer values around their lp solution/relaxation 590 // solution values. This was published in "RENS – The Relaxation Enforced 591 // Neighborhood" 2009 by Timo Berthold. 592 class RelaxationInducedNeighborhoodGenerator : public NeighborhoodGenerator { 593 public: RelaxationInducedNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const SharedResponseManager * response_manager,const SharedRelaxationSolutionRepository * relaxation_solutions,const SharedLPSolutionRepository * lp_solutions,SharedIncompleteSolutionManager * incomplete_solutions,const std::string & name)594 explicit RelaxationInducedNeighborhoodGenerator( 595 NeighborhoodGeneratorHelper const* helper, 596 const SharedResponseManager* response_manager, 597 const SharedRelaxationSolutionRepository* relaxation_solutions, 598 const SharedLPSolutionRepository* lp_solutions, 599 SharedIncompleteSolutionManager* incomplete_solutions, 600 const std::string& name) 601 : NeighborhoodGenerator(name, helper), 602 response_manager_(response_manager), 603 relaxation_solutions_(relaxation_solutions), 604 lp_solutions_(lp_solutions), 605 incomplete_solutions_(incomplete_solutions) { 606 CHECK(lp_solutions_ != nullptr || relaxation_solutions_ != nullptr || 607 incomplete_solutions != nullptr); 608 } 609 610 // Both initial solution and difficulty values are ignored. 611 Neighborhood Generate(const CpSolverResponse& initial_solution, 612 double difficulty, absl::BitGenRef random) final; 613 614 // Returns true if the required solutions are available. 615 bool ReadyToGenerate() const override; 616 617 private: 618 const SharedResponseManager* response_manager_; 619 const SharedRelaxationSolutionRepository* relaxation_solutions_; 620 const SharedLPSolutionRepository* lp_solutions_; 621 SharedIncompleteSolutionManager* incomplete_solutions_; 622 }; 623 624 // Generates a relaxation of the original model by removing a consecutive span 625 // of constraints starting at a random index. The number of constraints removed 626 // is in sync with the difficulty passed to the generator. 627 class ConsecutiveConstraintsRelaxationNeighborhoodGenerator 628 : public NeighborhoodGenerator { 629 public: ConsecutiveConstraintsRelaxationNeighborhoodGenerator(NeighborhoodGeneratorHelper const * helper,const std::string & name)630 explicit ConsecutiveConstraintsRelaxationNeighborhoodGenerator( 631 NeighborhoodGeneratorHelper const* helper, const std::string& name) 632 : NeighborhoodGenerator(name, helper) {} 633 Neighborhood Generate(const CpSolverResponse& initial_solution, 634 double difficulty, absl::BitGenRef random) final; 635 IsRelaxationGenerator()636 bool IsRelaxationGenerator() const override { return true; } ReadyToGenerate()637 bool ReadyToGenerate() const override { return true; } 638 }; 639 640 // Generates a relaxation of the original model by removing some constraints 641 // randomly with a given weight for each constraint that controls the 642 // probability of constraint getting removed. The number of constraints removed 643 // is in sync with the difficulty passed to the generator. Higher weighted 644 // constraints are more likely to get removed. 645 class WeightedRandomRelaxationNeighborhoodGenerator 646 : public NeighborhoodGenerator { 647 public: 648 WeightedRandomRelaxationNeighborhoodGenerator( 649 NeighborhoodGeneratorHelper const* helper, const std::string& name); 650 651 // Generates the neighborhood as described above. Also stores the removed 652 // constraints indices for adjusting the weights. 653 Neighborhood Generate(const CpSolverResponse& initial_solution, 654 double difficulty, absl::BitGenRef random) final; 655 IsRelaxationGenerator()656 bool IsRelaxationGenerator() const override { return true; } ReadyToGenerate()657 bool ReadyToGenerate() const override { return true; } 658 659 private: 660 // Adjusts the weights of the constraints removed to get the neighborhood 661 // based on the solve_data. 662 void AdditionalProcessingOnSynchronize(const SolveData& solve_data) override 663 ABSL_EXCLUSIVE_LOCKS_REQUIRED(generator_mutex_); 664 665 // Higher weighted constraints are more likely to get removed. 666 std::vector<double> constraint_weights_; 667 int num_removable_constraints_ = 0; 668 669 // Indices of the removed constraints per generated neighborhood. 670 absl::flat_hash_map<int64_t, std::vector<int>> removed_constraints_ 671 ABSL_GUARDED_BY(generator_mutex_); 672 673 // TODO(user): Move this to parent class if other generators start using 674 // feedbacks. 675 int64_t next_available_id_ ABSL_GUARDED_BY(generator_mutex_) = 0; 676 }; 677 678 } // namespace sat 679 } // namespace operations_research 680 681 #endif // OR_TOOLS_SAT_CP_MODEL_LNS_H_ 682