OR-Tools  9.1
synchronization.h
Go to the documentation of this file.
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_SYNCHRONIZATION_H_
15 #define OR_TOOLS_SAT_SYNCHRONIZATION_H_
16 
17 #include <cstdint>
18 #include <deque>
19 #include <limits>
20 #include <string>
21 #include <vector>
22 
23 #include "absl/random/bit_gen_ref.h"
24 #include "absl/random/random.h"
25 #include "absl/synchronization/mutex.h"
27 #include "ortools/base/logging.h"
28 #include "ortools/base/stl_util.h"
30 #include "ortools/sat/integer.h"
31 #include "ortools/sat/model.h"
32 #include "ortools/sat/sat_base.h"
34 #include "ortools/sat/util.h"
35 #include "ortools/util/bitset.h"
36 #include "ortools/util/logging.h"
38 
39 namespace operations_research {
40 namespace sat {
41 
42 // Thread-safe. Keeps a set of n unique best solution found so far.
43 //
44 // TODO(user): Maybe add some criteria to only keep solution with an objective
45 // really close to the best solution.
46 template <typename ValueType>
48  public:
49  explicit SharedSolutionRepository(int num_solutions_to_keep)
50  : num_solutions_to_keep_(num_solutions_to_keep) {
52  }
53 
54  // The solution format used by this class.
55  struct Solution {
56  // Solution with lower "rank" will be preferred
57  //
58  // TODO(user): Some LNS code assume that for the SharedSolutionRepository
59  // this rank is actually the unscaled internal minimization objective.
60  // Remove this assumptions by simply recomputing this value since it is not
61  // too costly to do so.
62  int64_t rank;
63 
64  std::vector<ValueType> variable_values;
65 
66  // Number of time this was returned by GetRandomBiasedSolution(). We use
67  // this information during the selection process.
68  //
69  // Should be private: only SharedSolutionRepository should modify this.
70  mutable int num_selected = 0;
71 
72  bool operator==(const Solution& other) const {
73  return rank == other.rank && variable_values == other.variable_values;
74  }
75  bool operator<(const Solution& other) const {
76  if (rank != other.rank) {
77  return rank < other.rank;
78  }
79  return variable_values < other.variable_values;
80  }
81  };
82 
83  // Returns the number of current solution in the pool. This will never
84  // decrease.
85  int NumSolutions() const;
86 
87  // Returns the solution #i where i must be smaller than NumSolutions().
88  Solution GetSolution(int index) const;
89 
90  // Returns the variable value of variable 'var_index' from solution
91  // 'solution_index' where solution_index must be smaller than NumSolutions()
92  // and 'var_index' must be smaller than number of variables.
93  ValueType GetVariableValueInSolution(int var_index, int solution_index) const;
94 
95  // Returns a random solution biased towards good solutions.
96  Solution GetRandomBiasedSolution(absl::BitGenRef random) const;
97 
98  // Add a new solution. Note that it will not be added to the pool of solution
99  // right away. One must call Synchronize for this to happen.
100  //
101  // Works in O(num_solutions_to_keep_).
102  void Add(const Solution& solution);
103 
104  // Updates the current pool of solution with the one recently added. Note that
105  // we use a stable ordering of solutions, so the final pool will be
106  // independent on the order of the calls to AddSolution() provided that the
107  // set of added solutions is the same.
108  //
109  // Works in O(num_solutions_to_keep_).
110  void Synchronize();
111 
112  protected:
113  // Helper method for adding the solutions once the mutex is acquired.
114  void AddInternal(const Solution& solution)
115  ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
116 
118  mutable absl::Mutex mutex_;
119  int64_t num_synchronization_ ABSL_GUARDED_BY(mutex_) = 0;
120 
121  // Our two solutions pools, the current one and the new one that will be
122  // merged into the current one on each Synchronize() calls.
123  mutable std::vector<int> tmp_indices_ ABSL_GUARDED_BY(mutex_);
124  std::vector<Solution> solutions_ ABSL_GUARDED_BY(mutex_);
125  std::vector<Solution> new_solutions_ ABSL_GUARDED_BY(mutex_);
126 };
127 
128 // This is currently only used to store feasible solution from our 'relaxation'
129 // LNS generators which in turn are used to generate some RINS neighborhood.
131  : public SharedSolutionRepository<int64_t> {
132  public:
133  explicit SharedRelaxationSolutionRepository(int num_solutions_to_keep)
134  : SharedSolutionRepository<int64_t>(num_solutions_to_keep) {}
135 
137 };
138 
140  public:
141  explicit SharedLPSolutionRepository(int num_solutions_to_keep)
142  : SharedSolutionRepository<double>(num_solutions_to_keep) {}
143 
144  void NewLPSolution(std::vector<double> lp_solution);
145 };
146 
147 // Set of partly filled solutions. They are meant to be finished by some lns
148 // worker.
149 //
150 // The solutions are stored as a vector of doubles. The value at index i
151 // represents the solution value of model variable indexed i. Note that some
152 // values can be infinity which should be interpreted as 'unknown' solution
153 // value for that variable. These solutions can not necessarily be completed to
154 // complete feasible solutions.
156  public:
157  bool HasNewSolution() const;
158  std::vector<double> GetNewSolution();
159 
160  void AddNewSolution(const std::vector<double>& lp_solution);
161 
162  private:
163  // New solutions are added and removed from the back.
164  std::vector<std::vector<double>> solutions_;
165  mutable absl::Mutex mutex_;
166 };
167 
168 // Manages the global best response kept by the solver. This class is
169 // responsible for logging the progress of the solutions and bounds as they are
170 // found.
171 //
172 // All functions are thread-safe except if specified otherwise.
174  public:
175  explicit SharedResponseManager(Model* model);
176 
177  // Loads the initial objective bounds and keep a reference to the objective to
178  // properly display the scaled bounds. This is optional if the model has no
179  // objective.
180  //
181  // This function is not thread safe.
182  void InitializeObjective(const CpModelProto& cp_model);
183 
184  // Reports OPTIMAL and stop the search if any gap limit are specified and
185  // crossed. By default, we only stop when we have the true optimal, which is
186  // well defined since we are solving our pure integer problem exactly.
188 
189  // Returns the current solver response. That is the best known response at the
190  // time of the call with the best feasible solution and objective bounds.
191  //
192  // Note that the solver statistics correspond to the last time a better
193  // solution was found or SetStatsFromModel() was called.
194  //
195  // If full response is true, we will do more postprocessing by calling all the
196  // AddFinalSolutionPostprocessor() postprocesors. Note that the response given
197  // to the AddSolutionCallback() will not call them.
198  CpSolverResponse GetResponse(bool full_response = true);
199 
200  // These "postprocessing" steps will be applied in REVERSE order of
201  // registration to all solution passed to the callbacks.
203  std::function<void(CpSolverResponse*)> postprocessor);
204 
205  // These "postprocessing" steps will only be applied after the others to the
206  // solution returned by GetResponse().
208  std::function<void(CpSolverResponse*)> postprocessor);
209 
210  // Adds a callback that will be called on each new solution (for
211  // statisfiablity problem) or each improving new solution (for an optimization
212  // problem). Returns its id so it can be unregistered if needed.
213  //
214  // Note that adding a callback is not free since the solution will be
215  // postsolved before this is called.
216  //
217  // Note that currently the class is waiting for the callback to finish before
218  // accepting any new updates. That could be changed if needed.
220  std::function<void(const CpSolverResponse&)> callback);
221  void UnregisterCallback(int callback_id);
222 
223  // The "inner" objective is the CpModelProto objective without scaling/offset.
224  // Note that these bound correspond to valid bound for the problem of finding
225  // a strictly better objective than the current one. Thus the lower bound is
226  // always a valid bound for the global problem, but the upper bound is NOT.
227  IntegerValue GetInnerObjectiveLowerBound();
228  IntegerValue GetInnerObjectiveUpperBound();
229 
230  // These functions return the same as the non-synchronized() version but
231  // only the values at the last time Synchronize() was called.
232  void Synchronize();
235 
236  // Returns the current best solution inner objective value or kInt64Max if
237  // there is no solution.
238  IntegerValue BestSolutionInnerObjectiveValue();
239 
240  // Returns the integral of the log of the absolute gap over deterministic
241  // time. This is mainly used to compare how fast the gap closes on a
242  // particular instance. Or to evaluate how efficient our LNS code is improving
243  // solution.
244  //
245  // Note: The integral will start counting on the first UpdatePrimalIntegral()
246  // call, since before the difference is assumed to be zero.
247  //
248  // Important: To report a proper deterministic integral, we only update it
249  // on UpdatePrimalIntegral() which should be called in the main subsolver
250  // synchronization loop.
251  //
252  // Note(user): In the litterature, people use the relative gap to the optimal
253  // solution (or the best known one), but this is ill defined in many case
254  // (like if the optimal cost is zero), so I prefer this version.
255  double PrimalIntegral() const;
256  void UpdatePrimalIntegral();
257 
258  // Sets this to true to have the "real" but non-deterministic primal integral.
259  // If this is true, then there is no need to manually call
260  // UpdatePrimalIntegral() but it is not an issue to do so.
262 
263  // Updates the inner objective bounds.
264  void UpdateInnerObjectiveBounds(const std::string& update_info,
265  IntegerValue lb, IntegerValue ub);
266 
267  // Reads the new solution from the response and update our state. For an
268  // optimization problem, we only do something if the solution is strictly
269  // improving.
270  //
271  // TODO(user): Only the following fields from response are accessed here, we
272  // might want a tighter API:
273  // - solution_info
274  // - solution
275  // - solution_lower_bounds and solution_upper_bounds.
277 
278  // Changes the solution to reflect the fact that the "improving" problem is
279  // infeasible. This means that if we have a solution, we have proven
280  // optimality, otherwise the global problem is infeasible.
281  //
282  // Note that this shouldn't be called before the solution is actually
283  // reported. We check for this case in NewSolution().
284  void NotifyThatImprovingProblemIsInfeasible(const std::string& worker_info);
285 
286  // Adds to the shared response a subset of assumptions that are enough to
287  // make the problem infeasible.
288  void AddUnsatCore(const std::vector<int>& core);
289 
290  // Sets the statistics in the response to the one of the solver inside the
291  // given in-memory model. This does nothing if the model is nullptr.
292  //
293  // TODO(user): Also support merging statistics together.
295 
296  // Returns true if we found the optimal solution or the problem was proven
297  // infeasible. Note that if the gap limit is reached, we will also report
298  // OPTIMAL and consider the problem solved.
299  bool ProblemIsSolved() const;
300 
301  // Returns the underlying solution repository where we keep a set of best
302  // solutions.
304  return solutions_;
305  }
307  return &solutions_;
308  }
309 
310  // This should be called after the model is loaded. It will read the file
311  // specified by --cp_model_load_debug_solution and properly fill the
312  // model->Get<DebugSolution>() vector.
313  //
314  // TODO(user): Note that for now, only the IntegerVariable value are loaded,
315  // not the value of the pure Booleans variables.
316  void LoadDebugSolution(Model*);
317 
318  // Debug only. Set dump prefix for solutions written to file.
319  void set_dump_prefix(const std::string& dump_prefix) {
320  dump_prefix_ = dump_prefix;
321  }
322 
323  // Display improvement stats.
325 
326  // This is here for the few codepath that needs to modify the returned
327  // response directly. Note that this do not work in parallel.
328  //
329  // TODO(user): This can probably be removed.
331  absl::MutexLock mutex_lock(&mutex_);
332  return &best_response_;
333  }
334 
335  private:
336  void TestGapLimitsIfNeeded() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
337  void FillObjectiveValuesInBestResponse()
338  ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
339  void SetStatsFromModelInternal(Model* model)
340  ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
341  void UpdatePrimalIntegralInternal() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
342 
343  void RegisterSolutionFound(const std::string& improvement_info)
344  ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
345  void RegisterObjectiveBoundImprovement(const std::string& improvement_info)
346  ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
347 
348  const bool enumerate_all_solutions_;
349  const WallTimer& wall_timer_;
350  ModelSharedTimeLimit* shared_time_limit_;
351  CpObjectiveProto const* objective_or_null_ = nullptr;
352 
353  mutable absl::Mutex mutex_;
354 
355  // Gap limits.
356  double absolute_gap_limit_ ABSL_GUARDED_BY(mutex_) = 0.0;
357  double relative_gap_limit_ ABSL_GUARDED_BY(mutex_) = 0.0;
358 
359  CpSolverResponse best_response_ ABSL_GUARDED_BY(mutex_);
360  SharedSolutionRepository<int64_t> solutions_ ABSL_GUARDED_BY(mutex_);
361 
362  int num_solutions_ ABSL_GUARDED_BY(mutex_) = 0;
363  int64_t inner_objective_lower_bound_ ABSL_GUARDED_BY(mutex_) =
364  std::numeric_limits<int64_t>::min();
365  int64_t inner_objective_upper_bound_ ABSL_GUARDED_BY(mutex_) =
366  std::numeric_limits<int64_t>::max();
367  int64_t best_solution_objective_value_ ABSL_GUARDED_BY(mutex_) =
368  std::numeric_limits<int64_t>::max();
369 
370  IntegerValue synchronized_inner_objective_lower_bound_ ABSL_GUARDED_BY(
371  mutex_) = IntegerValue(std::numeric_limits<int64_t>::min());
372  IntegerValue synchronized_inner_objective_upper_bound_ ABSL_GUARDED_BY(
373  mutex_) = IntegerValue(std::numeric_limits<int64_t>::max());
374 
375  bool update_integral_on_each_change_ ABSL_GUARDED_BY(mutex_) = false;
376  double primal_integral_ ABSL_GUARDED_BY(mutex_) = 0.0;
377  double last_absolute_gap_ ABSL_GUARDED_BY(mutex_) = 0.0;
378  double last_primal_integral_time_stamp_ ABSL_GUARDED_BY(mutex_) = 0.0;
379 
380  int next_callback_id_ ABSL_GUARDED_BY(mutex_) = 0;
381  std::vector<std::pair<int, std::function<void(const CpSolverResponse&)>>>
382  callbacks_ ABSL_GUARDED_BY(mutex_);
383 
384  std::vector<std::function<void(CpSolverResponse*)>> postprocessors_
385  ABSL_GUARDED_BY(mutex_);
386  std::vector<std::function<void(CpSolverResponse*)>> final_postprocessors_
387  ABSL_GUARDED_BY(mutex_);
388 
389  // Dump prefix.
390  std::string dump_prefix_;
391 
392  // Used for statistics of the improvements found by workers.
393  std::map<std::string, int> primal_improvements_count_ ABSL_GUARDED_BY(mutex_);
394  std::map<std::string, int> dual_improvements_count_ ABSL_GUARDED_BY(mutex_);
395 
396  SolverLogger* logger_;
397 };
398 
399 // This class manages a pool of lower and upper bounds on a set of variables in
400 // a parallel context.
402  public:
404 
405  // Reports a set of locally improved variable bounds to the shared bounds
406  // manager. The manager will compare these bounds changes against its
407  // global state, and incorporate the improving ones.
408  void ReportPotentialNewBounds(const CpModelProto& model_proto,
409  const std::string& worker_name,
410  const std::vector<int>& variables,
411  const std::vector<int64_t>& new_lower_bounds,
412  const std::vector<int64_t>& new_upper_bounds);
413 
414  // Returns a new id to be used in GetChangedBounds(). This is just an ever
415  // increasing sequence starting from zero. Note that the class is not designed
416  // to have too many of these.
417  int RegisterNewId();
418 
419  // When called, returns the set of bounds improvements since
420  // the last time this method was called with the same id.
421  void GetChangedBounds(int id, std::vector<int>* variables,
422  std::vector<int64_t>* new_lower_bounds,
423  std::vector<int64_t>* new_upper_bounds);
424 
425  // Publishes any new bounds so that GetChangedBounds() will reflect the latest
426  // state.
427  void Synchronize();
428 
429  private:
430  const int num_variables_;
431  const CpModelProto& model_proto_;
432 
433  absl::Mutex mutex_;
434 
435  // These are always up to date.
436  std::vector<int64_t> lower_bounds_ ABSL_GUARDED_BY(mutex_);
437  std::vector<int64_t> upper_bounds_ ABSL_GUARDED_BY(mutex_);
438  SparseBitset<int64_t> changed_variables_since_last_synchronize_
439  ABSL_GUARDED_BY(mutex_);
440 
441  // These are only updated on Synchronize().
442  std::vector<int64_t> synchronized_lower_bounds_ ABSL_GUARDED_BY(mutex_);
443  std::vector<int64_t> synchronized_upper_bounds_ ABSL_GUARDED_BY(mutex_);
444  std::deque<SparseBitset<int64_t>> id_to_changed_variables_
445  ABSL_GUARDED_BY(mutex_);
446 };
447 
448 template <typename ValueType>
450  absl::MutexLock mutex_lock(&mutex_);
451  return solutions_.size();
452 }
453 
454 template <typename ValueType>
457  absl::MutexLock mutex_lock(&mutex_);
458  return solutions_[i];
459 }
460 
461 template <typename ValueType>
463  int var_index, int solution_index) const {
464  absl::MutexLock mutex_lock(&mutex_);
465  return solutions_[solution_index].variable_values[var_index];
466 }
467 
468 // TODO(user): Experiments on the best distribution.
469 template <typename ValueType>
472  absl::BitGenRef random) const {
473  absl::MutexLock mutex_lock(&mutex_);
474  const int64_t best_rank = solutions_[0].rank;
475 
476  // As long as we have solution with the best objective that haven't been
477  // explored too much, we select one uniformly. Otherwise, we select a solution
478  // from the pool uniformly.
479  //
480  // Note(user): Because of the increase of num_selected, this is dependent on
481  // the order of call. It should be fine for "determinism" because we do
482  // generate the task of a batch always in the same order.
483  const int kExplorationThreshold = 100;
484 
485  // Select all the best solution with a low enough selection count.
486  tmp_indices_.clear();
487  for (int i = 0; i < solutions_.size(); ++i) {
488  const auto& solution = solutions_[i];
489  if (solution.rank == best_rank &&
490  solution.num_selected <= kExplorationThreshold) {
491  tmp_indices_.push_back(i);
492  }
493  }
494 
495  int index = 0;
496  if (tmp_indices_.empty()) {
497  index = absl::Uniform<int>(random, 0, solutions_.size());
498  } else {
499  index = tmp_indices_[absl::Uniform<int>(random, 0, tmp_indices_.size())];
500  }
501  solutions_[index].num_selected++;
502  return solutions_[index];
503 }
504 
505 template <typename ValueType>
506 void SharedSolutionRepository<ValueType>::Add(const Solution& solution) {
507  absl::MutexLock mutex_lock(&mutex_);
508  AddInternal(solution);
509 }
510 
511 template <typename ValueType>
513  const Solution& solution) {
514  int worse_solution_index = 0;
515  for (int i = 0; i < new_solutions_.size(); ++i) {
516  // Do not add identical solution.
517  if (new_solutions_[i] == solution) return;
518  if (new_solutions_[worse_solution_index] < new_solutions_[i]) {
519  worse_solution_index = i;
520  }
521  }
522  if (new_solutions_.size() < num_solutions_to_keep_) {
523  new_solutions_.push_back(solution);
524  } else if (solution < new_solutions_[worse_solution_index]) {
525  new_solutions_[worse_solution_index] = solution;
526  }
527 }
528 
529 template <typename ValueType>
531  absl::MutexLock mutex_lock(&mutex_);
532  solutions_.insert(solutions_.end(), new_solutions_.begin(),
533  new_solutions_.end());
534  new_solutions_.clear();
535 
536  // We use a stable sort to keep the num_selected count for the already
537  // existing solutions.
538  //
539  // TODO(user): Intoduce a notion of orthogonality to diversify the pool?
541  if (solutions_.size() > num_solutions_to_keep_) {
542  solutions_.resize(num_solutions_to_keep_);
543  }
544  num_synchronization_++;
545 }
546 
547 } // namespace sat
548 } // namespace operations_research
549 
550 #endif // OR_TOOLS_SAT_SYNCHRONIZATION_H_
int64_t min
Definition: alldiff_cst.cc:139
int64_t num_synchronization_ ABSL_GUARDED_BY(mutex_)=0
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
void AddNewSolution(const std::vector< double > &lp_solution)
void AddFinalSolutionPostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void NewSolution(const CpSolverResponse &response, Model *model)
MPCallback * callback
GRBmodel * model
void NewLPSolution(std::vector< double > lp_solution)
void AddUnsatCore(const std::vector< int > &core)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
int64_t max
Definition: alldiff_cst.cc:140
Definition: cleanup.h:22
CpSolverResponse GetResponse(bool full_response=true)
ValueType GetVariableValueInSolution(int var_index, int solution_index) const
int AddSolutionCallback(std::function< void(const CpSolverResponse &)> callback)
void InitializeObjective(const CpModelProto &cp_model)
void AddSolutionPostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
int index
Definition: pack.cc:509
void AddInternal(const Solution &solution) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_)
SharedResponseManager * response
void set_dump_prefix(const std::string &dump_prefix)
CpModelProto const * model_proto
SharedSolutionRepository< int64_t > * MutableSolutionsRepository()
void STLStableSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:75
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
Collection of objects used to extend the Constraint Solver library.
SatParameters parameters
Solution GetRandomBiasedSolution(absl::BitGenRef random) const
void SetGapLimitsFromParameters(const SatParameters &parameters)
const SharedSolutionRepository< int64_t > & SolutionsRepository() const
void NewRelaxationSolution(const CpSolverResponse &response)