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