OR-Tools  9.6
synchronization.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <algorithm>
17 #include <cctype>
18 #include <cmath>
19 #include <cstdint>
20 #include <cstdlib>
21 #include <deque>
22 #include <functional>
23 #include <limits>
24 #include <string>
25 #include <utility>
26 #include <vector>
27 
28 #include "ortools/base/logging.h"
29 #include "ortools/base/timer.h"
30 #if !defined(__PORTABLE_PLATFORM__)
31 #include "ortools/base/helpers.h"
32 #include "ortools/base/options.h"
33 #endif // __PORTABLE_PLATFORM__
34 #include "absl/container/btree_map.h"
35 #include "absl/container/flat_hash_map.h"
36 #include "absl/container/flat_hash_set.h"
37 #include "absl/flags/flag.h"
38 #include "absl/status/status.h"
39 #include "absl/strings/str_cat.h"
40 #include "absl/strings/str_format.h"
41 #include "absl/strings/string_view.h"
42 #include "absl/synchronization/mutex.h"
43 #include "absl/time/clock.h"
44 #include "absl/time/time.h"
45 #include "ortools/sat/cp_model.pb.h"
47 #include "ortools/sat/integer.h"
48 #include "ortools/sat/model.h"
49 #include "ortools/sat/sat_base.h"
50 #include "ortools/sat/sat_parameters.pb.h"
51 #include "ortools/sat/sat_solver.h"
52 #include "ortools/sat/util.h"
53 #include "ortools/util/bitset.h"
54 #include "ortools/util/logging.h"
58 
59 ABSL_FLAG(bool, cp_model_dump_solutions, false,
60  "DEBUG ONLY. If true, all the intermediate solution will be dumped "
61  "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.");
62 
63 namespace operations_research {
64 namespace sat {
65 
67  absl::Span<const int64_t> solution_values,
68  IntegerValue inner_objective_value) {
69  // Note that the Add() method already applies mutex lock. So we don't need it
70  // here.
71  if (solution_values.empty()) return;
72 
73  // Add this solution to the pool.
75  solution.variable_values.assign(solution_values.begin(),
76  solution_values.end());
77  // For now we use the negated lower bound as the "internal objective" to
78  // prefer solution with an higher bound.
79  //
80  // Note: If the model doesn't have objective, the best_objective_bound is set
81  // to default value 0.
82  solution.rank = -inner_objective_value.value();
83 
84  Add(solution);
85 }
86 
88  std::vector<double> lp_solution) {
89  if (lp_solution.empty()) return;
90 
91  // Add this solution to the pool.
93  solution.variable_values = std::move(lp_solution);
94 
95  // We always prefer to keep the solution from the last synchronize batch.
96  absl::MutexLock mutex_lock(&mutex_);
97  solution.rank = -num_synchronization_;
98  AddInternal(solution);
99 }
100 
102  absl::MutexLock mutex_lock(&mutex_);
103  return !solutions_.empty();
104 }
105 
107  absl::MutexLock mutex_lock(&mutex_);
108  std::vector<double> solution;
109  if (solutions_.empty()) return solution;
110 
111  solution = std::move(solutions_.back());
112  solutions_.pop_back();
113  return solution;
114 }
115 
117  const std::vector<double>& lp_solution) {
118  absl::MutexLock mutex_lock(&mutex_);
119  solutions_.push_back(lp_solution);
120 }
121 
123  : parameters_(*model->GetOrCreate<SatParameters>()),
124  wall_timer_(*model->GetOrCreate<WallTimer>()),
125  shared_time_limit_(model->GetOrCreate<ModelSharedTimeLimit>()),
126  solutions_(parameters_.solution_pool_size()),
127  logger_(model->GetOrCreate<SolverLogger>()) {}
128 
129 namespace {
130 
131 std::string ProgressMessage(const std::string& event_or_solution_count,
132  double time_in_seconds, double obj_best,
133  double obj_lb, double obj_ub,
134  const std::string& solution_info) {
135  const std::string obj_next =
136  obj_lb <= obj_ub ? absl::StrFormat("next:[%.9g,%.9g]", obj_lb, obj_ub)
137  : "next:[]";
138  return absl::StrFormat("#%-5s %6.2fs best:%-5.9g %-15s %s",
139  event_or_solution_count, time_in_seconds, obj_best,
140  obj_next, solution_info);
141 }
142 
143 std::string SatProgressMessage(const std::string& event_or_solution_count,
144  double time_in_seconds,
145  const std::string& solution_info) {
146  return absl::StrFormat("#%-5s %6.2fs %s", event_or_solution_count,
147  time_in_seconds, solution_info);
148 }
149 
150 } // namespace
151 
152 void FillSolveStatsInResponse(Model* model, CpSolverResponse* response) {
153  if (model == nullptr) return;
154  auto* sat_solver = model->GetOrCreate<SatSolver>();
155  auto* integer_trail = model->Get<IntegerTrail>();
156  response->set_num_booleans(sat_solver->NumVariables());
157  response->set_num_branches(sat_solver->num_branches());
158  response->set_num_conflicts(sat_solver->num_failures());
159  response->set_num_binary_propagations(sat_solver->num_propagations());
160  response->set_num_restarts(sat_solver->num_restarts());
161 
162  response->set_num_integers(
163  integer_trail == nullptr
164  ? 0
165  : integer_trail->NumIntegerVariables().value() / 2);
166  response->set_num_integer_propagations(
167  integer_trail == nullptr ? 0 : integer_trail->num_enqueues());
168 
169  // TODO(user): find a way to clear all stats fields that might be set by
170  // one of the callback.
171  response->set_num_lp_iterations(0);
172  for (const auto& set_stats :
174  set_stats(response);
175  }
176 }
177 
178 void SharedResponseManager::LogMessage(const std::string& prefix,
179  const std::string& message) {
180  absl::MutexLock mutex_lock(&mutex_);
181  SOLVER_LOG(logger_, absl::StrFormat("#%-5s %6.2fs %s", prefix,
182  wall_timer_.Get(), message));
183 }
184 
185 void SharedResponseManager::LogPeriodicMessage(const std::string& prefix,
186  const std::string& message,
187  double frequency_seconds,
188  absl::Time* last_logging_time) {
189  if (frequency_seconds < 0.0 || last_logging_time == nullptr) return;
190  const absl::Time now = absl::Now();
191  if (now - *last_logging_time < absl::Seconds(frequency_seconds)) {
192  return;
193  }
194 
195  absl::MutexLock mutex_lock(&mutex_);
196  *last_logging_time = now;
197  SOLVER_LOG(logger_, absl::StrFormat("#%-5s %6.2fs %s", prefix,
198  wall_timer_.Get(), message));
199 }
200 
201 void SharedResponseManager::InitializeObjective(const CpModelProto& cp_model) {
202  if (cp_model.has_objective()) {
203  objective_or_null_ = &cp_model.objective();
204  const Domain domain = ReadDomainFromProto(cp_model.objective());
205  if (!domain.IsEmpty()) {
206  UpdateInnerObjectiveBounds("initial_domain", IntegerValue(domain.Min()),
207  IntegerValue(domain.Max()));
208  }
209  } else {
210  objective_or_null_ = nullptr;
211  }
212 }
213 
214 void SharedResponseManager::SetSynchronizationMode(bool always_synchronize) {
215  absl::MutexLock mutex_lock(&mutex_);
216  always_synchronize_ = always_synchronize;
217 }
218 
220  absl::MutexLock mutex_lock(&mutex_);
221  update_integral_on_each_change_ = set;
222 }
223 
225  absl::MutexLock mutex_lock(&mutex_);
226  UpdateGapIntegralInternal();
227 }
228 
229 void SharedResponseManager::UpdateGapIntegralInternal() {
230  if (objective_or_null_ == nullptr) return;
231 
232  const double current_time = shared_time_limit_->GetElapsedDeterministicTime();
233  const double time_delta = current_time - last_gap_integral_time_stamp_;
234 
235  // We use the log of the absolute objective gap.
236  //
237  // Using the log should count no solution as just log(2*64) = 18, and
238  // otherwise just compare order of magnitude which seems nice. Also, It is
239  // more easy to compare the primal integral with the total time.
240  const CpObjectiveProto& obj = *objective_or_null_;
241  const double factor =
242  obj.scaling_factor() != 0.0 ? std::abs(obj.scaling_factor()) : 1.0;
243  const double bounds_delta = std::log(1 + factor * last_absolute_gap_);
244  gap_integral_ += time_delta * bounds_delta;
245 
246  // Update with new value.
247  last_gap_integral_time_stamp_ = current_time;
248  last_absolute_gap_ =
249  std::max(0.0, static_cast<double>(inner_objective_upper_bound_) -
250  static_cast<double>(inner_objective_lower_bound_));
251 }
252 
254  const SatParameters& parameters) {
255  absl::MutexLock mutex_lock(&mutex_);
256  if (objective_or_null_ == nullptr) return;
257  absolute_gap_limit_ = parameters.absolute_gap_limit();
258  relative_gap_limit_ = parameters.relative_gap_limit();
259 }
260 
261 void SharedResponseManager::TestGapLimitsIfNeeded() {
262  // This is called on each internal limit change, so it is a good place to
263  // update the integral. Note that this is not called at the end of the search
264  // though.
265  if (update_integral_on_each_change_) UpdateGapIntegralInternal();
266 
267  // Abort if there is not limit set, if the gap is not defined or if we already
268  // proved optimality or infeasibility.
269  if (absolute_gap_limit_ == 0 && relative_gap_limit_ == 0) return;
270  if (best_solution_objective_value_ >= kMaxIntegerValue) return;
271  if (inner_objective_lower_bound_ <= kMinIntegerValue) return;
272  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) return;
273 
274  const CpObjectiveProto& obj = *objective_or_null_;
275  const double user_best =
276  ScaleObjectiveValue(obj, best_solution_objective_value_);
277  const double user_bound =
278  ScaleObjectiveValue(obj, inner_objective_lower_bound_);
279  const double gap = std::abs(user_best - user_bound);
280  if (gap <= absolute_gap_limit_) {
281  SOLVER_LOG(logger_, "Absolute gap limit of ", absolute_gap_limit_,
282  " reached.");
283  UpdateBestStatus(CpSolverStatus::OPTIMAL);
284 
285  // Note(user): Some code path in single-thread assumes that the problem
286  // can only be solved when they have proven infeasibility and do not check
287  // the ProblemIsSolved() method. So we force a stop here.
288  shared_time_limit_->Stop();
289  }
290  if (gap / std::max(1.0, std::abs(user_best)) < relative_gap_limit_) {
291  SOLVER_LOG(logger_, "Relative gap limit of ", relative_gap_limit_,
292  " reached.");
293  UpdateBestStatus(CpSolverStatus::OPTIMAL);
294 
295  // Same as above.
296  shared_time_limit_->Stop();
297  }
298 }
299 
301  const std::string& update_info, IntegerValue lb, IntegerValue ub) {
302  absl::MutexLock mutex_lock(&mutex_);
303  CHECK(objective_or_null_ != nullptr);
304 
305  // The problem is already solved!
306  //
307  // TODO(user): A thread might not be notified right away that the new bounds
308  // that it is pushing make the problem infeasible. Fix that. For now we just
309  // abort early here to avoid logging the "#Done" message multiple times.
310  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
311  return;
312  }
313 
314  const bool change =
315  (lb > inner_objective_lower_bound_ || ub < inner_objective_upper_bound_);
316  if (lb > inner_objective_lower_bound_) {
317  // When the improving problem is infeasible, it is possible to report
318  // arbitrary high inner_objective_lower_bound_. We make sure it never cross
319  // the current best solution, so that we always report globablly valid lower
320  // bound.
321  DCHECK_LE(inner_objective_upper_bound_, best_solution_objective_value_);
322  inner_objective_lower_bound_ =
323  std::min(best_solution_objective_value_, lb.value());
324  }
325  if (ub < inner_objective_upper_bound_) {
326  inner_objective_upper_bound_ = ub.value();
327  }
328  if (inner_objective_lower_bound_ > inner_objective_upper_bound_) {
329  if (best_status_ == CpSolverStatus::FEASIBLE ||
330  best_status_ == CpSolverStatus::OPTIMAL) {
331  UpdateBestStatus(CpSolverStatus::OPTIMAL);
332  } else {
333  UpdateBestStatus(CpSolverStatus::INFEASIBLE);
334  }
335  if (update_integral_on_each_change_) UpdateGapIntegralInternal();
336  SOLVER_LOG(logger_,
337  SatProgressMessage("Done", wall_timer_.Get(), update_info));
338  return;
339  }
340  if (logger_->LoggingIsEnabled() && change) {
341  const CpObjectiveProto& obj = *objective_or_null_;
342  const double best =
343  ScaleObjectiveValue(obj, best_solution_objective_value_);
344  double new_lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
345  double new_ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
346  if (obj.scaling_factor() < 0) {
347  std::swap(new_lb, new_ub);
348  }
349  RegisterObjectiveBoundImprovement(update_info);
350  SOLVER_LOG(logger_, ProgressMessage("Bound", wall_timer_.Get(), best,
351  new_lb, new_ub, update_info));
352  }
353  if (change) TestGapLimitsIfNeeded();
354 }
355 
356 // Invariant: the status always start at UNKNOWN and can only evolve as follow:
357 // UNKNOWN -> FEASIBLE -> OPTIMAL
358 // UNKNOWN -> INFEASIBLE
360  const std::string& worker_info) {
361  absl::MutexLock mutex_lock(&mutex_);
362  if (best_status_ == CpSolverStatus::FEASIBLE ||
363  best_status_ == CpSolverStatus::OPTIMAL) {
364  // We also use this status to indicate that we enumerated all solutions to
365  // a feasible problem.
366  UpdateBestStatus(CpSolverStatus::OPTIMAL);
367 
368  // We just proved that the best solution cannot be improved uppon, so we
369  // have a new lower bound.
370  inner_objective_lower_bound_ = best_solution_objective_value_;
371  if (update_integral_on_each_change_) UpdateGapIntegralInternal();
372  } else {
373  CHECK_EQ(num_solutions_, 0);
374  UpdateBestStatus(CpSolverStatus::INFEASIBLE);
375  }
376  SOLVER_LOG(logger_,
377  SatProgressMessage("Done", wall_timer_.Get(), worker_info));
378 }
379 
380 void SharedResponseManager::AddUnsatCore(const std::vector<int>& core) {
381  absl::MutexLock mutex_lock(&mutex_);
382  unsat_cores_ = core;
383 }
384 
386  absl::MutexLock mutex_lock(&mutex_);
387  return IntegerValue(inner_objective_lower_bound_);
388 }
389 
391  absl::MutexLock mutex_lock(&mutex_);
392  return IntegerValue(inner_objective_upper_bound_);
393 }
394 
396  absl::MutexLock mutex_lock(&mutex_);
397  synchronized_inner_objective_lower_bound_ =
398  IntegerValue(inner_objective_lower_bound_);
399  synchronized_inner_objective_upper_bound_ =
400  IntegerValue(inner_objective_upper_bound_);
401  synchronized_best_status_ = best_status_;
402  if (solutions_.NumSolutions() > 0) {
403  first_solution_solvers_should_stop_ = true;
404  }
405 }
406 
408  absl::MutexLock mutex_lock(&mutex_);
409  return synchronized_inner_objective_lower_bound_;
410 }
411 
413  absl::MutexLock mutex_lock(&mutex_);
414  return synchronized_inner_objective_upper_bound_;
415 }
416 
418  absl::MutexLock mutex_lock(&mutex_);
419  return IntegerValue(best_solution_objective_value_);
420 }
421 
423  absl::MutexLock mutex_lock(&mutex_);
424  return gap_integral_;
425 }
426 
428  std::function<void(std::vector<int64_t>*)> postprocessor) {
429  absl::MutexLock mutex_lock(&mutex_);
430  solution_postprocessors_.push_back(postprocessor);
431 }
432 
434  std::function<void(CpSolverResponse*)> postprocessor) {
435  absl::MutexLock mutex_lock(&mutex_);
436  postprocessors_.push_back(postprocessor);
437 }
438 
440  std::function<void(CpSolverResponse*)> postprocessor) {
441  absl::MutexLock mutex_lock(&mutex_);
442  final_postprocessors_.push_back(postprocessor);
443 }
444 
446  std::function<void(const CpSolverResponse&)> callback) {
447  absl::MutexLock mutex_lock(&mutex_);
448  const int id = next_callback_id_++;
449  callbacks_.emplace_back(id, std::move(callback));
450  return id;
451 }
452 
454  absl::MutexLock mutex_lock(&mutex_);
455  for (int i = 0; i < callbacks_.size(); ++i) {
456  if (callbacks_[i].first == callback_id) {
457  callbacks_.erase(callbacks_.begin() + i);
458  return;
459  }
460  }
461  LOG(DFATAL) << "Callback id " << callback_id << " not registered.";
462 }
463 
464 CpSolverResponse SharedResponseManager::GetResponseInternal(
465  absl::Span<const int64_t> variable_values,
466  const std::string& solution_info) {
467  CpSolverResponse result;
468  result.set_status(best_status_);
469  if (!unsat_cores_.empty()) {
470  DCHECK_EQ(best_status_, CpSolverStatus::INFEASIBLE);
471  result.mutable_sufficient_assumptions_for_infeasibility()->Assign(
472  unsat_cores_.begin(), unsat_cores_.end());
473  }
474  FillObjectiveValuesInResponse(&result);
475  result.set_solution_info(solution_info);
476 
477  // Tricky: We copy the solution now for the case where MergeFrom() belows
478  // override it!
479  //
480  // TODO(user): Fix. This is messy, we should really just override stats not
481  // important things like solution or status with the MergeFrom() below.
482  if (best_status_ == CpSolverStatus::FEASIBLE ||
483  best_status_ == CpSolverStatus::OPTIMAL) {
484  result.mutable_solution()->Assign(variable_values.begin(),
485  variable_values.end());
486  }
487 
488  // Note that we allow subsolver_responses_ to override the fields set above.
489  // That is the status, solution_info and objective values...
490  if (!subsolver_responses_.empty()) {
491  result.MergeFrom(subsolver_responses_.front());
492  }
493 
494  if (result.status() == CpSolverStatus::FEASIBLE ||
495  result.status() == CpSolverStatus::OPTIMAL) {
496  // We need to copy the solution before we postsolve it.
497  std::vector<int64_t> solution(result.solution().begin(),
498  result.solution().end());
499  for (int i = solution_postprocessors_.size(); --i >= 0;) {
500  solution_postprocessors_[i](&solution);
501  }
502  result.mutable_solution()->Assign(solution.begin(), solution.end());
503  }
504 
505  // Apply response postprocessor to set things like timing information.
506  for (int i = postprocessors_.size(); --i >= 0;) {
507  postprocessors_[i](&result);
508  }
509  return result;
510 }
511 
513  absl::MutexLock mutex_lock(&mutex_);
514  CpSolverResponse result =
515  solutions_.NumSolutions() == 0
516  ? GetResponseInternal({}, "")
517  : GetResponseInternal(solutions_.GetSolution(0).variable_values,
518  solutions_.GetSolution(0).info);
519 
520  // If this is true, we postsolve and copy all of our solutions.
521  if (parameters_.fill_additional_solutions_in_response()) {
522  std::vector<int64_t> temp;
523  for (int i = 0; i < solutions_.NumSolutions(); ++i) {
524  temp = solutions_.GetSolution(i).variable_values;
525  for (int i = solution_postprocessors_.size(); --i >= 0;) {
526  solution_postprocessors_[i](&temp);
527  }
528  result.add_additional_solutions()->mutable_values()->Assign(temp.begin(),
529  temp.end());
530  }
531  }
532 
533  // final postprocessors will print out the final log. They must be called
534  // last.
535  for (int i = final_postprocessors_.size(); --i >= 0;) {
536  final_postprocessors_[i](&result);
537  }
538 
539  return result;
540 }
541 
543  const CpSolverResponse& response) {
544  absl::MutexLock mutex_lock(&mutex_);
545  return subsolver_responses_.push_back(response);
546 }
547 
548 void SharedResponseManager::FillObjectiveValuesInResponse(
549  CpSolverResponse* response) const {
550  if (objective_or_null_ == nullptr) return;
551  const CpObjectiveProto& obj = *objective_or_null_;
552 
553  if (best_status_ == CpSolverStatus::INFEASIBLE) {
554  response->clear_objective_value();
555  response->clear_best_objective_bound();
556  response->clear_inner_objective_lower_bound();
557  return;
558  }
559 
560  // Set the objective value.
561  // If we don't have any solution, we use our inner bound.
562  if (best_status_ == CpSolverStatus::UNKNOWN) {
563  response->set_objective_value(
564  ScaleObjectiveValue(obj, inner_objective_upper_bound_));
565  } else {
566  response->set_objective_value(
567  ScaleObjectiveValue(obj, best_solution_objective_value_));
568  }
569 
570  // Update the best bound in the response.
571  response->set_inner_objective_lower_bound(
572  ScaleInnerObjectiveValue(obj, inner_objective_lower_bound_));
573  response->set_best_objective_bound(
574  ScaleObjectiveValue(obj, inner_objective_lower_bound_));
575 
576  // Update the primal integral.
577  response->set_gap_integral(gap_integral_);
578 }
579 
581  absl::Span<const int64_t> solution_values, const std::string& solution_info,
582  Model* model) {
583  absl::MutexLock mutex_lock(&mutex_);
584 
585  // For SAT problems, we add the solution to the solution pool for retrieval
586  // later.
587  if (objective_or_null_ == nullptr) {
589  solution.variable_values.assign(solution_values.begin(),
590  solution_values.end());
591  solution.info = solution_info;
592 
593  solutions_.Add(solution);
594  }
595 
596  if (objective_or_null_ != nullptr) {
597  const int64_t objective_value =
598  ComputeInnerObjective(*objective_or_null_, solution_values);
599 
600  // Add this solution to the pool, even if it is not improving.
601  if (!solution_values.empty()) {
603  solution.variable_values.assign(solution_values.begin(),
604  solution_values.end());
605  solution.rank = objective_value;
606  solution.info = solution_info;
607  solutions_.Add(solution);
608  }
609 
610  // Ignore any non-strictly improving solution.
611  if (objective_value > inner_objective_upper_bound_) return;
612 
613  // Our inner_objective_lower_bound_ should be a globaly valid bound, until
614  // the problem become infeasible (i.e the lb > ub) in which case the bound
615  // is no longer globally valid. Here, because we have a strictly improving
616  // solution, we shouldn't be in the infeasible setting yet.
617  DCHECK_GE(objective_value, inner_objective_lower_bound_);
618 
619  DCHECK_LT(objective_value, best_solution_objective_value_);
620  best_solution_objective_value_ = objective_value;
621 
622  // Update the new bound.
623  inner_objective_upper_bound_ = objective_value - 1;
624  }
625 
626  // In single thread, no one is synchronizing the solution manager, so we
627  // should do it from here.
628  if (always_synchronize_) {
629  solutions_.Synchronize();
630  first_solution_solvers_should_stop_ = true;
631  }
632 
633  // Note that the objective will be filled by
634  // FillObjectiveValuesInResponse().
635  if (objective_or_null_ == nullptr && !parameters_.enumerate_all_solutions()) {
636  UpdateBestStatus(CpSolverStatus::OPTIMAL);
637  } else {
638  UpdateBestStatus(CpSolverStatus::FEASIBLE);
639  }
640 
641  // Mark model as OPTIMAL if the inner bound crossed.
642  if (objective_or_null_ != nullptr &&
643  inner_objective_lower_bound_ > inner_objective_upper_bound_) {
644  UpdateBestStatus(CpSolverStatus::OPTIMAL);
645  }
646 
647  // Logging.
648  ++num_solutions_;
649  // TODO(user): Remove this code and the need for model in this function.
650  if (logger_->LoggingIsEnabled()) {
651  std::string solution_message = solution_info;
652  if (model != nullptr) {
653  const int64_t num_bool = model->Get<Trail>()->NumVariables();
654  const int64_t num_fixed = model->Get<SatSolver>()->NumFixedVariables();
655  absl::StrAppend(&solution_message, " fixed_bools:", num_fixed, "/",
656  num_bool);
657  }
658 
659  if (objective_or_null_ != nullptr) {
660  const CpObjectiveProto& obj = *objective_or_null_;
661  const double best =
662  ScaleObjectiveValue(obj, best_solution_objective_value_);
663  double lb = ScaleObjectiveValue(obj, inner_objective_lower_bound_);
664  double ub = ScaleObjectiveValue(obj, inner_objective_upper_bound_);
665  if (obj.scaling_factor() < 0) {
666  std::swap(lb, ub);
667  }
668  RegisterSolutionFound(solution_message);
669  SOLVER_LOG(logger_, ProgressMessage(absl::StrCat(num_solutions_),
670  wall_timer_.Get(), best, lb, ub,
671  solution_message));
672  } else {
673  SOLVER_LOG(logger_,
674  SatProgressMessage(absl::StrCat(num_solutions_),
675  wall_timer_.Get(), solution_message));
676  }
677  }
678 
679  // Call callbacks.
680  // Note that we cannot call function that try to get the mutex_ here.
681  TestGapLimitsIfNeeded();
682  if (!callbacks_.empty()) {
683  CpSolverResponse copy = GetResponseInternal(solution_values, solution_info);
685  for (const auto& pair : callbacks_) {
686  pair.second(copy);
687  }
688  }
689 
690 #if !defined(__PORTABLE_PLATFORM__)
691  // We protect solution dumping with log_updates as LNS subsolvers share
692  // another solution manager, and we do not want to dump those.
693  if (logger_->LoggingIsEnabled() &&
694  absl::GetFlag(FLAGS_cp_model_dump_solutions)) {
695  const std::string file =
696  absl::StrCat(dump_prefix_, "solution_", num_solutions_, ".pb.txt");
697  LOG(INFO) << "Dumping solution to '" << file << "'.";
698 
699  // Note that here we only want to dump the non-postsolved solution.
700  // This is only used for debugging.
701  CpSolverResponse response;
702  response.mutable_solution()->Assign(solution_values.begin(),
703  solution_values.end());
705  }
706 #endif // __PORTABLE_PLATFORM__
707 }
708 
710  absl::MutexLock mutex_lock(&mutex_);
711  return synchronized_best_status_ == CpSolverStatus::OPTIMAL ||
712  synchronized_best_status_ == CpSolverStatus::INFEASIBLE;
713 }
714 
715 void SharedResponseManager::UpdateBestStatus(const CpSolverStatus& status) {
716  best_status_ = status;
717  if (always_synchronize_) {
718  synchronized_best_status_ = status;
719  }
720 }
721 
722 std::string ExtractSubSolverName(const std::string& improvement_info) {
723  if (improvement_info.empty()) return "";
724 
725  // We assume the subsolver name is always first.
726  for (int i = 0; i < improvement_info.size(); ++i) {
727  if (!std::isalnum(improvement_info[i]) && improvement_info[i] != '_') {
728  return improvement_info.substr(0, i);
729  }
730  }
731 
732  return improvement_info;
733 }
734 
735 void SharedResponseManager::RegisterSolutionFound(
736  const std::string& improvement_info) {
737  if (improvement_info.empty()) return;
738  primal_improvements_count_[ExtractSubSolverName(improvement_info)]++;
739 }
740 
741 void SharedResponseManager::RegisterObjectiveBoundImprovement(
742  const std::string& improvement_info) {
743  if (improvement_info.empty() || improvement_info == "initial domain") return;
744  dual_improvements_count_[ExtractSubSolverName(improvement_info)]++;
745 }
746 
748  absl::MutexLock mutex_lock(&mutex_);
749  if (!primal_improvements_count_.empty()) {
750  SOLVER_LOG(logger_, "");
751  SOLVER_LOG(logger_, "Solutions found per subsolver:");
752  for (const auto& entry : primal_improvements_count_) {
753  SOLVER_LOG(logger_, " '", entry.first, "': ", entry.second);
754  }
755  }
756  if (!dual_improvements_count_.empty()) {
757  SOLVER_LOG(logger_, "");
758  SOLVER_LOG(logger_, "Objective bounds found per subsolver:");
759  for (const auto& entry : dual_improvements_count_) {
760  SOLVER_LOG(logger_, " '", entry.first, "': ", entry.second);
761  }
762  }
763 }
764 
766  : num_variables_(model_proto.variables_size()),
767  model_proto_(model_proto),
768  lower_bounds_(num_variables_, std::numeric_limits<int64_t>::min()),
769  upper_bounds_(num_variables_, std::numeric_limits<int64_t>::max()),
770  synchronized_lower_bounds_(num_variables_,
771  std::numeric_limits<int64_t>::min()),
772  synchronized_upper_bounds_(num_variables_,
773  std::numeric_limits<int64_t>::max()) {
774  changed_variables_since_last_synchronize_.ClearAndResize(num_variables_);
775  for (int i = 0; i < num_variables_; ++i) {
776  lower_bounds_[i] = model_proto.variables(i).domain(0);
777  const int domain_size = model_proto.variables(i).domain_size();
778  upper_bounds_[i] = model_proto.variables(i).domain(domain_size - 1);
779  synchronized_lower_bounds_[i] = lower_bounds_[i];
780  synchronized_upper_bounds_[i] = upper_bounds_[i];
781  }
782 }
783 
785  const std::string& worker_name, const std::vector<int>& variables,
786  const std::vector<int64_t>& new_lower_bounds,
787  const std::vector<int64_t>& new_upper_bounds) {
788  CHECK_EQ(variables.size(), new_lower_bounds.size());
789  CHECK_EQ(variables.size(), new_upper_bounds.size());
790  int num_improvements = 0;
791 
792  absl::MutexLock mutex_lock(&mutex_);
793  for (int i = 0; i < variables.size(); ++i) {
794  const int var = variables[i];
795  if (var >= num_variables_) continue;
796  const int64_t old_lb = lower_bounds_[var];
797  const int64_t old_ub = upper_bounds_[var];
798  const int64_t new_lb = new_lower_bounds[i];
799  const int64_t new_ub = new_upper_bounds[i];
800  const bool changed_lb = new_lb > old_lb;
801  const bool changed_ub = new_ub < old_ub;
802  if (!changed_lb && !changed_ub) continue;
803 
804  VLOG(3) << worker_name << " var=" << var << " [" << old_lb << "," << old_ub
805  << "] -> [" << new_lb << "," << new_ub << "]";
806 
807  if (changed_lb) {
808  if (DEBUG_MODE && !debug_solution_.empty()) {
809  CHECK_LE(new_lb, debug_solution_[var]) << worker_name << " var=" << var;
810  }
811  lower_bounds_[var] = new_lb;
812  }
813  if (changed_ub) {
814  if (DEBUG_MODE && !debug_solution_.empty()) {
815  CHECK_GE(new_ub, debug_solution_[var]) << worker_name << " var=" << var;
816  }
817  upper_bounds_[var] = new_ub;
818  }
819  changed_variables_since_last_synchronize_.Set(var);
820  num_improvements++;
821  }
822  if (num_improvements > 0) {
823  bounds_exported_[worker_name] += num_improvements;
824  }
825 }
826 
827 // TODO(user): Because we look at the non-synchronized and up to date bounds,
828 // this break determinism if two solution for the same subpart comes at the same
829 // time.
831  const std::vector<int64_t>& solution,
832  const std::vector<int>& variables_to_fix) {
833  absl::MutexLock mutex_lock(&mutex_);
834 
835  // Abort if incompatible. Note that we only check the position that we are
836  // about to fix. This should be enough. Otherwise we might never accept any
837  // solution because the base LNS solution was not the same in some of the
838  // variables that we fixed here.
839  for (const int var : variables_to_fix) {
840  const int64_t value = solution[var];
841  if (value < lower_bounds_[var] || value > upper_bounds_[var]) {
842  VLOG(1) << "Incompatibility in FixVariablesFromPartialSolution() "
843  << "var: " << var << " value: " << value << " bounds: ["
844  << lower_bounds_[var] << "," << upper_bounds_[var] << "]";
845  return;
846  }
847  }
848 
849  // Fix the variables.
850  for (const int var : variables_to_fix) {
851  const int64_t old_lb = lower_bounds_[var];
852  const int64_t old_ub = upper_bounds_[var];
853  const bool changed_lb = solution[var] > old_lb;
854  const bool changed_ub = solution[var] < old_ub;
855  if (!changed_lb && !changed_ub) continue;
856 
857  lower_bounds_[var] = solution[var];
858  upper_bounds_[var] = solution[var];
859  changed_variables_since_last_synchronize_.Set(var);
860 
861  // This is problematic as we might find a different partial solution.
862  // To allow for further investigation, we currently fix it to the debug
863  // solution instead.
864  if (DEBUG_MODE && !debug_solution_.empty()) {
865  if (solution[var] != debug_solution_[var]) {
866  LOG(INFO) << "Fixing to a different solution for var=" << var
867  << " debug=" << debug_solution_[var]
868  << " partial=" << solution[var];
869  lower_bounds_[var] = debug_solution_[var];
870  upper_bounds_[var] = debug_solution_[var];
871  }
872  }
873  }
874 }
875 
877  absl::MutexLock mutex_lock(&mutex_);
878  for (const int var :
879  changed_variables_since_last_synchronize_.PositionsSetAtLeastOnce()) {
880  synchronized_lower_bounds_[var] = lower_bounds_[var];
881  synchronized_upper_bounds_[var] = upper_bounds_[var];
882  for (int j = 0; j < id_to_changed_variables_.size(); ++j) {
883  id_to_changed_variables_[j].Set(var);
884  }
885  }
886  changed_variables_since_last_synchronize_.ClearAll();
887 }
888 
890  absl::MutexLock mutex_lock(&mutex_);
891  const int id = id_to_changed_variables_.size();
892  id_to_changed_variables_.resize(id + 1);
893  id_to_changed_variables_[id].ClearAndResize(num_variables_);
894  for (int var = 0; var < num_variables_; ++var) {
895  const int64_t lb = model_proto_.variables(var).domain(0);
896  const int domain_size = model_proto_.variables(var).domain_size();
897  const int64_t ub = model_proto_.variables(var).domain(domain_size - 1);
898  if (lb != synchronized_lower_bounds_[var] ||
899  ub != synchronized_upper_bounds_[var]) {
900  id_to_changed_variables_[id].Set(var);
901  }
902  }
903  return id;
904 }
905 
907  int id, std::vector<int>* variables, std::vector<int64_t>* new_lower_bounds,
908  std::vector<int64_t>* new_upper_bounds) {
909  variables->clear();
910  new_lower_bounds->clear();
911  new_upper_bounds->clear();
912 
913  absl::MutexLock mutex_lock(&mutex_);
914  for (const int var : id_to_changed_variables_[id].PositionsSetAtLeastOnce()) {
915  variables->push_back(var);
916  new_lower_bounds->push_back(synchronized_lower_bounds_[var]);
917  new_upper_bounds->push_back(synchronized_upper_bounds_[var]);
918  }
919  id_to_changed_variables_[id].ClearAll();
920 }
921 
923  absl::MutexLock mutex_lock(&mutex_);
924  if (!bounds_exported_.empty()) {
925  SOLVER_LOG(logger, "");
926  SOLVER_LOG(logger, "Improving variable bounds shared per subsolver:");
927  for (const auto& entry : bounds_exported_) {
928  SOLVER_LOG(logger, " '", entry.first, "': ", entry.second);
929  }
930  }
931 }
932 
933 int SharedBoundsManager::NumBoundsExported(const std::string& worker_name) {
934  absl::MutexLock mutex_lock(&mutex_);
935  const auto it = bounds_exported_.find(worker_name);
936  if (it == bounds_exported_.end()) return 0;
937  return it->second;
938 }
939 
941  : always_synchronize_(always_synchronize) {}
942 
944  absl::MutexLock mutex_lock(&mutex_);
945  const int id = id_to_last_processed_binary_clause_.size();
946  id_to_last_processed_binary_clause_.resize(id + 1, 0);
947  id_to_clauses_exported_.resize(id + 1, 0);
948  return id;
949 }
950 
952  const std::string& worker_name) {
953  absl::MutexLock mutex_lock(&mutex_);
954  id_to_worker_name_[id] = worker_name;
955 }
956 
957 void SharedClausesManager::AddBinaryClause(int id, int lit1, int lit2) {
958  absl::MutexLock mutex_lock(&mutex_);
959  if (lit2 < lit1) std::swap(lit1, lit2);
960 
961  const auto p = std::make_pair(lit1, lit2);
962  const auto [unused_it, inserted] = added_binary_clauses_set_.insert(p);
963  if (inserted) {
964  added_binary_clauses_.push_back(p);
965  if (always_synchronize_) ++last_visible_clause_;
966  id_to_clauses_exported_[id]++;
967  // Small optim. If the worker is already up to date with clauses to import,
968  // we can mark this new clause as already seen.
969  if (id_to_last_processed_binary_clause_[id] ==
970  added_binary_clauses_.size() - 1) {
971  id_to_last_processed_binary_clause_[id]++;
972  }
973  }
974 }
975 
977  int id, std::vector<std::pair<int, int>>* new_clauses) {
978  new_clauses->clear();
979  absl::MutexLock mutex_lock(&mutex_);
980  const int last_binary_clause_seen = id_to_last_processed_binary_clause_[id];
981 
982  // Protects against the optim that increase the last_binary_clause_seen in
983  // AddBinaryClause(). Checks is nothing needs to be done.
984  if (last_binary_clause_seen >= last_visible_clause_) return;
985 
986  new_clauses->assign(added_binary_clauses_.begin() + last_binary_clause_seen,
987  added_binary_clauses_.begin() + last_visible_clause_);
988  id_to_last_processed_binary_clause_[id] = last_visible_clause_;
989 }
990 
992  absl::MutexLock mutex_lock(&mutex_);
993  absl::btree_map<std::string, int64_t> name_to_clauses;
994  for (int id = 0; id < id_to_clauses_exported_.size(); ++id) {
995  if (id_to_clauses_exported_[id] == 0) continue;
996  name_to_clauses[id_to_worker_name_[id]] = id_to_clauses_exported_[id];
997  }
998  if (!name_to_clauses.empty()) {
999  SOLVER_LOG(logger, "");
1000  SOLVER_LOG(logger, "Clauses shared per subsolver:");
1001  for (const auto& entry : name_to_clauses) {
1002  SOLVER_LOG(logger, " '", entry.first, "': ", entry.second);
1003  }
1004  }
1005 }
1006 
1008  absl::MutexLock mutex_lock(&mutex_);
1009  last_visible_clause_ = added_binary_clauses_.size();
1010  // TODO(user): We could cleanup added_binary_clauses_ periodically.
1011 }
1012 
1014  absl::Span<const std::pair<std::string, int64_t>> stats) {
1015  absl::MutexLock mutex_lock(&mutex_);
1016  for (const auto& [key, count] : stats) {
1017  stats_[key] += count;
1018  }
1019 }
1020 
1022  absl::MutexLock mutex_lock(&mutex_);
1023  if (stats_.empty()) return;
1024 
1025  SOLVER_LOG(logger, "");
1026  SOLVER_LOG(logger, "Stats across workers (summed):");
1027  std::vector<std::pair<std::string, int64_t>> to_sort_;
1028  for (const auto& [key, count] : stats_) {
1029  to_sort_.push_back({key, count});
1030  }
1031  std::sort(to_sort_.begin(), to_sort_.end());
1032  for (const auto& [key, count] : to_sort_) {
1033  SOLVER_LOG(logger, " ", key, ": ", FormatCounter(count));
1034  }
1035 }
1036 
1037 } // namespace sat
1038 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
double Get() const
Definition: timer.h:45
We call domain any subset of Int64 = [kint64min, kint64max].
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t Max() const
Returns the max value of the domain.
double GetElapsedDeterministicTime() const
Definition: time_limit.h:396
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void ReportPotentialNewBounds(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)
SharedBoundsManager(const CpModelProto &model_proto)
void FixVariablesFromPartialSolution(const std::vector< int64_t > &solution, const std::vector< int > &variables_to_fix)
int NumBoundsExported(const std::string &worker_name)
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
void AddBinaryClause(int id, int lit1, int lit2)
void GetUnseenBinaryClauses(int id, std::vector< std::pair< int, int >> *new_clauses)
void SetWorkerNameForId(int id, const std::string &worker_name)
void AddNewSolution(const std::vector< double > &lp_solution)
void NewLPSolution(std::vector< double > lp_solution)
void NewRelaxationSolution(absl::Span< const int64_t > solution_values, IntegerValue inner_objective_value)
void InitializeObjective(const CpModelProto &cp_model)
void LogPeriodicMessage(const std::string &prefix, const std::string &message, double frequency_seconds, absl::Time *last_logging_time)
void AddSolutionPostprocessor(std::function< void(std::vector< int64_t > *)> postprocessor)
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
void SetSynchronizationMode(bool always_synchronize)
void AddUnsatCore(const std::vector< int > &core)
void SetGapLimitsFromParameters(const SatParameters &parameters)
void AppendResponseToBeMerged(const CpSolverResponse &response)
void AddResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
int AddSolutionCallback(std::function< void(const CpSolverResponse &)> callback)
void NewSolution(absl::Span< const int64_t > solution_values, const std::string &solution_info, Model *model=nullptr)
void LogMessage(const std::string &prefix, const std::string &message)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
void AddInternal(const Solution &solution) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_)
void AddStats(absl::Span< const std::pair< std::string, int64_t >> stats)
SatParameters parameters
CpModelProto const * model_proto
SharedResponseManager * response
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
MPCallback * callback
const bool DEBUG_MODE
Definition: macros.h:24
absl::Status SetTextProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
Definition: base/file.cc:299
Options Defaults()
Definition: base/file.h:123
int NumVariables(const VariablesProto &variables)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
std::string ExtractSubSolverName(const std::string &improvement_info)
std::string FormatCounter(int64_t num)
Definition: sat/util.cc:48
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, absl::Span< const int64_t > solution)
void FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
int64_t ScaleInnerObjectiveValue(const CpObjectiveProto &proto, int64_t value)
Collection of objects used to extend the Constraint Solver library.
std::vector< std::function< void(CpSolverResponse *)> > callbacks
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\"'.")
std::string message
Definition: trace.cc:399
double objective_value
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define VLOG(verboselevel)
Definition: vlog.h:39