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