OR-Tools  9.2
synchronization.cc
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 
15 
16 #include <cstdint>
17 #include <limits>
18 
19 #if !defined(__PORTABLE_PLATFORM__)
20 #include "ortools/base/file.h"
22 #endif // __PORTABLE_PLATFORM__
23 
24 #include "absl/container/flat_hash_set.h"
25 #include "absl/random/random.h"
27 #include "ortools/base/stl_util.h"
30 #include "ortools/sat/integer.h"
32 #include "ortools/sat/model.h"
33 #include "ortools/sat/sat_base.h"
34 #include "ortools/util/logging.h"
36 
37 ABSL_FLAG(bool, cp_model_dump_solutions, false,
38  "DEBUG ONLY. If true, all the intermediate solution will be dumped "
39  "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.");
40 
41 ABSL_FLAG(
42  std::string, cp_model_load_debug_solution, "",
43  "DEBUG ONLY. When this is set to a non-empty file name, "
44  "we will interpret this as an internal solution which can be used for "
45  "debugging. For instance we use it to identify wrong cuts/reasons.");
46 
47 namespace operations_research {
48 namespace sat {
49 
51  const CpSolverResponse& response) {
52  // Note that the Add() method already applies mutex lock. So we don't need it
53  // here.
54  if (response.solution().empty()) return;
55 
56  // Add this solution to the pool.
58  solution.variable_values.assign(response.solution().begin(),
59  response.solution().end());
60  // For now we use the negated lower bound as the "internal objective" to
61  // prefer solution with an higher bound.
62  //
63  // Note: If the model doesn't have objective, the best_objective_bound is set
64  // to default value 0.
65  solution.rank = -response.best_objective_bound();
66 
67  Add(solution);
68 }
69 
71  std::vector<double> lp_solution) {
72  if (lp_solution.empty()) return;
73 
74  // Add this solution to the pool.
76  solution.variable_values = std::move(lp_solution);
77 
78  // We always prefer to keep the solution from the last synchronize batch.
79  absl::MutexLock mutex_lock(&mutex_);
80  solution.rank = -num_synchronization_;
81  AddInternal(solution);
82 }
83 
85  absl::MutexLock mutex_lock(&mutex_);
86  return !solutions_.empty();
87 }
88 
90  absl::MutexLock mutex_lock(&mutex_);
91  std::vector<double> solution;
92  if (solutions_.empty()) return solution;
93 
94  solution = std::move(solutions_.back());
95  solutions_.pop_back();
96  return solution;
97 }
98 
100  const std::vector<double>& lp_solution) {
101  absl::MutexLock mutex_lock(&mutex_);
102  solutions_.push_back(lp_solution);
103 }
104 
106  : parameters_(*model->GetOrCreate<SatParameters>()),
107  wall_timer_(*model->GetOrCreate<WallTimer>()),
108  shared_time_limit_(model->GetOrCreate<ModelSharedTimeLimit>()),
109  solutions_(parameters_.solution_pool_size()),
110  logger_(model->GetOrCreate<SolverLogger>()) {}
111 
112 namespace {
113 
114 std::string ProgressMessage(const std::string& event_or_solution_count,
115  double time_in_seconds, double obj_best,
116  double obj_lb, double obj_ub,
117  const std::string& solution_info) {
118  const std::string obj_next =
119  obj_lb <= obj_ub ? absl::StrFormat("next:[%.9g,%.9g]", obj_lb, obj_ub)
120  : "next:[]";
121  return absl::StrFormat("#%-5s %6.2fs best:%-5.9g %-15s %s",
122  event_or_solution_count, time_in_seconds, obj_best,
123  obj_next, solution_info);
124 }
125 
126 std::string SatProgressMessage(const std::string& event_or_solution_count,
127  double time_in_seconds,
128  const std::string& solution_info) {
129  return absl::StrFormat("#%-5s %6.2fs %s", event_or_solution_count,
130  time_in_seconds, solution_info);
131 }
132 
133 } // namespace
134 
136  absl::MutexLock mutex_lock(&mutex_);
137  SOLVER_LOG(logger_,
138  absl::StrFormat("#Model %6.2fs %s", wall_timer_.Get(), message));
139 }
140 
142  if (cp_model.has_objective()) {
143  objective_or_null_ = &cp_model.objective();
144  const Domain domain = ReadDomainFromProto(cp_model.objective());
145  if (!domain.IsEmpty()) {
146  UpdateInnerObjectiveBounds("initial_domain", IntegerValue(domain.Min()),
147  IntegerValue(domain.Max()));
148  }
149  } else {
150  objective_or_null_ = nullptr;
151  }
152 }
153 
155  absl::MutexLock mutex_lock(&mutex_);
156  update_integral_on_each_change_ = set;
157 }
158 
160  absl::MutexLock mutex_lock(&mutex_);
161  UpdateGapIntegralInternal();
162 }
163 
164 void SharedResponseManager::UpdateGapIntegralInternal() {
165  if (objective_or_null_ == nullptr) return;
166 
167  const double current_time = shared_time_limit_->GetElapsedDeterministicTime();
168  const double time_delta = current_time - last_gap_integral_time_stamp_;
169 
170  // We use the log of the absolute objective gap.
171  //
172  // Using the log should count no solution as just log(2*64) = 18, and
173  // otherwise just compare order of magnitude which seems nice. Also, It is
174  // more easy to compare the primal integral with the total time.
175  const CpObjectiveProto& obj = *objective_or_null_;
176  const double factor =
177  obj.scaling_factor() != 0.0 ? std::abs(obj.scaling_factor()) : 1.0;
178  const double bounds_delta = std::log(1 + factor * last_absolute_gap_);
179  gap_integral_ += time_delta * bounds_delta;
180 
181  // Update with new value.
182  last_gap_integral_time_stamp_ = current_time;
183  last_absolute_gap_ =
184  std::max(0.0, static_cast<double>(inner_objective_upper_bound_) -
185  static_cast<double>(inner_objective_lower_bound_));
186 }
187 
189  const SatParameters& parameters) {
190  absl::MutexLock mutex_lock(&mutex_);
191  if (objective_or_null_ == nullptr) return;
192  absolute_gap_limit_ = parameters.absolute_gap_limit();
193  relative_gap_limit_ = parameters.relative_gap_limit();
194 }
195 
196 void SharedResponseManager::TestGapLimitsIfNeeded() {
197  // This is called on each internal limit change, so it is a good place to
198  // update the integral. Note that this is not called at the end of the search
199  // though.
200  if (update_integral_on_each_change_) UpdateGapIntegralInternal();
201 
202  // Abort if there is not limit set, if the gap is not defined or if we already
203  // proved optimality or infeasibility.
204  if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0) return;
205  if (best_solution_objective_value_ >= kMaxIntegerValue) return;
206  if (inner_objective_lower_bound_ <= kMinIntegerValue) return;
207  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) return;
208 
209  const CpObjectiveProto& obj = *objective_or_null_;
210  const double user_best =
211  ScaleObjectiveValue(obj, best_solution_objective_value_);
212  const double user_bound =
213  ScaleObjectiveValue(obj, inner_objective_lower_bound_);
214  const double gap = std::abs(user_best - user_bound);
215  if (gap <= absolute_gap_limit_) {
216  SOLVER_LOG(logger_, "Absolute gap limit of ", absolute_gap_limit_,
217  " reached.");
218  best_response_.set_status(CpSolverStatus::OPTIMAL);
219 
220  // Note(user): Some code path in single-thread assumes that the problem
221  // can only be solved when they have proven infeasibility and do not check
222  // the ProblemIsSolved() method. So we force a stop here.
223  shared_time_limit_->Stop();
224  }
225  if (gap / std::max(1.0, std::abs(user_best)) < relative_gap_limit_) {
226  SOLVER_LOG(logger_, "Relative gap limit of ", relative_gap_limit_,
227  " reached.");
228  best_response_.set_status(CpSolverStatus::OPTIMAL);
229 
230  // Same as above.
231  shared_time_limit_->Stop();
232  }
233 }
234 
236  const std::string& update_info, IntegerValue lb, IntegerValue ub) {
237  absl::MutexLock mutex_lock(&mutex_);
238  CHECK(objective_or_null_ != nullptr);
239 
240  // The problem is already solved!
241  //
242  // TODO(user): A thread might not be notified right away that the new bounds
243  // that it is pushing make the problem infeasible. Fix that. For now we just
244  // abort early here to avoid logging the "#Done" message multiple times.
245  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
246  return;
247  }
248 
249  const bool change =
250  (lb > inner_objective_lower_bound_ || ub < inner_objective_upper_bound_);
251  if (lb > inner_objective_lower_bound_) {
252  // When the improving problem is infeasible, it is possible to report
253  // arbitrary high inner_objective_lower_bound_. We make sure it never cross
254  // the current best solution, so that we always report globablly valid lower
255  // bound.
256  DCHECK_LE(inner_objective_upper_bound_, best_solution_objective_value_);
257  inner_objective_lower_bound_ =
258  std::min(best_solution_objective_value_, lb.value());
259  }
260  if (ub < inner_objective_upper_bound_) {
261  inner_objective_upper_bound_ = ub.value();
262  }
263  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
264  if (best_response_.status() == CpSolverStatus::FEASIBLE ||
265  best_response_.status() == CpSolverStatus::OPTIMAL) {
266  best_response_.set_status(CpSolverStatus::OPTIMAL);
267  } else {
268  best_response_.set_status(CpSolverStatus::INFEASIBLE);
269  }
270  if (update_integral_on_each_change_) UpdateGapIntegralInternal();
271  SOLVER_LOG(logger_,
272  SatProgressMessage("Done", wall_timer_.Get(), update_info));
273  return;
274  }
275  if (logger_->LoggingIsEnabled() && change) {
276  const CpObjectiveProto& obj = *objective_or_null_;
277  const double best =
278  ScaleObjectiveValue(obj, best_solution_objective_value_);
279  double new_lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
280  double new_ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
281  if (obj.scaling_factor() < 0) {
282  std::swap(new_lb, new_ub);
283  }
284  RegisterObjectiveBoundImprovement(update_info);
285  SOLVER_LOG(logger_, ProgressMessage("Bound", wall_timer_.Get(), best,
286  new_lb, new_ub, update_info));
287  }
288  if (change) TestGapLimitsIfNeeded();
289 }
290 
291 // Invariant: the status always start at UNKNOWN and can only evolve as follow:
292 // UNKNOWN -> FEASIBLE -> OPTIMAL
293 // UNKNOWN -> INFEASIBLE
295  const std::string& worker_info) {
296  absl::MutexLock mutex_lock(&mutex_);
297  if (best_response_.status() == CpSolverStatus::FEASIBLE ||
298  best_response_.status() == CpSolverStatus::OPTIMAL) {
299  // We also use this status to indicate that we enumerated all solutions to
300  // a feasible problem.
301  best_response_.set_status(CpSolverStatus::OPTIMAL);
302 
303  // We just proved that the best solution cannot be improved uppon, so we
304  // have a new lower bound.
305  inner_objective_lower_bound_ = best_solution_objective_value_;
306  if (update_integral_on_each_change_) UpdateGapIntegralInternal();
307  } else {
308  CHECK_EQ(num_solutions_, 0);
309  best_response_.set_status(CpSolverStatus::INFEASIBLE);
310  }
311  SOLVER_LOG(logger_,
312  SatProgressMessage("Done", wall_timer_.Get(), worker_info));
313 }
314 
315 void SharedResponseManager::AddUnsatCore(const std::vector<int>& core) {
316  absl::MutexLock mutex_lock(&mutex_);
317  best_response_.clear_sufficient_assumptions_for_infeasibility();
318  for (const int ref : core) {
319  best_response_.add_sufficient_assumptions_for_infeasibility(ref);
320  }
321 }
322 
324  absl::MutexLock mutex_lock(&mutex_);
325  return IntegerValue(inner_objective_lower_bound_);
326 }
327 
329  absl::MutexLock mutex_lock(&mutex_);
330  return IntegerValue(inner_objective_upper_bound_);
331 }
332 
334  absl::MutexLock mutex_lock(&mutex_);
335  synchronized_inner_objective_lower_bound_ =
336  IntegerValue(inner_objective_lower_bound_);
337  synchronized_inner_objective_upper_bound_ =
338  IntegerValue(inner_objective_upper_bound_);
339 }
340 
342  absl::MutexLock mutex_lock(&mutex_);
343  return synchronized_inner_objective_lower_bound_;
344 }
345 
347  absl::MutexLock mutex_lock(&mutex_);
348  return synchronized_inner_objective_upper_bound_;
349 }
350 
352  absl::MutexLock mutex_lock(&mutex_);
353  return IntegerValue(best_solution_objective_value_);
354 }
355 
357  absl::MutexLock mutex_lock(&mutex_);
358  return gap_integral_;
359 }
360 
362  std::function<void(std::vector<int64_t>*)> postprocessor) {
363  absl::MutexLock mutex_lock(&mutex_);
364  solution_postprocessors_.push_back(postprocessor);
365 }
366 
368  std::function<void(CpSolverResponse*)> postprocessor) {
369  absl::MutexLock mutex_lock(&mutex_);
370  postprocessors_.push_back(postprocessor);
371 }
372 
374  std::function<void(CpSolverResponse*)> postprocessor) {
375  absl::MutexLock mutex_lock(&mutex_);
376  final_postprocessors_.push_back(postprocessor);
377 }
378 
380  std::function<void(const CpSolverResponse&)> callback) {
381  absl::MutexLock mutex_lock(&mutex_);
382  const int id = next_callback_id_++;
383  callbacks_.emplace_back(id, std::move(callback));
384  return id;
385 }
386 
388  absl::MutexLock mutex_lock(&mutex_);
389  for (int i = 0; i < callbacks_.size(); ++i) {
390  if (callbacks_[i].first == callback_id) {
391  callbacks_.erase(callbacks_.begin() + i);
392  return;
393  }
394  }
395  LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
396 }
397 
398 CpSolverResponse SharedResponseManager::GetResponseInternal() {
399  FillObjectiveValuesInBestResponse();
400 
401  // We need to copy the response before we postsolve it.
402  CpSolverResponse result = best_response_;
403  if (result.status() == CpSolverStatus::FEASIBLE ||
404  result.status() == CpSolverStatus::OPTIMAL) {
405  std::vector<int64_t> solution(result.solution().begin(),
406  result.solution().end());
407  for (int i = solution_postprocessors_.size(); --i >= 0;) {
408  solution_postprocessors_[i](&solution);
409  }
410  result.mutable_solution()->Assign(solution.begin(), solution.end());
411  }
412  for (int i = postprocessors_.size(); --i >= 0;) {
413  postprocessors_[i](&result);
414  }
415  return result;
416 }
417 
419  absl::MutexLock mutex_lock(&mutex_);
420  CpSolverResponse result = GetResponseInternal();
421  if (full_response) {
422  // If this is true, we postsolve and copy all of our solutions.
423  if (parameters_.fill_additional_solutions_in_response()) {
424  std::vector<int64_t> temp;
425  for (int i = 0; i < solutions_.NumSolutions(); ++i) {
426  temp = solutions_.GetSolution(i).variable_values;
427  for (int i = solution_postprocessors_.size(); --i >= 0;) {
428  solution_postprocessors_[i](&temp);
429  }
430  result.add_additional_solutions()->mutable_values()->Assign(
431  temp.begin(), temp.end());
432  }
433  }
434  for (int i = final_postprocessors_.size(); --i >= 0;) {
435  final_postprocessors_[i](&result);
436  }
437  }
438  return result;
439 }
440 
441 void SharedResponseManager::FillObjectiveValuesInBestResponse() {
442  if (objective_or_null_ == nullptr) return;
443  const CpObjectiveProto& obj = *objective_or_null_;
444 
445  if (best_response_.status() == CpSolverStatus::INFEASIBLE) {
446  best_response_.clear_objective_value();
447  best_response_.clear_best_objective_bound();
448  best_response_.clear_inner_objective_lower_bound();
449  return;
450  }
451 
452  // Set the objective value.
453  // If we don't have any solution, we use our inner bound.
454  if (best_response_.status() == CpSolverStatus::UNKNOWN) {
455  best_response_.set_objective_value(
456  ScaleObjectiveValue(obj, inner_objective_upper_bound_));
457  } else {
458  best_response_.set_objective_value(
459  ScaleObjectiveValue(obj, best_solution_objective_value_));
460  }
461 
462  // Update the best bound in the response.
463  best_response_.set_inner_objective_lower_bound(
464  ScaleInnerObjectiveValue(obj, inner_objective_lower_bound_));
465  best_response_.set_best_objective_bound(
466  ScaleObjectiveValue(obj, inner_objective_lower_bound_));
467 
468  // Update the primal integral.
469  best_response_.set_gap_integral(gap_integral_);
470 }
471 
473  Model* model) {
474  absl::MutexLock mutex_lock(&mutex_);
475 
476  // Special case if the user asked to keep solutions in the pool.
477  if (objective_or_null_ == nullptr && parameters_.enumerate_all_solutions() &&
480  solution.variable_values.assign(response.solution().begin(),
481  response.solution().end());
482  solutions_.Add(solution);
483  }
484 
485  if (objective_or_null_ != nullptr) {
486  const int64_t objective_value =
487  ComputeInnerObjective(*objective_or_null_, response);
488 
489  // Add this solution to the pool, even if it is not improving.
490  if (!response.solution().empty()) {
492  solution.variable_values.assign(response.solution().begin(),
493  response.solution().end());
494  solution.rank = objective_value;
495  solutions_.Add(solution);
496  }
497 
498  // Ignore any non-strictly improving solution.
499  if (objective_value > inner_objective_upper_bound_) return;
500 
501  // Our inner_objective_lower_bound_ should be a globaly valid bound, until
502  // the problem become infeasible (i.e the lb > ub) in which case the bound
503  // is no longer globally valid. Here, because we have a strictly improving
504  // solution, we shouldn't be in the infeasible setting yet.
505  DCHECK_GE(objective_value, inner_objective_lower_bound_);
506 
507  DCHECK_LT(objective_value, best_solution_objective_value_);
508  best_solution_objective_value_ = objective_value;
509 
510  // Update the new bound.
511  inner_objective_upper_bound_ = objective_value - 1;
512  }
513 
514  // TODO(user): Hack. In single thread, no one is synchronizing the solution,
515  // so we should do it from here. We currently "reuse"
516  // update_integral_on_each_change_ which should probably just change name.
517  if (update_integral_on_each_change_) {
518  solutions_.Synchronize();
519  }
520 
521  // Note that the objective will be filled by
522  // FillObjectiveValuesInBestResponse().
523  if (objective_or_null_ == nullptr && !parameters_.enumerate_all_solutions()) {
524  best_response_.set_status(CpSolverStatus::OPTIMAL);
525  } else {
526  best_response_.set_status(CpSolverStatus::FEASIBLE);
527  }
528 
529  best_response_.set_solution_info(response.solution_info());
530  *best_response_.mutable_solution() = response.solution();
531 
532  // Mark model as OPTIMAL if the inner bound crossed.
533  if (objective_or_null_ != nullptr &&
534  inner_objective_lower_bound_ > inner_objective_upper_bound_) {
535  best_response_.set_status(CpSolverStatus::OPTIMAL);
536  }
537 
538  // Logging.
539  ++num_solutions_;
540  if (logger_->LoggingIsEnabled()) {
541  std::string solution_info = response.solution_info();
542  if (model != nullptr) {
543  const int64_t num_bool = model->Get<Trail>()->NumVariables();
544  const int64_t num_fixed = model->Get<SatSolver>()->NumFixedVariables();
545  absl::StrAppend(&solution_info, " fixed_bools:", num_fixed, "/",
546  num_bool);
547  }
548 
549  if (objective_or_null_ != nullptr) {
550  const CpObjectiveProto& obj = *objective_or_null_;
551  const double best =
552  ScaleObjectiveValue(obj, best_solution_objective_value_);
553  double lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
554  double ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
555  if (obj.scaling_factor() < 0) {
556  std::swap(lb, ub);
557  }
558  RegisterSolutionFound(solution_info);
559  SOLVER_LOG(logger_, ProgressMessage(absl::StrCat(num_solutions_),
560  wall_timer_.Get(), best, lb, ub,
561  solution_info));
562  } else {
563  SOLVER_LOG(logger_, SatProgressMessage(absl::StrCat(num_solutions_),
564  wall_timer_.Get(), solution_info));
565  }
566  }
567 
568  // Call callbacks.
569  // Note that we cannot call function that try to get the mutex_ here.
570  TestGapLimitsIfNeeded();
571  if (!callbacks_.empty()) {
572  SetStatsFromModelInternal(model);
573  const CpSolverResponse copy = GetResponseInternal();
574  for (const auto& pair : callbacks_) {
575  pair.second(copy);
576  }
577  }
578 
579 #if !defined(__PORTABLE_PLATFORM__)
580  // We protect solution dumping with log_updates as LNS subsolvers share
581  // another solution manager, and we do not want to dump those.
582  if (absl::GetFlag(FLAGS_cp_model_dump_solutions)) {
583  const std::string file =
584  absl::StrCat(dump_prefix_, "solution_", num_solutions_, ".pb.txt");
585  LOG(INFO) << "Dumping solution to '" << file << "'.";
586  CHECK_OK(file::SetTextProto(file, best_response_, file::Defaults()));
587  }
588 #endif // __PORTABLE_PLATFORM__
589 }
590 
592 #if !defined(__PORTABLE_PLATFORM__)
593  if (absl::GetFlag(FLAGS_cp_model_load_debug_solution).empty()) return;
594  if (model->Get<DebugSolution>() != nullptr) return; // Already loaded.
595 
597  LOG(INFO) << "Reading solution from '"
598  << absl::GetFlag(FLAGS_cp_model_load_debug_solution) << "'.";
599  CHECK_OK(file::GetTextProto(absl::GetFlag(FLAGS_cp_model_load_debug_solution),
600  &response, file::Defaults()));
601 
602  const auto& mapping = *model->GetOrCreate<CpModelMapping>();
603  auto& debug_solution = *model->GetOrCreate<DebugSolution>();
604  debug_solution.resize(
605  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value());
606  for (int i = 0; i < response.solution().size(); ++i) {
607  if (!mapping.IsInteger(i)) continue;
608  const IntegerVariable var = mapping.Integer(i);
609  debug_solution[var] = response.solution(i);
610  debug_solution[NegationOf(var)] = -response.solution(i);
611  }
612 
613  // The objective variable is usually not part of the proto, but it is still
614  // nice to have it, so we recompute it here.
615  auto* objective_def = model->Get<ObjectiveDefinition>();
616  if (objective_def == nullptr) return;
617 
618  const IntegerVariable objective_var = objective_def->objective_var;
619  const int64_t objective_value =
620  ComputeInnerObjective(*objective_or_null_, response);
621  debug_solution[objective_var] = objective_value;
622  debug_solution[NegationOf(objective_var)] = -objective_value;
623 #endif // __PORTABLE_PLATFORM__
624 }
625 
627  absl::MutexLock mutex_lock(&mutex_);
628  SetStatsFromModelInternal(model);
629 }
630 
631 void SharedResponseManager::SetStatsFromModelInternal(Model* model) {
632  if (model == nullptr) return;
633  auto* sat_solver = model->GetOrCreate<SatSolver>();
634  auto* integer_trail = model->Get<IntegerTrail>();
635  best_response_.set_num_booleans(sat_solver->NumVariables());
636  best_response_.set_num_branches(sat_solver->num_branches());
637  best_response_.set_num_conflicts(sat_solver->num_failures());
638  best_response_.set_num_binary_propagations(sat_solver->num_propagations());
639  best_response_.set_num_restarts(sat_solver->num_restarts());
640  best_response_.set_num_integer_propagations(
641  integer_trail == nullptr ? 0 : integer_trail->num_enqueues());
642  auto* time_limit = model->Get<TimeLimit>();
643  best_response_.set_wall_time(time_limit->GetElapsedTime());
644  best_response_.set_deterministic_time(
645  time_limit->GetElapsedDeterministicTime());
646 
647  int64_t num_lp_iters = 0;
648  for (const LinearProgrammingConstraint* lp :
649  *model->GetOrCreate<LinearProgrammingConstraintCollection>()) {
650  num_lp_iters += lp->total_num_simplex_iterations();
651  }
652  best_response_.set_num_lp_iterations(num_lp_iters);
653 }
654 
656  absl::MutexLock mutex_lock(&mutex_);
657  return best_response_.status() == CpSolverStatus::OPTIMAL ||
658  best_response_.status() == CpSolverStatus::INFEASIBLE;
659 }
660 
661 std::string ExtractSubSolverName(const std::string& improvement_info) {
662  if (improvement_info.empty()) return "";
663 
664  // We assume the subsolver name is always first.
665  for (int i = 0; i < improvement_info.size(); ++i) {
666  if (!std::isalnum(improvement_info[i]) && improvement_info[i] != '_') {
667  return improvement_info.substr(0, i);
668  }
669  }
670 
671  return improvement_info;
672 }
673 
674 void SharedResponseManager::RegisterSolutionFound(
675  const std::string& improvement_info) {
676  if (improvement_info.empty()) return;
677  primal_improvements_count_[ExtractSubSolverName(improvement_info)]++;
678 }
679 
680 void SharedResponseManager::RegisterObjectiveBoundImprovement(
681  const std::string& improvement_info) {
682  if (improvement_info.empty() || improvement_info == "initial domain") return;
683  dual_improvements_count_[ExtractSubSolverName(improvement_info)]++;
684 }
685 
687  absl::MutexLock mutex_lock(&mutex_);
688  if (!primal_improvements_count_.empty()) {
689  SOLVER_LOG(logger_, "Solutions found per subsolver:");
690  for (const auto& entry : primal_improvements_count_) {
691  SOLVER_LOG(logger_, " '", entry.first, "': ", entry.second);
692  }
693  }
694  if (!dual_improvements_count_.empty()) {
695  SOLVER_LOG(logger_, "");
696  SOLVER_LOG(logger_, "Objective bounds found per subsolver:");
697  for (const auto& entry : dual_improvements_count_) {
698  SOLVER_LOG(logger_, " '", entry.first, "': ", entry.second);
699  }
700  }
701 }
702 
704  : num_variables_(model_proto.variables_size()),
705  model_proto_(model_proto),
706  lower_bounds_(num_variables_, std::numeric_limits<int64_t>::min()),
707  upper_bounds_(num_variables_, std::numeric_limits<int64_t>::max()),
708  synchronized_lower_bounds_(num_variables_,
709  std::numeric_limits<int64_t>::min()),
710  synchronized_upper_bounds_(num_variables_,
711  std::numeric_limits<int64_t>::max()) {
712  changed_variables_since_last_synchronize_.ClearAndResize(num_variables_);
713  for (int i = 0; i < num_variables_; ++i) {
714  lower_bounds_[i] = model_proto.variables(i).domain(0);
715  const int domain_size = model_proto.variables(i).domain_size();
716  upper_bounds_[i] = model_proto.variables(i).domain(domain_size - 1);
717  synchronized_lower_bounds_[i] = lower_bounds_[i];
718  synchronized_upper_bounds_[i] = upper_bounds_[i];
719  }
720 }
721 
723  const CpModelProto& model_proto, const std::string& worker_name,
724  const std::vector<int>& variables,
725  const std::vector<int64_t>& new_lower_bounds,
726  const std::vector<int64_t>& new_upper_bounds) {
727  CHECK_EQ(variables.size(), new_lower_bounds.size());
728  CHECK_EQ(variables.size(), new_upper_bounds.size());
729  int num_improvements = 0;
730 
731  absl::MutexLock mutex_lock(&mutex_);
732  for (int i = 0; i < variables.size(); ++i) {
733  const int var = variables[i];
734  if (var >= num_variables_) continue;
735  const int64_t old_lb = lower_bounds_[var];
736  const int64_t old_ub = upper_bounds_[var];
737  const int64_t new_lb = new_lower_bounds[i];
738  const int64_t new_ub = new_upper_bounds[i];
739  const bool changed_lb = new_lb > old_lb;
740  const bool changed_ub = new_ub < old_ub;
741  CHECK_GE(var, 0);
742  if (!changed_lb && !changed_ub) continue;
743 
744  if (changed_lb) {
745  lower_bounds_[var] = new_lb;
746  }
747  if (changed_ub) {
748  upper_bounds_[var] = new_ub;
749  }
750  changed_variables_since_last_synchronize_.Set(var);
751  num_improvements++;
752  }
753  // TODO(user): Display number of bound improvements cumulatively per
754  // workers at the end of the search.
755  if (num_improvements > 0) {
756  VLOG(2) << worker_name << " exports " << num_improvements
757  << " modifications";
758  }
759 }
760 
761 // TODO(user): Because we look at the non-synchronized and up to date bounds,
762 // this break determinism if two solution for the same subpart comes at the same
763 // time.
765  const std::vector<int64_t>& solution,
766  const std::vector<int>& variables_to_fix) {
767  absl::MutexLock mutex_lock(&mutex_);
768 
769  // Abort if incompatible. Note that we only check the position that we are
770  // about to fix. This should be enough. Otherwise we might never accept any
771  // solution because the base LNS solution was not the same in some of the
772  // variables that we fixed here.
773  for (const int var : variables_to_fix) {
774  const int64_t value = solution[var];
775  if (value < lower_bounds_[var] || value > upper_bounds_[var]) {
776  VLOG(1) << "Incompatibility in FixVariablesFromPartialSolution() "
777  << "var: " << var << " value: " << value << " bounds: ["
778  << lower_bounds_[var] << "," << upper_bounds_[var] << "]";
779  return;
780  }
781  }
782 
783  // Fix the variables.
784  for (const int var : variables_to_fix) {
785  const int64_t old_lb = lower_bounds_[var];
786  const int64_t old_ub = upper_bounds_[var];
787  const bool changed_lb = solution[var] > old_lb;
788  const bool changed_ub = solution[var] < old_ub;
789  if (!changed_lb && !changed_ub) continue;
790 
791  lower_bounds_[var] = solution[var];
792  upper_bounds_[var] = solution[var];
793  changed_variables_since_last_synchronize_.Set(var);
794  }
795 }
796 
798  absl::MutexLock mutex_lock(&mutex_);
799  for (const int var :
800  changed_variables_since_last_synchronize_.PositionsSetAtLeastOnce()) {
801  synchronized_lower_bounds_[var] = lower_bounds_[var];
802  synchronized_upper_bounds_[var] = upper_bounds_[var];
803  for (int j = 0; j < id_to_changed_variables_.size(); ++j) {
804  id_to_changed_variables_[j].Set(var);
805  }
806  }
807  changed_variables_since_last_synchronize_.ClearAll();
808 }
809 
811  absl::MutexLock mutex_lock(&mutex_);
812  const int id = id_to_changed_variables_.size();
813  id_to_changed_variables_.resize(id + 1);
814  id_to_changed_variables_[id].ClearAndResize(num_variables_);
815  for (int var = 0; var < num_variables_; ++var) {
816  const int64_t lb = model_proto_.variables(var).domain(0);
817  const int domain_size = model_proto_.variables(var).domain_size();
818  const int64_t ub = model_proto_.variables(var).domain(domain_size - 1);
819  if (lb != synchronized_lower_bounds_[var] ||
820  ub != synchronized_upper_bounds_[var]) {
821  id_to_changed_variables_[id].Set(var);
822  }
823  }
824  return id;
825 }
826 
828  int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
829  std::vector<int64_t>* new_upper_bounds) {
830  variables->clear();
831  new_lower_bounds->clear();
832  new_upper_bounds->clear();
833 
834  absl::MutexLock mutex_lock(&mutex_);
835  for (const int var : id_to_changed_variables_[id].PositionsSetAtLeastOnce()) {
836  variables->push_back(var);
837  new_lower_bounds->push_back(synchronized_lower_bounds_[var]);
838  new_upper_bounds->push_back(synchronized_upper_bounds_[var]);
839  }
840  id_to_changed_variables_[id].ClearAll();
841 }
842 
843 } // namespace sat
844 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
::operations_research::sat::CpSolverSolution * add_additional_solutions()
int64_t min
Definition: alldiff_cst.cc:139
double Get() const
Definition: timer.h:45
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define CHECK_GE(val1, val2)
Definition: base/logging.h:706
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
void AddNewSolution(const std::vector< double > &lp_solution)
IntegerVariable NumIntegerVariables() const
Definition: integer.h:638
ModelSharedTimeLimit * time_limit
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
#define CHECK_OK(x)
Definition: base/logging.h:44
absl::Status SetTextProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
Definition: base/file.cc:285
void AddSolutionPostprocessor(std::function< void(std::vector< int64_t > *)> postprocessor)
#define VLOG(verboselevel)
Definition: base/logging.h:983
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
int64_t ScaleInnerObjectiveValue(const CpObjectiveProto &proto, int64_t value)
void NewSolution(const CpSolverResponse &response, Model *model)
MPCallback * callback
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
#define LOG(severity)
Definition: base/logging.h:420
GRBmodel * model
void NewLPSolution(std::vector< double > lp_solution)
::PROTOBUF_NAMESPACE_ID::RepeatedField< int64_t > * mutable_solution()
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int64_t Max() const
Returns the max value of the domain.
int NumVariables(const VariablesProto &variables)
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
std::string ExtractSubSolverName(const std::string &improvement_info)
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
int64_t Min() const
Returns the min value of the domain.
CpSolverResponse GetResponse(bool full_response=true)
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
int Defaults()
Definition: base/file.h:119
int AddSolutionCallback(std::function< void(const CpSolverResponse &)> callback)
void InitializeObjective(const CpModelProto &cp_model)
SharedBoundsManager(const CpModelProto &model_proto)
const ::operations_research::sat::CpObjectiveProto & objective() const
void AddInternal(const Solution &solution) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_)
ABSL_FLAG(bool, cp_model_dump_solutions, false, "DEBUG ONLY. If true, all the intermediate solution will be dumped " "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.")
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:894
SharedResponseManager * response
std::string message
Definition: trace.cc:398
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
::operations_research::sat::CpSolverStatus status() const
CpModelProto const * model_proto
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:30
void FixVariablesFromPartialSolution(const std::vector< int64_t > &solution, const std::vector< int > &variables_to_fix)
We call domain any subset of Int64 = [kint64min, kint64max].
::PROTOBUF_NAMESPACE_ID::RepeatedField< int64_t > * mutable_values()
void ReportPotentialNewBounds(const CpModelProto &model_proto, const std::string &worker_name, const std::vector< int > &variables, const std::vector< int64_t > &new_lower_bounds, const std::vector< int64_t > &new_upper_bounds)
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
absl::Status GetTextProto(const absl::string_view &filename, google::protobuf::Message *proto, int flags)
Definition: base/file.cc:275
void AddResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:892
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
Collection of objects used to extend the Constraint Solver library.
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, const CpSolverResponse &response)
SatParameters parameters
double GetElapsedDeterministicTime() const
Definition: time_limit.h:385
IntVar * var
Definition: expr_array.cc:1874
void SetGapLimitsFromParameters(const SatParameters &parameters)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool IsEmpty() const
Returns true if this is the empty set.
int64_t value
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:893
const int INFO
Definition: log_severity.h:31
void NewRelaxationSolution(const CpSolverResponse &response)