Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
synchronization.h
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#ifndef ORTOOLS_SAT_SYNCHRONIZATION_H_
15#define ORTOOLS_SAT_SYNCHRONIZATION_H_
16
17#include <algorithm>
18#include <array>
19#include <atomic>
20#include <cstddef>
21#include <cstdint>
22#include <deque>
23#include <functional>
24#include <limits>
25#include <memory>
26#include <optional>
27#include <string>
28#include <utility>
29#include <vector>
30
31#include "absl/algorithm/container.h"
32#include "absl/base/thread_annotations.h"
33#include "absl/container/btree_map.h"
34#include "absl/container/flat_hash_map.h"
35#include "absl/container/flat_hash_set.h"
36#include "absl/random/bit_gen_ref.h"
37#include "absl/random/random.h"
38#include "absl/strings/string_view.h"
39#include "absl/synchronization/mutex.h"
40#include "absl/time/time.h"
41#include "absl/types/span.h"
44#include "ortools/base/timer.h"
48#include "ortools/sat/model.h"
50#include "ortools/sat/util.h"
51#include "ortools/util/bitset.h"
54
55namespace operations_research {
56namespace sat {
57
62
63 std::string change_info;
64
65 // Set to true if the solver found a new solution with a improved objective
66 // value.
67 bool new_best_solution = false;
68
69 // Set to true if the solver found a new lower or upper bound for the
70 // objective.
71 bool new_lower_bound = false;
72 bool new_upper_bound = false;
73
74 // Whether the solver finished the search. The callback will not be called
75 // again after this is true.
76 bool solved = false;
77};
78
79// Thread-safe. Keeps a set of n unique best solution found so far.
80//
81// TODO(user): Maybe add some criteria to only keep solution with an objective
82// really close to the best solution.
83template <typename ValueType>
85 public:
87 absl::string_view name = "",
88 int source_id = -1)
89 : name_(name),
91 source_id_(source_id) {}
92
93 // The solution format used by this class.
94 struct Solution {
95 // Solution with lower "rank" will be preferred
96 //
97 // TODO(user): Some LNS code assume that for the SharedSolutionRepository
98 // this rank is actually the unscaled internal minimization objective.
99 // Remove this assumptions by simply recomputing this value since it is not
100 // too costly to do so.
101 int64_t rank = 0;
102
103 std::vector<ValueType> variable_values;
104
105 std::string info;
106
107 // Number of time this was returned by GetRandomBiasedSolution(). We use
108 // this information during the selection process.
109 //
110 // Should be private: only SharedSolutionRepository should modify this.
111 mutable int num_selected = 0;
112
113 int source_id; // Internal information.
114
115 bool operator==(const Solution& other) const {
116 return rank == other.rank && variable_values == other.variable_values;
117 }
118 bool operator<(const Solution& other) const {
119 if (rank != other.rank) {
120 return rank < other.rank;
121 }
122 return variable_values < other.variable_values;
123 }
124 };
125
126 // Returns the number of current solution in the pool. This will never
127 // decrease.
128 int NumSolutions() const;
129
130 // Returns the solution #i where i must be smaller than NumSolutions().
131 // Returns nullptr if i is out of range.
132 std::shared_ptr<const Solution> GetSolution(int index) const;
133
134 // Returns the rank of the best known solution. If there is no solution, this
135 // will return std::numeric_limits<int64_t>::max().
136 int64_t GetBestRank() const;
137
138 std::vector<std::shared_ptr<const Solution>> GetBestNSolutions(int n) const;
139
140 // Returns the variable value of variable 'var_index' from solution
141 // 'solution_index' where solution_index must be smaller than NumSolutions()
142 // and 'var_index' must be smaller than number of variables.
143 ValueType GetVariableValueInSolution(int var_index, int solution_index) const;
144
145 // Returns a random solution biased towards good solutions.
146 std::shared_ptr<const Solution> GetRandomBiasedSolution(
147 absl::BitGenRef random) const;
148
149 // Add a new solution. Note that it will not be added to the pool of solution
150 // right away. One must call Synchronize for this to happen. In order to be
151 // deterministic, this will keep all solutions until Synchronize() is called,
152 // so we need to be careful not to generate too many solutions at once.
153 //
154 // Returns a shared pointer to the solution that was stored in the repository.
155 std::shared_ptr<const Solution> Add(Solution solution);
156
157 // Updates the current pool of solution with the one recently added. Note that
158 // we use a stable ordering of solutions, so the final pool will be
159 // independent on the order of the calls to AddSolution() provided that the
160 // set of added solutions is the same.
161 //
162 // Works in O(num_solutions_to_keep_).
163 //
164 // If f() is provided, it will be called on all new solutions.
165 void Synchronize(std::function<void(const Solution& solution)> f = nullptr);
166
167 std::vector<std::string> TableLineStats() const {
168 absl::MutexLock mutex_lock(mutex_);
169 return {FormatName(name_), FormatCounter(num_added_),
170 FormatCounter(num_queried_), FormatCounter(num_synchronization_)};
171 }
172
173 int64_t NumRecentlyNonImproving() const {
174 absl::MutexLock mutex_lock(mutex_);
175 return num_non_improving_;
176 }
177
179 absl::MutexLock mutex_lock(mutex_);
180 new_solutions_.clear();
181 solutions_.clear();
182 ++source_id_;
183 }
184
185 int source_id() const {
186 absl::MutexLock mutex_lock(mutex_);
187 return source_id_;
188 }
189
190 int num_queried() const {
191 absl::MutexLock mutex_lock(mutex_);
192 return num_queried_;
193 }
194
196
197 void SetDiversityLimit(int value) { diversity_limit_ = value; }
198
199 protected:
200 const std::string name_;
203
204 mutable absl::Mutex mutex_;
205 int source_id_ ABSL_GUARDED_BY(mutex_);
206 int64_t num_added_ ABSL_GUARDED_BY(mutex_) = 0;
207 mutable int64_t num_queried_ ABSL_GUARDED_BY(mutex_) = 0;
208 int64_t num_synchronization_ ABSL_GUARDED_BY(mutex_) = 0;
209
210 mutable int64_t num_queried_at_last_sync_ ABSL_GUARDED_BY(mutex_) = 0;
211 mutable int64_t num_non_improving_ ABSL_GUARDED_BY(mutex_) = 0;
212
213 // Our two solutions pools, the current one and the new one that will be
214 // merged into the current one on each Synchronize() calls.
215 mutable std::vector<int> tmp_indices_ ABSL_GUARDED_BY(mutex_);
216 std::vector<std::shared_ptr<Solution>> solutions_ ABSL_GUARDED_BY(mutex_);
217 std::vector<std::shared_ptr<Solution>> new_solutions_ ABSL_GUARDED_BY(mutex_);
218
219 // For computing orthogonality.
220 std::vector<int64_t> ABSL_GUARDED_BY(mutex_) distances_;
221 std::vector<int64_t> ABSL_GUARDED_BY(mutex_) buffer_;
222};
223
224// Solutions coming from the LP.
226 public:
230
231 void NewLPSolution(std::vector<double> lp_solution);
232};
233
234// This stores all the feasible solutions the solver know about.
235// Moreover, for meta-heuristics, we keep them in different buckets.
237 public:
238 explicit SharedSolutionPool(const SatParameters& parameters_)
239 : best_solutions_(parameters_.solution_pool_size(), "best_solutions"),
240 alternative_path_(parameters_.alternative_pool_size(),
241 "alternative_path", /*source_id=*/0) {
242 best_solutions_.SetDiversityLimit(
243 parameters_.solution_pool_diversity_limit());
244 }
245
247 return best_solutions_;
248 }
249
250 // Note that the given random generator is likely local to the thread calling
251 // this.
252 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
253 GetSolutionToImprove(absl::BitGenRef random) const {
254 // If we seems to have trouble making progress, work on the alternative
255 // path too.
256 if (alternative_path_.num_solutions_to_keep() > 0 &&
257 best_solutions_.NumRecentlyNonImproving() > 100 &&
258 absl::Bernoulli(random, 0.5) && alternative_path_.NumSolutions() > 0) {
259 // Tricky: We might clear the alternative_path_ between NumSolutions()
260 // and this call.
261 auto result = alternative_path_.GetRandomBiasedSolution(random);
262 if (result != nullptr) return result;
263 }
264
265 if (best_solutions_.NumSolutions() > 0) {
266 return best_solutions_.GetRandomBiasedSolution(random);
267 }
268 return nullptr;
269 }
270
271 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution> Add(
273
274 void Synchronize(absl::BitGenRef random);
275
276 void AddTableStats(std::vector<std::vector<std::string>>* table) const {
277 table->push_back(best_solutions_.TableLineStats());
278 table->push_back(alternative_path_.TableLineStats());
279 }
280
281 private:
282 // Currently we only have two "pools" of solutions.
283 SharedSolutionRepository<int64_t> best_solutions_;
284 SharedSolutionRepository<int64_t> alternative_path_;
285
286 // We also keep a list of possible "path seeds" in n buckets defined according
287 // to the objective value of the solution. These are updated on Synchronize().
288 // Bucket i will only contain the last seen solution in the internal objective
289 // range [ranks_[i], ranks_[i + 1]).
290 //
291 // ranks_[0] should always be min_rank_, and seeds_[0] should be one of the
292 // best known solution. We usually never select seeds_[0] but keep it around
293 // for later in case new best solutions are found.
294 absl::Mutex mutex_;
295 int64_t max_rank_ ABSL_GUARDED_BY(mutex_) =
296 std::numeric_limits<int64_t>::min();
297 int64_t min_rank_ ABSL_GUARDED_BY(mutex_) =
298 std::numeric_limits<int64_t>::max();
299 std::vector<int64_t> ranks_;
300 std::vector<
301 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>>
302 ABSL_GUARDED_BY(mutex_) seeds_;
303};
304
305// Set of best solution from the feasibility jump workers.
306//
307// We store (solution, num_violated_constraints), so we have a list of solutions
308// that violate as little constraints as possible. This can be used to set the
309// phase during SAT search.
310//
311// TODO(user): We could also use it after first solution to orient a SAT search
312// towards better solutions. But then it is a bit trickier to rank solutions
313// compared to the old ones.
315 public:
317 : SharedSolutionRepository<int64_t>(10, "fj solution hints") {}
318
319 void AddSolution(std::vector<int64_t> solution, int num_violations) {
321 sol.rank = num_violations;
322 sol.variable_values = std::move(solution);
323 Add(sol);
324 }
325};
326
327// Set of partly filled solutions. They are meant to be finished by some lns
328// worker.
329//
330// The solutions are stored as a vector of doubles. The value at index i
331// represents the solution value of model variable indexed i. Note that some
332// values can be infinity which should be interpreted as 'unknown' solution
333// value for that variable. These solutions can not necessarily be completed to
334// complete feasible solutions.
336 public:
337 // This adds a new solution to the stack.
338 // Note that we keep the last 100 ones at most.
339 void AddSolution(const std::vector<double>& lp_solution);
340
341 bool HasSolution() const;
342
343 // If there are no solution, this return an empty vector.
344 std::vector<double> PopLast();
345
346 std::vector<std::string> TableLineStats() const {
347 absl::MutexLock mutex_lock(mutex_);
348 return {FormatName("pump"), FormatCounter(num_added_),
349 FormatCounter(num_queried_)};
350 }
351
352 private:
353 mutable absl::Mutex mutex_;
354 std::deque<std::vector<double>> solutions_ ABSL_GUARDED_BY(mutex_);
355 int64_t num_added_ ABSL_GUARDED_BY(mutex_) = 0;
356 mutable int64_t num_queried_ ABSL_GUARDED_BY(mutex_) = 0;
357};
358
359// Manages the global best response kept by the solver. This class is
360// responsible for logging the progress of the solutions and bounds as they are
361// found.
362//
363// All functions are thread-safe except if specified otherwise.
365 public:
366 explicit SharedResponseManager(Model* model);
367
368 // Loads the initial objective bounds and keep a reference to the objective to
369 // properly display the scaled bounds. This is optional if the model has no
370 // objective.
371 //
372 // This function is not thread safe.
373 void InitializeObjective(const CpModelProto& cp_model);
374
375 // Reports OPTIMAL and stop the search if any gap limit are specified and
376 // crossed. By default, we only stop when we have the true optimal, which is
377 // well defined since we are solving our pure integer problem exactly.
378 void SetGapLimitsFromParameters(const SatParameters& parameters);
379
380 // Returns the current solver response. That is the best known response at the
381 // time of the call with the best feasible solution and objective bounds.
382 //
383 // We will do more postprocessing by calling all the
384 // AddFinalSolutionPostprocessor() postprocesors. Note that the response given
385 // to the AddSolutionCallback() will not call them.
387
388 // These will be called in REVERSE order on any feasible solution returned
389 // to the user.
391 std::function<void(std::vector<int64_t>*)> postprocessor);
392
393 // These "postprocessing" steps will be applied in REVERSE order of
394 // registration to all solution passed to the callbacks.
396 std::function<void(CpSolverResponse*)> postprocessor);
397
398 // These "postprocessing" steps will only be applied after the others to the
399 // solution returned by GetResponse().
401 std::function<void(CpSolverResponse*)> postprocessor);
402
403 // Similar to the one above, but this one has access to the local model used
404 // to create the response. It can thus extract statistics and fill them.
406 std::function<void(Model*, CpSolverResponse*)> postprocessor);
407
408 // Calls all registered AddStatisticsPostprocessor() with the given model on
409 // the given response.
410 void FillSolveStatsInResponse(Model* model, CpSolverResponse* response);
411
412 // Adds a callback that will be called on each new solution (for
413 // satisfiability problem) or each improving new solution (for an optimization
414 // problem). Returns its id so it can be unregistered if needed.
415 //
416 // Note that adding a callback is not free since the solution will be
417 // post-solved before this is called.
418 //
419 // Note that currently the class is waiting for the callback to finish before
420 // accepting any new updates. That could be changed if needed.
422 std::function<void(const CpSolverResponse&)> callback);
423 void UnregisterCallback(int callback_id);
424
425 // Adds a callback that will be called on each update to the objective (either
426 // when a new improving solution is found, or when the bounds are updated).
427 // This callback does not provide the postsolved solution, so it is relatively
428 // cheap.
430 std::function<void(const SolverStatusChangeInfo&)> callback);
431
432 // Adds an inline callback that will be called on each new solution (for
433 // satisfiability problem) or each improving new solution (for an optimization
434 // problem). Returns its id so it can be unregistered if needed.
435 //
436 // Note that adding a callback is not free since the solution will be
437 // post-solved before this is called.
438 //
439 // Note that currently the class is waiting for the callback to finish before
440 // accepting any new updates. That could be changed if needed.
441 int AddLogCallback(
442 std::function<std::string(const CpSolverResponse&)> callback);
443 void UnregisterLogCallback(int callback_id);
444
445 // Adds a callback that will be called on each new best objective bound
446 // found. Returns its id so it can be unregistered if needed.
447 //
448 // Note that currently the class is waiting for the callback to finish before
449 // accepting any new updates. That could be changed if needed.
450 int AddBestBoundCallback(std::function<void(double)> callback);
451 void UnregisterBestBoundCallback(int callback_id);
452
453 // The "inner" objective is the CpModelProto objective without scaling/offset.
454 // Note that these bound correspond to valid bound for the problem of finding
455 // a strictly better objective than the current one. Thus the lower bound is
456 // always a valid bound for the global problem, but the upper bound is NOT.
457 //
458 // This is always the last bounds in "always_synchronize" mode, otherwise it
459 // correspond to the bounds at the last Synchronize() call.
460 void Synchronize();
461 IntegerValue GetInnerObjectiveLowerBound();
462 IntegerValue GetInnerObjectiveUpperBound();
464 if (solution_pool_.BestSolutions().NumSolutions() > 0) {
465 return solution_pool_.BestSolutions().GetBestRank();
466 } else {
468 }
469 }
470
471 // Returns the current best solution inner objective value or kInt64Max if
472 // there is no solution.
473 IntegerValue BestSolutionInnerObjectiveValue();
474
475 // Returns the integral of the log of the absolute gap over deterministic
476 // time. This is mainly used to compare how fast the gap closes on a
477 // particular instance. Or to evaluate how efficient our LNS code is improving
478 // solution.
479 //
480 // Note: The integral will start counting on the first UpdateGapIntegral()
481 // call, since before the difference is assumed to be zero.
482 //
483 // Important: To report a proper deterministic integral, we only update it
484 // on UpdateGapIntegral() which should be called in the main subsolver
485 // synchronization loop.
486 //
487 // Note(user): In the litterature, people use the relative gap to the optimal
488 // solution (or the best known one), but this is ill defined in many case
489 // (like if the optimal cost is zero), so I prefer this version.
490 double GapIntegral() const;
491 void UpdateGapIntegral();
492
493 // Sets this to true to have the "real" but non-deterministic primal integral.
494 // If this is true, then there is no need to manually call
495 // UpdateGapIntegral() but it is not an issue to do so.
497
498 // Sets this to false, it you want new solutions to wait for the Synchronize()
499 // call.
500 // The default 'true' indicates that all solutions passed through
501 // NewSolution() are always propagated to the best response and to the
502 // solution manager.
503 void SetSynchronizationMode(bool always_synchronize);
504
505 // Updates the inner objective bounds.
506 void UpdateInnerObjectiveBounds(const std::string& update_info,
507 IntegerValue lb, IntegerValue ub);
508
509 // Reads the new solution from the response and update our state. For an
510 // optimization problem, we only do something if the solution is strictly
511 // improving. Returns a shared pointer to the solution that was potentially
512 // stored in the repository.
513 std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>
514 NewSolution(absl::Span<const int64_t> solution_values,
515 absl::string_view solution_info, Model* model = nullptr,
516 int source_id = -1);
517
518 // Changes the solution to reflect the fact that the "improving" problem is
519 // infeasible. This means that if we have a solution, we have proven
520 // optimality, otherwise the global problem is infeasible.
521 //
522 // Note that this shouldn't be called before the solution is actually
523 // reported. We check for this case in NewSolution().
524 void NotifyThatImprovingProblemIsInfeasible(absl::string_view worker_info);
525
526 // Adds to the shared response a subset of assumptions that are enough to
527 // make the problem infeasible.
528 void AddUnsatCore(const std::vector<int>& core);
529
530 // Returns true if we found the optimal solution or the problem was proven
531 // infeasible. Note that if the gap limit is reached, we will also report
532 // OPTIMAL and consider the problem solved.
533 bool ProblemIsSolved() const;
534
535 bool HasFeasibleSolution() const {
536 return solution_pool_.BestSolutions().NumSolutions() > 0;
537 }
538
539 // Returns the underlying solution repository where we keep a set of best
540 // solutions.
541 const SharedSolutionPool& SolutionPool() const { return solution_pool_; }
542
543 // Debug only. Set dump prefix for solutions written to file.
544 void set_dump_prefix(absl::string_view dump_prefix) {
545 dump_prefix_ = dump_prefix;
546 }
547
548 // Wrapper around our SolverLogger, but protected by mutex.
549 void LogMessage(absl::string_view prefix, absl::string_view message);
550 void LogMessageWithThrottling(absl::string_view prefix,
551 absl::string_view message);
552 bool LoggingIsEnabled() const;
553
554 void AppendResponseToBeMerged(const CpSolverResponse& response);
555
557 return &first_solution_solvers_should_stop_;
558 }
559
560 // We just store a loaded DebugSolution here. Note that this is supposed to be
561 // stored once and then never change, so we do not need a mutex.
562 void LoadDebugSolution(absl::Span<const int64_t> solution) {
563 debug_solution_.assign(solution.begin(), solution.end());
564 }
565 const std::vector<int64_t>& DebugSolution() const { return debug_solution_; }
566
567 private:
568 void TestGapLimitsIfNeeded() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
569 void FillObjectiveValuesInResponse(CpSolverResponse* response) const
570 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
571 void UpdateGapIntegralInternal() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
572
573 SolverStatusChangeInfo GetSolverStatusChangeInfo()
574 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
575
576 void UpdateBestStatus(const CpSolverStatus& status)
577 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
578
579 // Generates a response for callbacks and GetResponse().
580 CpSolverResponse GetResponseInternal(
581 absl::Span<const int64_t> variable_values,
582 absl::string_view solution_info) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
583
584 const SatParameters& parameters_;
585 const WallTimer& wall_timer_;
586 ModelSharedTimeLimit* shared_time_limit_;
587 ModelRandomGenerator* random_;
588 CpObjectiveProto const* objective_or_null_ = nullptr;
589
590 mutable absl::Mutex mutex_;
591
592 // Gap limits.
593 double absolute_gap_limit_ ABSL_GUARDED_BY(mutex_) = 0.0;
594 double relative_gap_limit_ ABSL_GUARDED_BY(mutex_) = 0.0;
595
596 CpSolverStatus best_status_ ABSL_GUARDED_BY(mutex_) = CpSolverStatus::UNKNOWN;
597 CpSolverStatus synchronized_best_status_ ABSL_GUARDED_BY(mutex_) =
599 std::vector<int> unsat_cores_ ABSL_GUARDED_BY(mutex_);
600 SharedSolutionPool solution_pool_; // Thread-safe.
601
602 int num_solutions_ ABSL_GUARDED_BY(mutex_) = 0;
603 int64_t inner_objective_lower_bound_ ABSL_GUARDED_BY(mutex_) =
604 std::numeric_limits<int64_t>::min();
605 int64_t inner_objective_upper_bound_ ABSL_GUARDED_BY(mutex_) =
606 std::numeric_limits<int64_t>::max();
607 int64_t best_solution_objective_value_ ABSL_GUARDED_BY(mutex_) =
608 std::numeric_limits<int64_t>::max();
609
610 bool always_synchronize_ ABSL_GUARDED_BY(mutex_) = true;
611 IntegerValue synchronized_inner_objective_lower_bound_ ABSL_GUARDED_BY(
612 mutex_) = IntegerValue(std::numeric_limits<int64_t>::min());
613 IntegerValue synchronized_inner_objective_upper_bound_ ABSL_GUARDED_BY(
614 mutex_) = IntegerValue(std::numeric_limits<int64_t>::max());
615
616 bool update_integral_on_each_change_ ABSL_GUARDED_BY(mutex_) = false;
617 double gap_integral_ ABSL_GUARDED_BY(mutex_) = 0.0;
618 double last_absolute_gap_ ABSL_GUARDED_BY(mutex_) = 0.0;
619 double last_gap_integral_time_stamp_ ABSL_GUARDED_BY(mutex_) = 0.0;
620
621 int next_callback_id_ ABSL_GUARDED_BY(mutex_) = 0;
622 std::vector<std::pair<int, std::function<void(const CpSolverResponse&)>>>
623 callbacks_ ABSL_GUARDED_BY(mutex_);
624
625 int next_search_log_callback_id_ ABSL_GUARDED_BY(mutex_) = 0;
626 std::vector<
627 std::pair<int, std::function<std::string(const CpSolverResponse&)>>>
628 search_log_callbacks_ ABSL_GUARDED_BY(mutex_);
629
630 int next_best_bound_callback_id_ ABSL_GUARDED_BY(mutex_) = 0;
631 std::vector<std::pair<int, std::function<void(double)>>> best_bound_callbacks_
632 ABSL_GUARDED_BY(mutex_);
633
634 std::vector<std::function<void(std::vector<int64_t>*)>>
635 solution_postprocessors_ ABSL_GUARDED_BY(mutex_);
636 std::vector<std::function<void(CpSolverResponse*)>> postprocessors_
637 ABSL_GUARDED_BY(mutex_);
638 std::vector<std::function<void(CpSolverResponse*)>> final_postprocessors_
639 ABSL_GUARDED_BY(mutex_);
640 std::vector<std::function<void(Model*, CpSolverResponse*)>>
641 statistics_postprocessors_ ABSL_GUARDED_BY(mutex_);
642 std::vector<std::function<void(const SolverStatusChangeInfo&)>>
643 status_change_callbacks_ ABSL_GUARDED_BY(mutex_);
644
645 // Dump prefix.
646 std::string dump_prefix_;
647
648 SolverLogger* logger_ ABSL_GUARDED_BY(mutex_);
649 absl::flat_hash_map<std::string, int> throttling_ids_ ABSL_GUARDED_BY(mutex_);
650
651 int bounds_logging_id_;
652 std::vector<CpSolverResponse> subsolver_responses_ ABSL_GUARDED_BY(mutex_);
653
654 std::atomic<bool> first_solution_solvers_should_stop_ = false;
655
656 std::vector<int64_t> debug_solution_;
657};
658
659// This class manages a pool of lower and upper bounds on a set of variables in
660// a parallel context.
662 public:
663 explicit SharedBoundsManager(const CpModelProto& model_proto);
664
665 // Reports a set of locally improved variable bounds to the shared bounds
666 // manager. The manager will compare these bounds changes against its
667 // global state, and incorporate the improving ones.
668 void ReportPotentialNewBounds(const std::string& worker_name,
669 absl::Span<const int> variables,
670 absl::Span<const int64_t> new_lower_bounds,
671 absl::Span<const int64_t> new_upper_bounds);
672
673 // If we solved a small independent component of the full problem, then we can
674 // in most situation fix the solution on this subspace.
675 //
676 // Note that because there can be more than one optimal solution on an
677 // independent subproblem, it is important to do that in a locked fashion, and
678 // reject future incompatible fixing.
679 //
680 // Note that this do not work with symmetries. And for now we don't call it
681 // when this is the case.
682 void FixVariablesFromPartialSolution(absl::Span<const int64_t> solution,
683 absl::Span<const int> variables_to_fix);
684
685 // Returns a new id to be used in GetChangedBounds(). This is just an ever
686 // increasing sequence starting from zero. Note that the class is not designed
687 // to have too many of these.
688 int RegisterNewId();
689
690 // When called, returns the set of bounds improvements since
691 // the last time this method was called with the same id.
692 void GetChangedBounds(int id, std::vector<int>* variables,
693 std::vector<int64_t>* new_lower_bounds,
694 std::vector<int64_t>* new_upper_bounds);
695
696 // This should not be called too often as it lock the class for
697 // O(num_variables) time.
698 void UpdateDomains(std::vector<Domain>* domains);
699
700 // Publishes any new bounds so that GetChangedBounds() will reflect the latest
701 // state.
702 void Synchronize();
703
704 void LogStatistics(SolverLogger* logger);
705 int NumBoundsExported(absl::string_view worker_name);
706
707 // If non-empty, we will check that all bounds update contains this solution.
708 // Note that this might fail once we reach optimality and we might have wrong
709 // bounds, but if it fail before that it can help find bugs.
710 void LoadDebugSolution(absl::Span<const int64_t> solution) {
711 debug_solution_.assign(solution.begin(), solution.end());
712 }
713
714 // Debug only. Set dump prefix for solutions written to file.
715 void set_dump_prefix(absl::string_view dump_prefix) {
716 dump_prefix_ = dump_prefix;
717 }
718
719 private:
720 const int num_variables_;
721 const CpModelProto& model_proto_;
722
723 absl::Mutex mutex_;
724
725 // These are always up to date.
726 std::vector<int64_t> lower_bounds_ ABSL_GUARDED_BY(mutex_);
727 std::vector<int64_t> upper_bounds_ ABSL_GUARDED_BY(mutex_);
728 SparseBitset<int> changed_variables_since_last_synchronize_
729 ABSL_GUARDED_BY(mutex_);
730 int64_t total_num_improvements_ ABSL_GUARDED_BY(mutex_) = 0;
731
732 // These are only updated on Synchronize().
733 std::vector<int64_t> synchronized_lower_bounds_ ABSL_GUARDED_BY(mutex_);
734 std::vector<int64_t> synchronized_upper_bounds_ ABSL_GUARDED_BY(mutex_);
735 std::deque<SparseBitset<int>> id_to_changed_variables_
736 ABSL_GUARDED_BY(mutex_);
737
738 // We track the number of bounds exported by each solver, and the "extra"
739 // bounds pushed due to symmetries.
740 struct Counters {
741 int64_t num_exported = 0;
742 int64_t num_symmetric = 0;
743 };
744 absl::btree_map<std::string, Counters> bounds_exported_
745 ABSL_GUARDED_BY(mutex_);
746
747 // Symmetry info.
748 bool has_symmetry_ = false;
749 std::vector<int> var_to_representative_; // Identity if not touched.
750 std::vector<int> var_to_orbit_index_;
751 CompactVectorVector<int, int> orbits_;
752
753 std::vector<int64_t> debug_solution_;
754 std::string dump_prefix_;
755 int export_counter_ = 0;
756};
757
758// Emit a stream of clauses in batches without duplicates. Each batch has a
759// fixed number of literals, containing the smallest clauses added.
760// It has a finite size internal buffer that is a small multiple of the batch
761// size.
762//
763// This uses a finite buffer, so some clauses may be dropped if we generate too
764// many more than we export, but that is rarely a problem because if we drop a
765// clause on a worker, one of the following will most likely happen:
766// 1. Some other worker learns the clause and shares it later.
767// 2. All other workers also learn and drop the clause.
768// 3. No other worker learns the clause, so it was not that helpful anyway.
769//
770// Note that this uses literals as encoded in a cp_model.proto. Thus, the
771// literals can be negative numbers.
772//
773// TODO(user): This class might not want to live in this file now it no
774// longer needs to be thread-safe.
776 public:
777 static constexpr int kMinClauseSize = 3;
778 static constexpr int kMaxClauseSize = 32;
779 static constexpr int kMinLbd = 2;
780 static constexpr int kMaxLbd = 5;
781 // Export 4KiB of clauses per batch.
782 static constexpr int kMaxLiteralsPerBatch = 4096 / sizeof(int);
783
785 // Move only - this is an expensive class to copy.
788
789 // Adds the clause to a future batch and returns true if the clause is new,
790 // otherwise returns false.
791 bool Add(absl::Span<const int> clause, int lbd = 2);
792
793 // Stop a clause being added to future batches.
794 // Returns true if the clause is new.
795 // This is approximate and can have false positives and negatives, it is still
796 // guaranteed to prevent adding the same clause twice to the next batch.
797 bool BlockClause(absl::Span<const int> clause);
798
799 // Returns a set of clauses totalling up to kMaxLiteralsPerBatch and clears
800 // the internal buffer.
801 // Increases the LBD threshold if the batch is underfull, and decreases it if
802 // too many clauses were dropped.
804
806 old_fingerprints_.clear();
807 fingerprints_.clear();
808 fingerprints_.reserve(kMaxFingerprints);
809 }
810
811 // Returns the number of buffered literals in clauses of a given size.
812 int NumLiteralsOfSize(int size) const;
813 int NumBufferedLiterals() const;
814
815 int lbd_threshold() const { return lbd_threshold_; }
816 void set_lbd_threshold(int lbd_threshold) { lbd_threshold_ = lbd_threshold; }
817
818 // Computes a hash that is independent of the order of literals in the clause.
819 static size_t HashClause(absl::Span<const int> clause, size_t hash_seed = 0);
820
821 private:
822 // This needs to be >> the number of clauses we can plausibly learn in
823 // a few seconds.
824 constexpr static size_t kMaxFingerprints = 1024 * 1024 / sizeof(size_t);
825 constexpr static int kNumSizes = kMaxClauseSize - kMinClauseSize + 1;
826
827 std::vector<int>* MutableBufferForSize(int size) {
828 return &clauses_by_size_[size - kMinClauseSize];
829 }
830 absl::Span<const int> BufferForSize(int size) const {
831 return clauses_by_size_[size - kMinClauseSize];
832 }
833 absl::Span<const int> NextClause(int size) const;
834 void PopClause(int size);
835 // Computes the number of clauses of a given size.
836 int NumClausesOfSize(int size) const;
837
838 int lbd_threshold_ = kMinLbd;
839 int64_t dropped_literals_since_last_batch_ = 0;
840
841 absl::flat_hash_set<size_t> fingerprints_;
842 absl::flat_hash_set<size_t> old_fingerprints_;
843 std::array<std::vector<int>, kNumSizes> clauses_by_size_;
844};
845
846// This class holds clauses found and shared by workers.
847// It is exact for binary clauses, but approximate for longer ones.
848//
849// It is thread-safe.
850//
851// Note that this uses literal as encoded in a cp_model.proto. Thus, the
852// literals can be negative numbers.
854 public:
855 explicit SharedClausesManager(bool always_synchronize);
856 void AddBinaryClause(int id, int lit1, int lit2);
857
858 // Returns new glue clauses.
859 // The spans are guaranteed to remain valid until the next call to
860 // SyncClauses().
862
863 void AddBatch(int id, CompactVectorVector<int> batch);
864
865 // Fills new_clauses with
866 // {{lit1 of clause1, lit2 of clause1},
867 // {lit1 of clause2, lit2 of clause2},
868 // ...}
869 void GetUnseenBinaryClauses(int id,
870 std::vector<std::pair<int, int>>* new_clauses);
871
872 // Ids are used to identify which worker is exporting/importing clauses.
873 int RegisterNewId(absl::string_view worker_name, bool may_terminate_early);
874
875 // Unlocks waiting binary clauses for workers if always_synchronize is false.
876 // Periodically starts a new sharing round, making glue clauses visible.
877 void Synchronize();
878
879 // For statistics, notify how many clauses where imported in that worker id
880 // database.
881 void NotifyNumImported(int id, int64_t num_imported);
882
883 // Search statistics.
884 void LogStatistics(SolverLogger* logger);
885
886 private:
887 // Returns true if `reader_id` should read batches produced by `writer_id`.
888 bool ShouldReadBatch(int reader_id, int writer_id)
889 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
890
891 static constexpr int kMinBatches = 64;
892 mutable absl::Mutex mutex_;
893
894 // Binary clauses:
895 // Cache to avoid adding the same binary clause twice.
896 absl::flat_hash_set<std::pair<int, int>> added_binary_clauses_set_
897 ABSL_GUARDED_BY(mutex_);
898 std::vector<std::pair<int, int>> added_binary_clauses_
899 ABSL_GUARDED_BY(mutex_);
900 std::vector<int> id_to_last_processed_binary_clause_ ABSL_GUARDED_BY(mutex_);
901 int last_visible_binary_clause_ ABSL_GUARDED_BY(mutex_) = 0;
902
903 // This is slightly subtle - we need to track the batches that might be
904 // currently being processed by each worker to make sure we don't erase any
905 // batch that a worker might currently be reading.
906 std::vector<int> id_to_last_returned_batch_ ABSL_GUARDED_BY(mutex_);
907 std::vector<int> id_to_last_finished_batch_ ABSL_GUARDED_BY(mutex_);
908
909 std::deque<CompactVectorVector<int>> batches_ ABSL_GUARDED_BY(mutex_);
910 // pending_batches_ contains clauses produced by individual workers that have
911 // not yet been merged into batches_, which can be read by other workers. When
912 // this is long enough they will be merged into a single batch and appended to
913 // batches_.
914 std::vector<CompactVectorVector<int>> pending_batches_
915 ABSL_GUARDED_BY(mutex_);
916 int num_full_workers_ ABSL_GUARDED_BY(mutex_) = 0;
917
918 const bool always_synchronize_ = true;
919
920 // Stats:
921 std::vector<int64_t> id_to_num_exported_ ABSL_GUARDED_BY(mutex_);
922 std::vector<int64_t> id_to_num_imported_ ABSL_GUARDED_BY(mutex_);
923 std::vector<int64_t> id_to_num_updated_ ABSL_GUARDED_BY(mutex_);
924 std::vector<std::string> id_to_worker_name_ ABSL_GUARDED_BY(mutex_);
925};
926
927// A class that allows to exchange root level bounds on linear2.
928//
929// TODO(user): Add Synchronize() support and only publish new bounds when this
930// is called.
932 public:
933 int RegisterNewId(std::string worker_name);
934 void LogStatistics(SolverLogger* logger);
935
936 // This should only contain canonicalized expression.
937 // See the code for IsCanonicalized() for the definition.
938 struct Key {
939 int vars[2];
940 IntegerValue coeffs[2];
941
943 return vars[0] >= 0 && vars[1] >= 0 && vars[0] < vars[1] &&
944 std::gcd(coeffs[0].value(), coeffs[1].value()) == 1;
945 }
946
947 bool operator==(const Key& o) const {
948 return vars[0] == o.vars[0] && vars[1] == o.vars[1] &&
949 coeffs[0] == o.coeffs[0] && coeffs[1] == o.coeffs[1];
950 }
951
952 template <typename H>
953 friend H AbslHashValue(H h, const Key& k) {
954 return H::combine(std::move(h), k.vars[0], k.vars[1], k.coeffs[0],
955 k.coeffs[1]);
956 }
957
958 template <typename Sink>
959 friend void AbslStringify(Sink& sink, const Key& k) {
960 absl::Format(&sink, "%d X%d + %d X%d", k.coeffs[0].value(), k.vars[0],
961 k.coeffs[1].value(), k.vars[1]);
962 }
963 };
964
965 // Exports new bounds on the given expr (should be canonicalized).
966 void Add(int id, Key expr, IntegerValue lb, IntegerValue ub);
967
968 // This is called less often, and maybe not every-worker that exports want to
969 // export, so we use a separate id space. Because we rely on hash map to
970 // check if a bound is new, it is not such a big deal that a worker re-read
971 // once the bounds it exported.
972 int RegisterNewImportId(std::string name);
973
974 // Returns the linear2 and their bounds.
975 // We only return changes since the last call with the same id.
976 std::vector<std::pair<Key, std::pair<IntegerValue, IntegerValue>>>
977 NewlyUpdatedBounds(int import_id);
978
979 // This is not filled by NewlyUpdatedBounds() because we want to track the
980 // bounds that were not already known by the worker at the time of the import,
981 // and we don't have this information here.
982 void NotifyNumImported(int import_id, int num) {
983 absl::MutexLock mutex_lock(mutex_);
984 import_id_to_num_imported_[import_id] += num;
985 }
986
987 private:
988 void MaybeCompressNewlyUpdateKeys() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
989
990 absl::Mutex mutex_;
991
992 // The best known bounds for each key.
993 absl::flat_hash_map<Key, std::pair<IntegerValue, IntegerValue>> shared_bounds_
994 ABSL_GUARDED_BY(mutex_);
995
996 // Ever growing list of updated position in shared_bounds_.
997 // Note that we do reduce it in MaybeCompressNewlyUpdateKeys(), but that
998 // requires all registered workers to have at least imported some bounds.
999 //
1000 // TODO(user): use indirect addressing so that newly_updated_keys_ can just
1001 // deal with indices, and it is a bit tighter memory wise? We also avoid
1002 // hash-lookups on NewlyUpdatedBounds(). But since this is only called at
1003 // level zero on new bounds, I don't think we care.
1004 std::vector<Key> newly_updated_keys_;
1005
1006 // For import.
1007 std::vector<std::string> import_id_to_name_ ABSL_GUARDED_BY(mutex_);
1008 std::vector<int> import_id_to_index_ ABSL_GUARDED_BY(mutex_);
1009 std::vector<int> import_id_to_num_imported_ ABSL_GUARDED_BY(mutex_);
1010
1011 // Just for reporting at the end of the solve.
1012 struct Stats {
1013 int64_t num_new = 0;
1014 int64_t num_update = 0;
1015 int64_t num_imported = 0; // Copy of import_id_to_num_imported_.
1016 bool empty() const {
1017 return num_new == 0 && num_update == 0 && num_imported == 0;
1018 }
1019 };
1020 std::vector<Stats> id_to_stats_ ABSL_GUARDED_BY(mutex_);
1021 std::vector<std::string> id_to_worker_name_ ABSL_GUARDED_BY(mutex_);
1022};
1023
1024// Simple class to add statistics by name and print them at the end.
1026 public:
1027 SharedStatistics() = default;
1028
1029 // Adds a bunch of stats, adding count for the same key together.
1030 void AddStats(absl::Span<const std::pair<std::string, int64_t>> stats);
1031
1032 // Logs all the added stats.
1033 void Log(SolverLogger* logger);
1034
1035 private:
1036 absl::Mutex mutex_;
1037 absl::flat_hash_map<std::string, int64_t> stats_ ABSL_GUARDED_BY(mutex_);
1038};
1039
1040template <typename ValueType>
1042 absl::MutexLock mutex_lock(mutex_);
1043 return solutions_.size();
1044}
1045
1046template <typename ValueType>
1047std::shared_ptr<const typename SharedSolutionRepository<ValueType>::Solution>
1049 absl::MutexLock mutex_lock(mutex_);
1050 if (i >= solutions_.size()) return nullptr;
1051 ++num_queried_;
1052 return solutions_[i];
1053}
1054
1055template <typename ValueType>
1057 absl::MutexLock mutex_lock(mutex_);
1058 if (solutions_.empty()) return std::numeric_limits<int64_t>::max();
1059 return solutions_[0]->rank;
1060}
1061
1062template <typename ValueType>
1063std::vector<std::shared_ptr<
1066 absl::MutexLock mutex_lock(mutex_);
1067 // Sorted by rank and unique.
1068 DCHECK(absl::c_is_sorted(solutions_,
1069 [](const std::shared_ptr<const Solution>& a,
1070 const std::shared_ptr<const Solution>& b) {
1071 return a->rank < b->rank;
1072 }));
1073 DCHECK(absl::c_adjacent_find(solutions_,
1074 [](const std::shared_ptr<const Solution>& a,
1075 const std::shared_ptr<const Solution>& b) {
1076 return *a == *b;
1077 }) == solutions_.end());
1078 std::vector<std::shared_ptr<const Solution>> result;
1079 const int num_solutions = std::min(static_cast<int>(solutions_.size()), n);
1080 result.reserve(num_solutions);
1081 for (int i = 0; i < num_solutions; ++i) {
1082 result.push_back(solutions_[i]);
1083 }
1084 return result;
1085}
1086
1087template <typename ValueType>
1089 int var_index, int solution_index) const {
1090 absl::MutexLock mutex_lock(mutex_);
1091 return solutions_[solution_index]->variable_values[var_index];
1092}
1093
1094// TODO(user): Experiments on the best distribution.
1095template <typename ValueType>
1096std::shared_ptr<const typename SharedSolutionRepository<ValueType>::Solution>
1098 absl::BitGenRef random) const {
1099 absl::MutexLock mutex_lock(mutex_);
1100 if (solutions_.empty()) return nullptr;
1101 ++num_queried_;
1102 int index = 0;
1103
1104 if (solutions_.size() > 1) {
1105 const int64_t best_rank = solutions_[0]->rank;
1106
1107 // As long as we have solution with the best objective that haven't been
1108 // explored too much, we select one uniformly. Otherwise, we select a
1109 // solution from the pool uniformly.
1110 //
1111 // Note(user): Because of the increase of num_selected, this is dependent on
1112 // the order of call. It should be fine for "determinism" because we do
1113 // generate the task of a batch always in the same order.
1114 const int kExplorationThreshold = 100;
1115
1116 // Select all the best solution with a low enough selection count.
1117 tmp_indices_.clear();
1118 for (int i = 0; i < solutions_.size(); ++i) {
1119 std::shared_ptr<const Solution> solution = solutions_[i];
1120 if (solution->rank == best_rank &&
1121 solution->num_selected <= kExplorationThreshold) {
1122 tmp_indices_.push_back(i);
1123 }
1124 }
1125
1126 if (tmp_indices_.empty()) {
1127 index = absl::Uniform<int>(random, 0, solutions_.size());
1128 } else {
1129 index = tmp_indices_[absl::Uniform<int>(random, 0, tmp_indices_.size())];
1130 }
1131 }
1132
1133 CHECK_GE(index, 0);
1134 CHECK_LT(index, solutions_.size());
1135 solutions_[index]->num_selected++;
1136 return solutions_[index];
1137}
1138
1139template <typename ValueType>
1140std::shared_ptr<const typename SharedSolutionRepository<ValueType>::Solution>
1142 std::shared_ptr<Solution> solution_ptr =
1143 std::make_shared<Solution>(std::move(solution));
1144 if (num_solutions_to_keep_ <= 0) return std::move(solution_ptr);
1145 {
1146 absl::MutexLock mutex_lock(mutex_);
1147 ++num_added_;
1148 solution_ptr->source_id = source_id_;
1149 new_solutions_.push_back(solution_ptr);
1150 }
1151 return solution_ptr;
1152}
1153
1154template <typename ValueType>
1156 std::function<void(const Solution& solution)> f) {
1157 absl::MutexLock mutex_lock(mutex_);
1158 if (new_solutions_.empty()) {
1159 const int64_t diff = num_queried_ - num_queried_at_last_sync_;
1160 num_non_improving_ += diff;
1161 num_queried_at_last_sync_ = num_queried_;
1162 return;
1163 }
1164
1165 if (f != nullptr) {
1167 &new_solutions_,
1168 [](const std::shared_ptr<Solution>& a,
1169 const std::shared_ptr<Solution>& b) { return *a < *b; });
1170 for (const auto& ptr : new_solutions_) {
1171 f(*ptr);
1172 }
1173 }
1174
1175 const int64_t old_best_rank = solutions_.empty()
1176 ? std::numeric_limits<int64_t>::max()
1177 : solutions_[0]->rank;
1178
1179 solutions_.insert(solutions_.end(), new_solutions_.begin(),
1180 new_solutions_.end());
1181 new_solutions_.clear();
1182
1183 // We use a stable sort to keep the num_selected count for the already
1184 // existing solutions (in case of duplicates).
1186 &solutions_, [](const std::shared_ptr<Solution>& a,
1187 const std::shared_ptr<Solution>& b) { return *a < *b; });
1188 const int64_t new_best_rank = solutions_[0]->rank;
1189
1190 // If we have more than num_solutions_to_keep_ solutions with the best rank,
1191 // select them via orthogonality.
1192 if (solutions_.size() > num_solutions_to_keep_ &&
1194 int num_best = 1;
1195 while (num_best < solutions_.size() &&
1196 solutions_[num_best]->rank == new_best_rank) {
1197 ++num_best;
1198 }
1199
1200 if (num_best > num_solutions_to_keep_ &&
1202 // We should only be here if a new solution (not in our current set) was
1203 // found. It could be one we saw before but forgot about. We put one
1204 // first.
1205 for (auto& solution : solutions_) {
1206 if (solution->num_selected == 0) {
1207 // TODO(user): randomize amongst new solution?
1208 std::swap(solutions_[0], solution);
1209 break;
1210 }
1211 }
1212
1213 // We are going to be in O(n^2 * solution_size + 2^n),
1214 // so keep n <= diversity_limit_.
1215 solutions_.resize(std::min(diversity_limit_, num_best));
1216
1217 // Fill the pairwise distances.
1218 const int n = solutions_.size();
1219 distances_.resize(n * n);
1220 const int size = solutions_[0]->variable_values.size();
1221 for (int i = 0; i < n; ++i) {
1222 for (int j = i + 1; j < n; ++j) {
1223 int64_t dist = 0;
1224 for (int k = 0; k < size; ++k) {
1225 if (solutions_[i]->variable_values[k] !=
1226 solutions_[j]->variable_values[k]) {
1227 ++dist;
1228 }
1229 }
1230 distances_[i * n + j] = distances_[j * n + i] = dist;
1231 }
1232 }
1233
1234 // In order to not get stuck on a subset that always maximize the sum of
1235 // orthogonality, we pick the first element (which should be a new one
1236 // thanks to the swap above), and we maximize the sum of orthogonality
1237 // with the rest.
1238 //
1239 // This way, as we find new solution, the set changes slowly.
1240 //
1241 // TODO(user): When n == num_solutions_to_keep_ + 1, there is
1242 // a faster algo thant 2^n since there is only n possible sets. Fix.
1243 const std::vector<int> selected =
1244 FindMostDiverseSubset(num_solutions_to_keep_, n, distances_, buffer_,
1245 /*always_pick_mask = */ 1);
1246
1247 DCHECK(std::is_sorted(selected.begin(), selected.end()));
1248 int new_size = 0;
1249 for (const int s : selected) {
1250 solutions_[new_size++] = std::move(solutions_[s]);
1251 }
1252 solutions_.resize(new_size);
1253
1254 if (VLOG_IS_ON(3)) {
1255 int min_count = std::numeric_limits<int>::max();
1256 int max_count = 0;
1257 for (const auto& s : solutions_) {
1258 CHECK(s != nullptr);
1259 min_count = std::min(s->num_selected, min_count);
1260 max_count = std::max(s->num_selected, max_count);
1261 }
1262 int64_t score = 0;
1263 for (const int i : selected) {
1264 for (const int j : selected) {
1265 if (i > j) score += distances_[i * n + j];
1266 }
1267 }
1268 LOG(INFO) << name_ << " rank=" << new_best_rank
1269 << " num=" << num_solutions_to_keep_ << "/" << num_best
1270 << " orthogonality=" << score << " count=[" << min_count
1271 << ", " << max_count << "]";
1272 }
1273 }
1274 }
1275
1276 if (solutions_.size() > num_solutions_to_keep_) {
1277 solutions_.resize(num_solutions_to_keep_);
1278 }
1279 CHECK(!solutions_.empty());
1280 if (!solutions_.empty()) {
1281 VLOG(4) << "Solution pool update:" << " num_solutions=" << solutions_.size()
1282 << " min_rank=" << solutions_[0]->rank
1283 << " max_rank=" << solutions_.back()->rank;
1284 }
1285
1286 num_synchronization_++;
1287 if (new_best_rank < old_best_rank) {
1288 num_non_improving_ = 0;
1289 } else {
1290 const int64_t diff = num_queried_ - num_queried_at_last_sync_;
1291 num_non_improving_ += diff;
1292 }
1293 num_queried_at_last_sync_ = num_queried_;
1294}
1295
1296// Thread-safe.
1298 public:
1300
1301 // Each LratProofHandler should call this to get a unique "worker ID".
1302 int NewSubSolverId();
1303
1305 bool lrat_check_enabled, bool drat_check_enabled,
1306 int num_assumed_clauses,
1307 double walltime_in_seconds);
1308
1309 void NewProofFile(absl::string_view filename);
1310 std::vector<std::string> GetProofFilenames();
1311
1312 void Log(SolverLogger* logger);
1313
1314 private:
1315 absl::Mutex mutex_;
1316 int num_subsolvers_ ABSL_GUARDED_BY(mutex_);
1317 int num_valid_proofs_ ABSL_GUARDED_BY(mutex_);
1318 int num_invalid_proofs_ ABSL_GUARDED_BY(mutex_);
1319 int num_unknown_proofs_ ABSL_GUARDED_BY(mutex_);
1320 bool lrat_check_enabled_ ABSL_GUARDED_BY(mutex_);
1321 bool drat_check_enabled_ ABSL_GUARDED_BY(mutex_);
1322 int num_assumed_clauses_ ABSL_GUARDED_BY(mutex_);
1323 double walltime_in_seconds_ ABSL_GUARDED_BY(mutex_);
1324 std::vector<std::string> proof_filenames_ ABSL_GUARDED_BY(mutex_);
1325};
1326
1327} // namespace sat
1328} // namespace operations_research
1329
1330#endif // ORTOOLS_SAT_SYNCHRONIZATION_H_
void LoadDebugSolution(absl::Span< const int64_t > solution)
int NumBoundsExported(absl::string_view worker_name)
void set_dump_prefix(absl::string_view dump_prefix)
void UpdateDomains(std::vector< Domain > *domains)
void ReportPotentialNewBounds(const std::string &worker_name, absl::Span< const int > variables, absl::Span< const int64_t > new_lower_bounds, absl::Span< const int64_t > new_upper_bounds)
void FixVariablesFromPartialSolution(absl::Span< const int64_t > solution, absl::Span< const 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)
SharedBoundsManager(const CpModelProto &model_proto)
int RegisterNewId(absl::string_view worker_name, bool may_terminate_early)
void AddBinaryClause(int id, int lit1, int lit2)
void AddBatch(int id, CompactVectorVector< int > batch)
void GetUnseenBinaryClauses(int id, std::vector< std::pair< int, int > > *new_clauses)
const CompactVectorVector< int > & GetUnseenClauses(int id)
void NotifyNumImported(int id, int64_t num_imported)
void AddSolution(const std::vector< double > &lp_solution)
void NewLPSolution(std::vector< double > lp_solution)
void NotifyNumImported(int import_id, int num)
void NewProofFile(absl::string_view filename)
void NewSubsolverProofStatus(DratChecker::Status status, bool lrat_check_enabled, bool drat_check_enabled, int num_assumed_clauses, double walltime_in_seconds)
void AddSolution(std::vector< int64_t > solution, int num_violations)
void AppendResponseToBeMerged(const CpSolverResponse &response)
void AddUnsatCore(const std::vector< int > &core)
const std::vector< int64_t > & DebugSolution() const
void AddStatisticsPostprocessor(std::function< void(Model *, CpSolverResponse *)> postprocessor)
void LogMessage(absl::string_view prefix, absl::string_view message)
void SetSynchronizationMode(bool always_synchronize)
int AddLogCallback(std::function< std::string(const CpSolverResponse &)> callback)
int AddBestBoundCallback(std::function< void(double)> callback)
void LoadDebugSolution(absl::Span< const int64_t > solution)
const SharedSolutionPool & SolutionPool() const
void AddStatusChangeCallback(std::function< void(const SolverStatusChangeInfo &)> callback)
void InitializeObjective(const CpModelProto &cp_model)
void SetGapLimitsFromParameters(const SatParameters &parameters)
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void set_dump_prefix(absl::string_view dump_prefix)
void AddResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
int AddSolutionCallback(std::function< void(const CpSolverResponse &)> callback)
void AddSolutionPostprocessor(std::function< void(std::vector< int64_t > *)> postprocessor)
void FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > NewSolution(absl::Span< const int64_t > solution_values, absl::string_view solution_info, Model *model=nullptr, int source_id=-1)
void LogMessageWithThrottling(absl::string_view prefix, absl::string_view message)
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
void NotifyThatImprovingProblemIsInfeasible(absl::string_view worker_info)
const SharedSolutionRepository< int64_t > & BestSolutions() const
SharedSolutionPool(const SatParameters &parameters_)
void AddTableStats(std::vector< std::vector< std::string > > *table) const
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > GetSolutionToImprove(absl::BitGenRef random) const
std::shared_ptr< const SharedSolutionRepository< int64_t >::Solution > Add(SharedSolutionRepository< int64_t >::Solution solution)
std::vector< int64_t > ABSL_GUARDED_BY(mutex_) buffer_
std::shared_ptr< const Solution > GetRandomBiasedSolution(absl::BitGenRef random) const
int64_t num_queried_ ABSL_GUARDED_BY(mutex_)=0
std::vector< std::shared_ptr< const Solution > > GetBestNSolutions(int n) const
std::vector< int > tmp_indices_ ABSL_GUARDED_BY(mutex_)
std::shared_ptr< const Solution > GetSolution(int index) const
std::shared_ptr< const Solution > Add(Solution solution)
std::vector< std::shared_ptr< Solution > > new_solutions_ ABSL_GUARDED_BY(mutex_)
std::vector< std::shared_ptr< Solution > > solutions_ ABSL_GUARDED_BY(mutex_)
std::vector< std::string > TableLineStats() const
SharedSolutionRepository(int num_solutions_to_keep, absl::string_view name="", int source_id=-1)
int64_t num_queried_at_last_sync_ ABSL_GUARDED_BY(mutex_)=0
int64_t num_non_improving_ ABSL_GUARDED_BY(mutex_)=0
std::vector< int64_t > ABSL_GUARDED_BY(mutex_) distances_
void Synchronize(std::function< void(const Solution &solution)> f=nullptr)
ValueType GetVariableValueInSolution(int var_index, int solution_index) const
int64_t num_synchronization_ ABSL_GUARDED_BY(mutex_)=0
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
bool BlockClause(absl::Span< const int > clause)
UniqueClauseStream(const UniqueClauseStream &)=delete
bool Add(absl::Span< const int > clause, int lbd=2)
UniqueClauseStream(UniqueClauseStream &&)=default
void STLStableSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:72
std::vector< int > FindMostDiverseSubset(int k, int n, absl::Span< const int64_t > distances, std::vector< int64_t > &buffer, int always_pick_mask)
Definition util.cc:973
std::string FormatName(absl::string_view name)
Definition util.h:342
OR-Tools root namespace.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
std::string FormatCounter(int64_t num)
Definition logging.cc:30
STL namespace.
friend void AbslStringify(Sink &sink, const Key &k)