OR-Tools  9.1
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  : enumerate_all_solutions_(
107  model->GetOrCreate<SatParameters>()->enumerate_all_solutions()),
108  wall_timer_(*model->GetOrCreate<WallTimer>()),
109  shared_time_limit_(model->GetOrCreate<ModelSharedTimeLimit>()),
110  solutions_(model->GetOrCreate<SatParameters>()->solution_pool_size()),
111  logger_(model->GetOrCreate<SolverLogger>()) {}
112 
113 namespace {
114 
115 std::string ProgressMessage(const std::string& event_or_solution_count,
116  double time_in_seconds, double obj_best,
117  double obj_lb, double obj_ub,
118  const std::string& solution_info) {
119  const std::string obj_next =
120  absl::StrFormat("next:[%.9g,%.9g]", obj_lb, obj_ub);
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  if (cp_model.has_objective()) {
137  objective_or_null_ = &cp_model.objective();
138  const Domain domain = ReadDomainFromProto(cp_model.objective());
139  if (!domain.IsEmpty()) {
140  UpdateInnerObjectiveBounds("initial_domain", IntegerValue(domain.Min()),
141  IntegerValue(domain.Max()));
142  }
143  } else {
144  objective_or_null_ = nullptr;
145  }
146 }
147 
149  absl::MutexLock mutex_lock(&mutex_);
150  update_integral_on_each_change_ = set;
151 }
152 
154  absl::MutexLock mutex_lock(&mutex_);
155  UpdatePrimalIntegralInternal();
156 }
157 
158 void SharedResponseManager::UpdatePrimalIntegralInternal() {
159  if (objective_or_null_ == nullptr) return;
160 
161  const double current_time = shared_time_limit_->GetElapsedDeterministicTime();
162  const double time_delta = current_time - last_primal_integral_time_stamp_;
163 
164  // We use the log of the absolute objective gap.
165  //
166  // Using the log should count no solution as just log(2*64) = 18, and
167  // otherwise just compare order of magnitude which seems nice. Also, It is
168  // more easy to compare the primal integral with the total time.
169  const CpObjectiveProto& obj = *objective_or_null_;
170  const double factor =
171  obj.scaling_factor() != 0.0 ? std::abs(obj.scaling_factor()) : 1.0;
172  const double bounds_delta = std::log(1 + factor * last_absolute_gap_);
173  primal_integral_ += time_delta * bounds_delta;
174 
175  // Update with new value.
176  last_primal_integral_time_stamp_ = current_time;
177  last_absolute_gap_ =
178  std::max(0.0, static_cast<double>(inner_objective_upper_bound_) -
179  static_cast<double>(inner_objective_lower_bound_));
180 }
181 
183  const SatParameters& parameters) {
184  absl::MutexLock mutex_lock(&mutex_);
185  if (objective_or_null_ == nullptr) return;
186  absolute_gap_limit_ = parameters.absolute_gap_limit();
187  relative_gap_limit_ = parameters.relative_gap_limit();
188 }
189 
190 void SharedResponseManager::TestGapLimitsIfNeeded() {
191  // This is called on each internal limit change, so it is a good place to
192  // update the integral. Note that this is not called at the end of the search
193  // though.
194  if (update_integral_on_each_change_) UpdatePrimalIntegralInternal();
195 
196  if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0) return;
197  if (best_solution_objective_value_ >= kMaxIntegerValue) return;
198  if (inner_objective_lower_bound_ <= kMinIntegerValue) return;
199 
200  const CpObjectiveProto& obj = *objective_or_null_;
201  const double user_best =
202  ScaleObjectiveValue(obj, best_solution_objective_value_);
203  const double user_bound =
204  ScaleObjectiveValue(obj, inner_objective_lower_bound_);
205  const double gap = std::abs(user_best - user_bound);
206  if (gap <= absolute_gap_limit_) {
207  SOLVER_LOG(logger_, "Absolute gap limit of ", absolute_gap_limit_,
208  " reached.");
209  best_response_.set_status(CpSolverStatus::OPTIMAL);
210 
211  // Note(user): Some code path in single-thread assumes that the problem
212  // can only be solved when they have proven infeasibility and do not check
213  // the ProblemIsSolved() method. So we force a stop here.
214  shared_time_limit_->Stop();
215  }
216  if (gap / std::max(1.0, std::abs(user_best)) < relative_gap_limit_) {
217  SOLVER_LOG(logger_, "Relative gap limit of ", relative_gap_limit_,
218  " reached.");
219  best_response_.set_status(CpSolverStatus::OPTIMAL);
220 
221  // Same as above.
222  shared_time_limit_->Stop();
223  }
224 }
225 
227  const std::string& update_info, IntegerValue lb, IntegerValue ub) {
228  absl::MutexLock mutex_lock(&mutex_);
229  CHECK(objective_or_null_ != nullptr);
230 
231  // The problem is already solved!
232  //
233  // TODO(user): A thread might not be notified right away that the new bounds
234  // that it is pushing make the problem infeasible. Fix that. For now we just
235  // abort early here to avoid logging the "#Done" message multiple times.
236  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
237  return;
238  }
239 
240  const bool change =
241  (lb > inner_objective_lower_bound_ || ub < inner_objective_upper_bound_);
242  if (lb > inner_objective_lower_bound_) {
243  // When the improving problem is infeasible, it is possible to report
244  // arbitrary high inner_objective_lower_bound_. We make sure it never cross
245  // the current best solution, so that we always report globablly valid lower
246  // bound.
247  DCHECK_LE(inner_objective_upper_bound_, best_solution_objective_value_);
248  inner_objective_lower_bound_ =
249  std::min(best_solution_objective_value_, lb.value());
250  }
251  if (ub < inner_objective_upper_bound_) {
252  inner_objective_upper_bound_ = ub.value();
253  }
254  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
255  if (best_response_.status() == CpSolverStatus::FEASIBLE ||
256  best_response_.status() == CpSolverStatus::OPTIMAL) {
257  best_response_.set_status(CpSolverStatus::OPTIMAL);
258  } else {
259  best_response_.set_status(CpSolverStatus::INFEASIBLE);
260  }
261  if (update_integral_on_each_change_) UpdatePrimalIntegralInternal();
262  SOLVER_LOG(logger_,
263  SatProgressMessage("Done", wall_timer_.Get(), update_info));
264  return;
265  }
266  if (logger_->LoggingIsEnabled() && change) {
267  const CpObjectiveProto& obj = *objective_or_null_;
268  const double best =
269  ScaleObjectiveValue(obj, best_solution_objective_value_);
270  double new_lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
271  double new_ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
272  if (obj.scaling_factor() < 0) {
273  std::swap(new_lb, new_ub);
274  }
275  RegisterObjectiveBoundImprovement(update_info);
276  SOLVER_LOG(logger_, ProgressMessage("Bound", wall_timer_.Get(), best,
277  new_lb, new_ub, update_info));
278  }
279  if (change) TestGapLimitsIfNeeded();
280 }
281 
282 // Invariant: the status always start at UNKNOWN and can only evolve as follow:
283 // UNKNOWN -> FEASIBLE -> OPTIMAL
284 // UNKNOWN -> INFEASIBLE
286  const std::string& worker_info) {
287  absl::MutexLock mutex_lock(&mutex_);
288  if (best_response_.status() == CpSolverStatus::FEASIBLE ||
289  best_response_.status() == CpSolverStatus::OPTIMAL) {
290  // We also use this status to indicate that we enumerated all solutions to
291  // a feasible problem.
292  best_response_.set_status(CpSolverStatus::OPTIMAL);
293  if (objective_or_null_ == nullptr) {
294  best_response_.set_all_solutions_were_found(true);
295  }
296 
297  // We just proved that the best solution cannot be improved uppon, so we
298  // have a new lower bound.
299  inner_objective_lower_bound_ = best_solution_objective_value_;
300  if (update_integral_on_each_change_) UpdatePrimalIntegralInternal();
301  } else {
302  CHECK_EQ(num_solutions_, 0);
303  best_response_.set_status(CpSolverStatus::INFEASIBLE);
304  }
305  SOLVER_LOG(logger_,
306  SatProgressMessage("Done", wall_timer_.Get(), worker_info));
307 }
308 
309 void SharedResponseManager::AddUnsatCore(const std::vector<int>& core) {
310  absl::MutexLock mutex_lock(&mutex_);
311  best_response_.clear_sufficient_assumptions_for_infeasibility();
312  for (const int ref : core) {
313  best_response_.add_sufficient_assumptions_for_infeasibility(ref);
314  }
315 }
316 
318  absl::MutexLock mutex_lock(&mutex_);
319  return IntegerValue(inner_objective_lower_bound_);
320 }
321 
323  absl::MutexLock mutex_lock(&mutex_);
324  return IntegerValue(inner_objective_upper_bound_);
325 }
326 
328  absl::MutexLock mutex_lock(&mutex_);
329  synchronized_inner_objective_lower_bound_ =
330  IntegerValue(inner_objective_lower_bound_);
331  synchronized_inner_objective_upper_bound_ =
332  IntegerValue(inner_objective_upper_bound_);
333 }
334 
336  absl::MutexLock mutex_lock(&mutex_);
337  return synchronized_inner_objective_lower_bound_;
338 }
339 
341  absl::MutexLock mutex_lock(&mutex_);
342  return synchronized_inner_objective_upper_bound_;
343 }
344 
346  absl::MutexLock mutex_lock(&mutex_);
347  return IntegerValue(best_solution_objective_value_);
348 }
349 
351  absl::MutexLock mutex_lock(&mutex_);
352  return primal_integral_;
353 }
354 
356  std::function<void(CpSolverResponse*)> postprocessor) {
357  absl::MutexLock mutex_lock(&mutex_);
358  postprocessors_.push_back(postprocessor);
359 }
360 
362  std::function<void(CpSolverResponse*)> postprocessor) {
363  absl::MutexLock mutex_lock(&mutex_);
364  final_postprocessors_.push_back(postprocessor);
365 }
366 
368  std::function<void(const CpSolverResponse&)> callback) {
369  absl::MutexLock mutex_lock(&mutex_);
370  const int id = next_callback_id_++;
371  callbacks_.emplace_back(id, std::move(callback));
372  return id;
373 }
374 
376  absl::MutexLock mutex_lock(&mutex_);
377  for (int i = 0; i < callbacks_.size(); ++i) {
378  if (callbacks_[i].first == callback_id) {
379  callbacks_.erase(callbacks_.begin() + i);
380  return;
381  }
382  }
383  LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
384 }
385 
387  absl::MutexLock mutex_lock(&mutex_);
388  FillObjectiveValuesInBestResponse();
389 
390  // We need to copy the response before we postsolve it.
391  CpSolverResponse result = best_response_;
392  for (int i = postprocessors_.size(); --i >= 0;) {
393  postprocessors_[i](&result);
394  }
395  if (full_response) {
396  for (int i = final_postprocessors_.size(); --i >= 0;) {
397  final_postprocessors_[i](&result);
398  }
399  }
400  return result;
401 }
402 
403 void SharedResponseManager::FillObjectiveValuesInBestResponse() {
404  if (objective_or_null_ == nullptr) return;
405  const CpObjectiveProto& obj = *objective_or_null_;
406 
407  if (best_response_.status() == CpSolverStatus::INFEASIBLE) {
408  best_response_.clear_objective_value();
409  best_response_.clear_best_objective_bound();
410  return;
411  }
412 
413  // Set the objective value.
414  // If we don't have any solution, we use our inner bound.
415  if (best_response_.status() == CpSolverStatus::UNKNOWN) {
416  best_response_.set_objective_value(
417  ScaleObjectiveValue(obj, inner_objective_upper_bound_));
418  } else {
419  best_response_.set_objective_value(
420  ScaleObjectiveValue(obj, best_solution_objective_value_));
421  }
422 
423  // Update the best bound in the response.
424  best_response_.set_best_objective_bound(
425  ScaleObjectiveValue(obj, inner_objective_lower_bound_));
426 
427  // Update the primal integral.
428  best_response_.set_primal_integral(primal_integral_);
429 }
430 
432  Model* model) {
433  absl::MutexLock mutex_lock(&mutex_);
434 
435  if (objective_or_null_ != nullptr) {
436  const int64_t objective_value =
437  ComputeInnerObjective(*objective_or_null_, response);
438 
439  // Add this solution to the pool, even if it is not improving.
440  if (!response.solution().empty()) {
442  solution.variable_values.assign(response.solution().begin(),
443  response.solution().end());
444  solution.rank = objective_value;
445  solutions_.Add(solution);
446  }
447 
448  // Ignore any non-strictly improving solution.
449  if (objective_value > inner_objective_upper_bound_) return;
450 
451  // Our inner_objective_lower_bound_ should be a globaly valid bound, until
452  // the problem become infeasible (i.e the lb > ub) in which case the bound
453  // is no longer globally valid. Here, because we have a strictly improving
454  // solution, we shouldn't be in the infeasible setting yet.
455  DCHECK_GE(objective_value, inner_objective_lower_bound_);
456 
457  DCHECK_LT(objective_value, best_solution_objective_value_);
458  best_solution_objective_value_ = objective_value;
459 
460  // Update the new bound.
461  inner_objective_upper_bound_ = objective_value - 1;
462  }
463 
464  // Note that the objective will be filled by
465  // FillObjectiveValuesInBestResponse().
466  if (objective_or_null_ == nullptr && !enumerate_all_solutions_) {
467  best_response_.set_status(CpSolverStatus::OPTIMAL);
468  } else {
469  best_response_.set_status(CpSolverStatus::FEASIBLE);
470  }
471 
472  best_response_.set_solution_info(response.solution_info());
473  *best_response_.mutable_solution() = response.solution();
474  *best_response_.mutable_solution_lower_bounds() =
475  response.solution_lower_bounds();
476  *best_response_.mutable_solution_upper_bounds() =
477  response.solution_upper_bounds();
478 
479  // Mark model as OPTIMAL if the inner bound crossed.
480  if (objective_or_null_ != nullptr &&
481  inner_objective_lower_bound_ > inner_objective_upper_bound_) {
482  best_response_.set_status(CpSolverStatus::OPTIMAL);
483  }
484 
485  // Logging.
486  ++num_solutions_;
487  if (logger_->LoggingIsEnabled()) {
488  std::string solution_info = response.solution_info();
489  if (model != nullptr) {
490  const int64_t num_bool = model->Get<Trail>()->NumVariables();
491  const int64_t num_fixed = model->Get<SatSolver>()->NumFixedVariables();
492  absl::StrAppend(&solution_info, " fixed_bools:", num_fixed, "/",
493  num_bool);
494  }
495 
496  if (objective_or_null_ != nullptr) {
497  const CpObjectiveProto& obj = *objective_or_null_;
498  const double best =
499  ScaleObjectiveValue(obj, best_solution_objective_value_);
500  double lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
501  double ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
502  if (obj.scaling_factor() < 0) {
503  std::swap(lb, ub);
504  }
505  RegisterSolutionFound(solution_info);
506  SOLVER_LOG(logger_, ProgressMessage(absl::StrCat(num_solutions_),
507  wall_timer_.Get(), best, lb, ub,
508  solution_info));
509  } else {
510  SOLVER_LOG(logger_, SatProgressMessage(absl::StrCat(num_solutions_),
511  wall_timer_.Get(), solution_info));
512  }
513  }
514 
515  // Call callbacks.
516  // Note that we cannot call function that try to get the mutex_ here.
517  TestGapLimitsIfNeeded();
518  if (!callbacks_.empty()) {
519  FillObjectiveValuesInBestResponse();
520  SetStatsFromModelInternal(model);
521 
522  // We need to copy the response before we postsolve it.
523  CpSolverResponse copy = best_response_;
524  for (int i = postprocessors_.size(); --i >= 0;) {
525  postprocessors_[i](&copy);
526  }
527  for (const auto& pair : callbacks_) {
528  pair.second(copy);
529  }
530  }
531 
532 #if !defined(__PORTABLE_PLATFORM__)
533  // We protect solution dumping with log_updates as LNS subsolvers share
534  // another solution manager, and we do not want to dump those.
535  if (absl::GetFlag(FLAGS_cp_model_dump_solutions)) {
536  const std::string file =
537  absl::StrCat(dump_prefix_, "solution_", num_solutions_, ".pbtxt");
538  LOG(INFO) << "Dumping solution to '" << file << "'.";
539  CHECK_OK(file::SetTextProto(file, best_response_, file::Defaults()));
540  }
541 #endif // __PORTABLE_PLATFORM__
542 }
543 
545 #if !defined(__PORTABLE_PLATFORM__)
546  if (absl::GetFlag(FLAGS_cp_model_load_debug_solution).empty()) return;
547  if (model->Get<DebugSolution>() != nullptr) return; // Already loaded.
548 
550  LOG(INFO) << "Reading solution from '"
551  << absl::GetFlag(FLAGS_cp_model_load_debug_solution) << "'.";
552  CHECK_OK(file::GetTextProto(absl::GetFlag(FLAGS_cp_model_load_debug_solution),
553  &response, file::Defaults()));
554 
555  const auto& mapping = *model->GetOrCreate<CpModelMapping>();
556  auto& debug_solution = *model->GetOrCreate<DebugSolution>();
557  debug_solution.resize(
558  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value());
559  for (int i = 0; i < response.solution().size(); ++i) {
560  if (!mapping.IsInteger(i)) continue;
561  const IntegerVariable var = mapping.Integer(i);
562  debug_solution[var] = response.solution(i);
563  debug_solution[NegationOf(var)] = -response.solution(i);
564  }
565 
566  // The objective variable is usually not part of the proto, but it is still
567  // nice to have it, so we recompute it here.
568  auto* objective_def = model->Get<ObjectiveDefinition>();
569  if (objective_def == nullptr) return;
570 
571  const IntegerVariable objective_var = objective_def->objective_var;
572  const int64_t objective_value =
573  ComputeInnerObjective(*objective_or_null_, response);
574  debug_solution[objective_var] = objective_value;
575  debug_solution[NegationOf(objective_var)] = -objective_value;
576 #endif // __PORTABLE_PLATFORM__
577 }
578 
580  absl::MutexLock mutex_lock(&mutex_);
581  SetStatsFromModelInternal(model);
582 }
583 
584 void SharedResponseManager::SetStatsFromModelInternal(Model* model) {
585  if (model == nullptr) return;
586  auto* sat_solver = model->GetOrCreate<SatSolver>();
587  auto* integer_trail = model->Get<IntegerTrail>();
588  best_response_.set_num_booleans(sat_solver->NumVariables());
589  best_response_.set_num_branches(sat_solver->num_branches());
590  best_response_.set_num_conflicts(sat_solver->num_failures());
591  best_response_.set_num_binary_propagations(sat_solver->num_propagations());
592  best_response_.set_num_restarts(sat_solver->num_restarts());
593  best_response_.set_num_integer_propagations(
594  integer_trail == nullptr ? 0 : integer_trail->num_enqueues());
595  auto* time_limit = model->Get<TimeLimit>();
596  best_response_.set_wall_time(time_limit->GetElapsedTime());
597  best_response_.set_deterministic_time(
598  time_limit->GetElapsedDeterministicTime());
599 
600  int64_t num_lp_iters = 0;
601  for (const LinearProgrammingConstraint* lp :
602  *model->GetOrCreate<LinearProgrammingConstraintCollection>()) {
603  num_lp_iters += lp->total_num_simplex_iterations();
604  }
605  best_response_.set_num_lp_iterations(num_lp_iters);
606 }
607 
609  absl::MutexLock mutex_lock(&mutex_);
610  return best_response_.status() == CpSolverStatus::OPTIMAL ||
611  best_response_.status() == CpSolverStatus::INFEASIBLE;
612 }
613 
614 std::string ExtractSubSolverName(const std::string& improvement_info) {
615  if (improvement_info.empty()) return "";
616 
617  // We assume the subsolver name is always first.
618  for (int i = 0; i < improvement_info.size(); ++i) {
619  if (!std::isalnum(improvement_info[i]) && improvement_info[i] != '_') {
620  return improvement_info.substr(0, i);
621  }
622  }
623 
624  return improvement_info;
625 }
626 
627 void SharedResponseManager::RegisterSolutionFound(
628  const std::string& improvement_info) {
629  if (improvement_info.empty()) return;
630  primal_improvements_count_[ExtractSubSolverName(improvement_info)]++;
631 }
632 
633 void SharedResponseManager::RegisterObjectiveBoundImprovement(
634  const std::string& improvement_info) {
635  if (improvement_info.empty() || improvement_info == "initial domain") return;
636  dual_improvements_count_[ExtractSubSolverName(improvement_info)]++;
637 }
638 
640  absl::MutexLock mutex_lock(&mutex_);
641  if (!primal_improvements_count_.empty()) {
642  SOLVER_LOG(logger_, "Solutions found per subsolver:");
643  for (const auto& entry : primal_improvements_count_) {
644  SOLVER_LOG(logger_, " '", entry.first, "': ", entry.second);
645  }
646  }
647  if (!dual_improvements_count_.empty()) {
648  SOLVER_LOG(logger_, "");
649  SOLVER_LOG(logger_, "Objective bounds found per subsolver:");
650  for (const auto& entry : dual_improvements_count_) {
651  SOLVER_LOG(logger_, " '", entry.first, "': ", entry.second);
652  }
653  }
654 }
655 
657  : num_variables_(model_proto.variables_size()),
658  model_proto_(model_proto),
659  lower_bounds_(num_variables_, std::numeric_limits<int64_t>::min()),
660  upper_bounds_(num_variables_, std::numeric_limits<int64_t>::max()),
661  synchronized_lower_bounds_(num_variables_,
662  std::numeric_limits<int64_t>::min()),
663  synchronized_upper_bounds_(num_variables_,
664  std::numeric_limits<int64_t>::max()) {
665  changed_variables_since_last_synchronize_.ClearAndResize(num_variables_);
666  for (int i = 0; i < num_variables_; ++i) {
667  lower_bounds_[i] = model_proto.variables(i).domain(0);
668  const int domain_size = model_proto.variables(i).domain_size();
669  upper_bounds_[i] = model_proto.variables(i).domain(domain_size - 1);
670  synchronized_lower_bounds_[i] = lower_bounds_[i];
671  synchronized_upper_bounds_[i] = upper_bounds_[i];
672  }
673 }
674 
676  const CpModelProto& model_proto, const std::string& worker_name,
677  const std::vector<int>& variables,
678  const std::vector<int64_t>& new_lower_bounds,
679  const std::vector<int64_t>& new_upper_bounds) {
680  CHECK_EQ(variables.size(), new_lower_bounds.size());
681  CHECK_EQ(variables.size(), new_upper_bounds.size());
682  int num_improvements = 0;
683 
684  absl::MutexLock mutex_lock(&mutex_);
685  for (int i = 0; i < variables.size(); ++i) {
686  const int var = variables[i];
687  if (var >= num_variables_) continue;
688  const int64_t old_lb = lower_bounds_[var];
689  const int64_t old_ub = upper_bounds_[var];
690  const int64_t new_lb = new_lower_bounds[i];
691  const int64_t new_ub = new_upper_bounds[i];
692  const bool changed_lb = new_lb > old_lb;
693  const bool changed_ub = new_ub < old_ub;
694  CHECK_GE(var, 0);
695  if (!changed_lb && !changed_ub) continue;
696 
697  if (changed_lb) {
698  lower_bounds_[var] = new_lb;
699  }
700  if (changed_ub) {
701  upper_bounds_[var] = new_ub;
702  }
703  changed_variables_since_last_synchronize_.Set(var);
704  num_improvements++;
705  }
706  // TODO(user): Display number of bound improvements cumulatively per
707  // workers at the end of the search.
708  if (num_improvements > 0) {
709  VLOG(2) << worker_name << " exports " << num_improvements
710  << " modifications";
711  }
712 }
713 
715  absl::MutexLock mutex_lock(&mutex_);
716  for (const int var :
717  changed_variables_since_last_synchronize_.PositionsSetAtLeastOnce()) {
718  synchronized_lower_bounds_[var] = lower_bounds_[var];
719  synchronized_upper_bounds_[var] = upper_bounds_[var];
720  for (int j = 0; j < id_to_changed_variables_.size(); ++j) {
721  id_to_changed_variables_[j].Set(var);
722  }
723  }
724  changed_variables_since_last_synchronize_.ClearAll();
725 }
726 
728  absl::MutexLock mutex_lock(&mutex_);
729  const int id = id_to_changed_variables_.size();
730  id_to_changed_variables_.resize(id + 1);
731  id_to_changed_variables_[id].ClearAndResize(num_variables_);
732  for (int var = 0; var < num_variables_; ++var) {
733  const int64_t lb = model_proto_.variables(var).domain(0);
734  const int domain_size = model_proto_.variables(var).domain_size();
735  const int64_t ub = model_proto_.variables(var).domain(domain_size - 1);
736  if (lb != synchronized_lower_bounds_[var] ||
737  ub != synchronized_upper_bounds_[var]) {
738  id_to_changed_variables_[id].Set(var);
739  }
740  }
741  return id;
742 }
743 
745  int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
746  std::vector<int64_t>* new_upper_bounds) {
747  variables->clear();
748  new_lower_bounds->clear();
749  new_upper_bounds->clear();
750 
751  absl::MutexLock mutex_lock(&mutex_);
752  for (const int var : id_to_changed_variables_[id].PositionsSetAtLeastOnce()) {
753  variables->push_back(var);
754  new_lower_bounds->push_back(synchronized_lower_bounds_[var]);
755  new_upper_bounds->push_back(synchronized_upper_bounds_[var]);
756  }
757  id_to_changed_variables_[id].ClearAll();
758 }
759 
760 } // namespace sat
761 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
int64_t min
Definition: alldiff_cst.cc:139
double Get() const
Definition: timer.h:45
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:63
#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)
IntegerVariable NumIntegerVariables() const
Definition: integer.h:599
ModelSharedTimeLimit * time_limit
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
#define CHECK_OK(x)
Definition: base/logging.h:42
absl::Status SetTextProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
Definition: base/file.cc:285
#define VLOG(verboselevel)
Definition: base/logging.h:979
void AddFinalSolutionPostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
void NewSolution(const CpSolverResponse &response, Model *model)
MPCallback * callback
#define LOG(severity)
Definition: base/logging.h:416
GRBmodel * model
void NewLPSolution(std::vector< double > lp_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)
void AddSolutionPostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
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:890
SharedResponseManager * response
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
CpModelProto const * model_proto
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
We call domain any subset of Int64 = [kint64min, kint64max].
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
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:888
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:384
IntVar * var
Definition: expr_array.cc:1874
::PROTOBUF_NAMESPACE_ID::int64 domain(int index) const
Definition: cp_model.pb.h:6777
void SetGapLimitsFromParameters(const SatParameters &parameters)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool IsEmpty() const
Returns true if this is the empty set.
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
const int INFO
Definition: log_severity.h:31
void NewRelaxationSolution(const CpSolverResponse &response)