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