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