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