OR-Tools  9.2
synchronization.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <cstdint>
17#include <limits>
18
19#if !defined(__PORTABLE_PLATFORM__)
20#include "ortools/base/file.h"
22#endif // __PORTABLE_PLATFORM__
23
24#include "absl/container/flat_hash_set.h"
25#include "absl/random/random.h"
30#include "ortools/sat/integer.h"
32#include "ortools/sat/model.h"
36
37ABSL_FLAG(bool, cp_model_dump_solutions, false,
38 "DEBUG ONLY. If true, all the intermediate solution will be dumped "
39 "under '\"FLAGS_cp_model_dump_prefix\" + \"solution_xxx.pb.txt\"'.");
40
42 std::string, cp_model_load_debug_solution, "",
43 "DEBUG ONLY. When this is set to a non-empty file name, "
44 "we will interpret this as an internal solution which can be used for "
45 "debugging. For instance we use it to identify wrong cuts/reasons.");
46
47namespace operations_research {
48namespace sat {
49
52 // Note that the Add() method already applies mutex lock. So we don't need it
53 // here.
54 if (response.solution().empty()) return;
55
56 // Add this solution to the pool.
58 solution.variable_values.assign(response.solution().begin(),
59 response.solution().end());
60 // For now we use the negated lower bound as the "internal objective" to
61 // prefer solution with an higher bound.
62 //
63 // Note: If the model doesn't have objective, the best_objective_bound is set
64 // to default value 0.
65 solution.rank = -response.best_objective_bound();
66
67 Add(solution);
68}
69
71 std::vector<double> lp_solution) {
72 if (lp_solution.empty()) return;
73
74 // Add this solution to the pool.
76 solution.variable_values = std::move(lp_solution);
77
78 // We always prefer to keep the solution from the last synchronize batch.
79 absl::MutexLock mutex_lock(&mutex_);
80 solution.rank = -num_synchronization_;
81 AddInternal(solution);
82}
83
85 absl::MutexLock mutex_lock(&mutex_);
86 return !solutions_.empty();
87}
88
90 absl::MutexLock mutex_lock(&mutex_);
91 std::vector<double> solution;
92 if (solutions_.empty()) return solution;
93
94 solution = std::move(solutions_.back());
95 solutions_.pop_back();
96 return solution;
97}
98
100 const std::vector<double>& lp_solution) {
101 absl::MutexLock mutex_lock(&mutex_);
102 solutions_.push_back(lp_solution);
103}
104
106 : 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
112namespace {
113
114std::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
125std::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
135 absl::MutexLock mutex_lock(&mutex_);
136 SOLVER_LOG(logger_,
137 absl::StrFormat("#Model %6.2fs %s", wall_timer_.Get(), message));
138}
139
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
154 absl::MutexLock mutex_lock(&mutex_);
155 update_integral_on_each_change_ = set;
156}
157
159 absl::MutexLock mutex_lock(&mutex_);
160 UpdateGapIntegralInternal();
161}
162
163void 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
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
195void 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
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
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
311void 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
320 absl::MutexLock mutex_lock(&mutex_);
321 return IntegerValue(inner_objective_lower_bound_);
322}
323
325 absl::MutexLock mutex_lock(&mutex_);
326 return IntegerValue(inner_objective_upper_bound_);
327}
328
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
338 absl::MutexLock mutex_lock(&mutex_);
339 return synchronized_inner_objective_lower_bound_;
340}
341
343 absl::MutexLock mutex_lock(&mutex_);
344 return synchronized_inner_objective_upper_bound_;
345}
346
348 absl::MutexLock mutex_lock(&mutex_);
349 return IntegerValue(best_solution_objective_value_);
350}
351
353 absl::MutexLock mutex_lock(&mutex_);
354 return gap_integral_;
355}
356
358 std::function<void(std::vector<int64_t>*)> postprocessor) {
359 absl::MutexLock mutex_lock(&mutex_);
360 solution_postprocessors_.push_back(postprocessor);
361}
362
364 std::function<void(CpSolverResponse*)> postprocessor) {
365 absl::MutexLock mutex_lock(&mutex_);
366 postprocessors_.push_back(postprocessor);
367}
368
370 std::function<void(CpSolverResponse*)> postprocessor) {
371 absl::MutexLock mutex_lock(&mutex_);
372 final_postprocessors_.push_back(postprocessor);
373}
374
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
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
394CpSolverResponse 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
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
437void 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
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() &&
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()) {
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
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
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),
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
623 absl::MutexLock mutex_lock(&mutex_);
624 SetStatsFromModelInternal(model);
625}
626
627void 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 :
646 num_lp_iters += lp->total_num_simplex_iterations();
647 }
648 best_response_.set_num_lp_iterations(num_lp_iters);
649}
650
652 absl::MutexLock mutex_lock(&mutex_);
653 return best_response_.status() == CpSolverStatus::OPTIMAL ||
654 best_response_.status() == CpSolverStatus::INFEASIBLE;
655}
656
657std::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
670void SharedResponseManager::RegisterSolutionFound(
671 const std::string& improvement_info) {
672 if (improvement_info.empty()) return;
673 primal_improvements_count_[ExtractSubSolverName(improvement_info)]++;
674}
675
676void 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
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
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
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.
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
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
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
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
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:492
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:889
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:699
#define CHECK_GE(val1, val2)
Definition: base/logging.h:703
#define CHECK_OK(x)
Definition: base/logging.h:43
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:891
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:890
#define LOG(severity)
Definition: base/logging.h:417
#define VLOG(verboselevel)
Definition: base/logging.h:980
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:385
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
const ::operations_research::sat::CpObjectiveProto & objective() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
::operations_research::sat::CpSolverStatus status() const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int64_t > * mutable_solution()
::operations_research::sat::CpSolverSolution * add_additional_solutions()
::PROTOBUF_NAMESPACE_ID::RepeatedField< int64_t > * mutable_values()
IntegerVariable NumIntegerVariables() const
Definition: integer.h:638
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
SharedBoundsManager(const CpModelProto &model_proto)
void ReportPotentialNewBounds(const CpModelProto &model_proto, const std::string &worker_name, const std::vector< int > &variables, const std::vector< int64_t > &new_lower_bounds, const std::vector< int64_t > &new_upper_bounds)
void FixVariablesFromPartialSolution(const std::vector< int64_t > &solution, const std::vector< int > &variables_to_fix)
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
void AddNewSolution(const std::vector< double > &lp_solution)
void NewLPSolution(std::vector< double > lp_solution)
void NewRelaxationSolution(const CpSolverResponse &response)
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 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 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:119
absl::Status GetTextProto(const absl::string_view &filename, google::protobuf::Message *proto, int flags)
Definition: base/file.cc:275
absl::Status SetTextProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
Definition: base/file.cc:285
int NumVariables(const VariablesProto &variables)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, const CpSolverResponse &response)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
double ScaleObjectiveValue(const CpObjectiveProto &proto, int64_t value)
std::string ExtractSubSolverName(const std::string &improvement_info)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:30
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
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69