Google OR-Tools v9.11
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-2024 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 OR_TOOLS_SAT_SYNCHRONIZATION_H_
15#define OR_TOOLS_SAT_SYNCHRONIZATION_H_
16
17#include <array>
18#include <atomic>
19#include <cstddef>
20#include <cstdint>
21#include <deque>
22#include <functional>
23#include <limits>
24#include <string>
25#include <utility>
26#include <vector>
27
28#include "absl/base/thread_annotations.h"
29#include "absl/container/btree_map.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/flat_hash_set.h"
32#include "absl/random/bit_gen_ref.h"
33#include "absl/random/random.h"
34#include "absl/strings/string_view.h"
35#include "absl/synchronization/mutex.h"
36#include "absl/time/time.h"
37#include "absl/types/span.h"
40#include "ortools/base/timer.h"
41#include "ortools/sat/cp_model.pb.h"
42#include "ortools/sat/integer.h"
43#include "ortools/sat/model.h"
44#include "ortools/sat/sat_parameters.pb.h"
45#include "ortools/sat/util.h"
46#include "ortools/util/bitset.h"
49
50namespace operations_research {
51namespace sat {
52
53// Thread-safe. Keeps a set of n unique best solution found so far.
54//
55// TODO(user): Maybe add some criteria to only keep solution with an objective
56// really close to the best solution.
57template <typename ValueType>
59 public:
60 explicit SharedSolutionRepository(int num_solutions_to_keep,
61 absl::string_view name = "")
62 : name_(name), num_solutions_to_keep_(num_solutions_to_keep) {}
63
64 // The solution format used by this class.
65 struct Solution {
66 // Solution with lower "rank" will be preferred
67 //
68 // TODO(user): Some LNS code assume that for the SharedSolutionRepository
69 // this rank is actually the unscaled internal minimization objective.
70 // Remove this assumptions by simply recomputing this value since it is not
71 // too costly to do so.
72 int64_t rank = 0;
73
74 std::vector<ValueType> variable_values;
75
76 std::string info;
77
78 // Number of time this was returned by GetRandomBiasedSolution(). We use
79 // this information during the selection process.
80 //
81 // Should be private: only SharedSolutionRepository should modify this.
82 mutable int num_selected = 0;
83
84 bool operator==(const Solution& other) const {
85 return rank == other.rank && variable_values == other.variable_values;
86 }
87 bool operator<(const Solution& other) const {
88 if (rank != other.rank) {
89 return rank < other.rank;
90 }
91 return variable_values < other.variable_values;
92 }
93 };
94
95 // Returns the number of current solution in the pool. This will never
96 // decrease.
97 int NumSolutions() const;
98
99 // Returns the solution #i where i must be smaller than NumSolutions().
100 Solution GetSolution(int index) const;
101
102 // Returns the rank of the best known solution.
103 // You shouldn't call this if NumSolutions() is zero.
104 int64_t GetBestRank() const;
105
106 // Returns the variable value of variable 'var_index' from solution
107 // 'solution_index' where solution_index must be smaller than NumSolutions()
108 // and 'var_index' must be smaller than number of variables.
109 ValueType GetVariableValueInSolution(int var_index, int solution_index) const;
110
111 // Returns a random solution biased towards good solutions.
112 Solution GetRandomBiasedSolution(absl::BitGenRef random) const;
113
114 // Add a new solution. Note that it will not be added to the pool of solution
115 // right away. One must call Synchronize for this to happen. In order to be
116 // deterministic, this will keep all solutions until Synchronize() is called,
117 // so we need to be careful not to generate too many solutions at once.
118 void Add(const Solution& solution);
119
120 // Updates the current pool of solution with the one recently added. Note that
121 // we use a stable ordering of solutions, so the final pool will be
122 // independent on the order of the calls to AddSolution() provided that the
123 // set of added solutions is the same.
124 //
125 // Works in O(num_solutions_to_keep_).
127
128 std::vector<std::string> TableLineStats() const {
129 absl::MutexLock mutex_lock(&mutex_);
130 return {FormatName(name_), FormatCounter(num_added_),
131 FormatCounter(num_queried_), FormatCounter(num_synchronization_)};
132 }
133
134 protected:
135 const std::string name_;
137
138 mutable absl::Mutex mutex_;
139 int64_t num_added_ ABSL_GUARDED_BY(mutex_) = 0;
140 mutable int64_t num_queried_ ABSL_GUARDED_BY(mutex_) = 0;
141 int64_t num_synchronization_ ABSL_GUARDED_BY(mutex_) = 0;
142
143 // Our two solutions pools, the current one and the new one that will be
144 // merged into the current one on each Synchronize() calls.
145 mutable std::vector<int> tmp_indices_ ABSL_GUARDED_BY(mutex_);
146 std::vector<Solution> solutions_ ABSL_GUARDED_BY(mutex_);
147 std::vector<Solution> new_solutions_ ABSL_GUARDED_BY(mutex_);
148};
149
151 public:
152 explicit SharedLPSolutionRepository(int num_solutions_to_keep)
153 : SharedSolutionRepository<double>(num_solutions_to_keep,
154 "lp solutions") {}
155
156 void NewLPSolution(std::vector<double> lp_solution);
157};
158
159// Set of partly filled solutions. They are meant to be finished by some lns
160// worker.
161//
162// The solutions are stored as a vector of doubles. The value at index i
163// represents the solution value of model variable indexed i. Note that some
164// values can be infinity which should be interpreted as 'unknown' solution
165// value for that variable. These solutions can not necessarily be completed to
166// complete feasible solutions.
168 public:
169 // This adds a new solution to the stack.
170 // Note that we keep the last 100 ones at most.
171 void AddSolution(const std::vector<double>& lp_solution);
172
173 bool HasSolution() const;
174
175 // If there are no solution, this return an empty vector.
176 std::vector<double> PopLast();
177
178 std::vector<std::string> TableLineStats() const {
179 absl::MutexLock mutex_lock(&mutex_);
180 return {FormatName("pump"), FormatCounter(num_added_),
181 FormatCounter(num_queried_)};
182 }
183
184 private:
185 mutable absl::Mutex mutex_;
186 std::deque<std::vector<double>> solutions_ ABSL_GUARDED_BY(mutex_);
187 int64_t num_added_ ABSL_GUARDED_BY(mutex_) = 0;
188 mutable int64_t num_queried_ ABSL_GUARDED_BY(mutex_) = 0;
189};
190
191// Used by FillSolveStatsInResponse() to extract statistic to put in a
192// CpSolverResponse. The callbacks registered here are supposed to only modify
193// the statistic fields, nothing else.
195 std::vector<std::function<void(CpSolverResponse*)>> callbacks;
196};
197
198// Get the solve statistics from the associated model classes and fills the
199// response with them.
200void FillSolveStatsInResponse(Model* model, CpSolverResponse* response);
201
202// Manages the global best response kept by the solver. This class is
203// responsible for logging the progress of the solutions and bounds as they are
204// found.
205//
206// All functions are thread-safe except if specified otherwise.
208 public:
210
211 // Loads the initial objective bounds and keep a reference to the objective to
212 // properly display the scaled bounds. This is optional if the model has no
213 // objective.
214 //
215 // This function is not thread safe.
216 void InitializeObjective(const CpModelProto& cp_model);
217
218 // Reports OPTIMAL and stop the search if any gap limit are specified and
219 // crossed. By default, we only stop when we have the true optimal, which is
220 // well defined since we are solving our pure integer problem exactly.
221 void SetGapLimitsFromParameters(const SatParameters& parameters);
222
223 // Returns the current solver response. That is the best known response at the
224 // time of the call with the best feasible solution and objective bounds.
225 //
226 // We will do more postprocessing by calling all the
227 // AddFinalSolutionPostprocessor() postprocesors. Note that the response given
228 // to the AddSolutionCallback() will not call them.
229 CpSolverResponse GetResponse();
230
231 // These will be called in REVERSE order on any feasible solution returned
232 // to the user.
234 std::function<void(std::vector<int64_t>*)> postprocessor);
235
236 // These "postprocessing" steps will be applied in REVERSE order of
237 // registration to all solution passed to the callbacks.
239 std::function<void(CpSolverResponse*)> postprocessor);
240
241 // These "postprocessing" steps will only be applied after the others to the
242 // solution returned by GetResponse().
244 std::function<void(CpSolverResponse*)> postprocessor);
245
246 // Adds a callback that will be called on each new solution (for
247 // satisfiability problem) or each improving new solution (for an optimization
248 // problem). Returns its id so it can be unregistered if needed.
249 //
250 // Note that adding a callback is not free since the solution will be
251 // post-solved before this is called.
252 //
253 // Note that currently the class is waiting for the callback to finish before
254 // accepting any new updates. That could be changed if needed.
256 std::function<void(const CpSolverResponse&)> callback);
257 void UnregisterCallback(int callback_id);
258
259 // Adds an inline callback that will be called on each new solution (for
260 // satisfiability problem) or each improving new solution (for an optimization
261 // problem). Returns its id so it can be unregistered if needed.
262 //
263 // Note that adding a callback is not free since the solution will be
264 // post-solved before this is called.
265 //
266 // Note that currently the class is waiting for the callback to finish before
267 // accepting any new updates. That could be changed if needed.
268 int AddLogCallback(
269 std::function<std::string(const CpSolverResponse&)> callback);
270 void UnregisterLogCallback(int callback_id);
271
272 // Adds a callback that will be called on each new best objective bound
273 // found. Returns its id so it can be unregistered if needed.
274 //
275 // Note that currently the class is waiting for the callback to finish before
276 // accepting any new updates. That could be changed if needed.
277 int AddBestBoundCallback(std::function<void(double)> callback);
278 void UnregisterBestBoundCallback(int callback_id);
279
280 // The "inner" objective is the CpModelProto objective without scaling/offset.
281 // Note that these bound correspond to valid bound for the problem of finding
282 // a strictly better objective than the current one. Thus the lower bound is
283 // always a valid bound for the global problem, but the upper bound is NOT.
284 //
285 // This is always the last bounds in "always_synchronize" mode, otherwise it
286 // correspond to the bounds at the last Synchronize() call.
287 void Synchronize();
288 IntegerValue GetInnerObjectiveLowerBound();
289 IntegerValue GetInnerObjectiveUpperBound();
290
291 // Returns the current best solution inner objective value or kInt64Max if
292 // there is no solution.
293 IntegerValue BestSolutionInnerObjectiveValue();
294
295 // Returns the integral of the log of the absolute gap over deterministic
296 // time. This is mainly used to compare how fast the gap closes on a
297 // particular instance. Or to evaluate how efficient our LNS code is improving
298 // solution.
299 //
300 // Note: The integral will start counting on the first UpdateGapIntegral()
301 // call, since before the difference is assumed to be zero.
302 //
303 // Important: To report a proper deterministic integral, we only update it
304 // on UpdateGapIntegral() which should be called in the main subsolver
305 // synchronization loop.
306 //
307 // Note(user): In the litterature, people use the relative gap to the optimal
308 // solution (or the best known one), but this is ill defined in many case
309 // (like if the optimal cost is zero), so I prefer this version.
310 double GapIntegral() const;
311 void UpdateGapIntegral();
312
313 // Sets this to true to have the "real" but non-deterministic primal integral.
314 // If this is true, then there is no need to manually call
315 // UpdateGapIntegral() but it is not an issue to do so.
317
318 // Sets this to false, it you want new solutions to wait for the Synchronize()
319 // call.
320 // The default 'true' indicates that all solutions passed through
321 // NewSolution() are always propagated to the best response and to the
322 // solution manager.
323 void SetSynchronizationMode(bool always_synchronize);
324
325 // Updates the inner objective bounds.
326 void UpdateInnerObjectiveBounds(const std::string& update_info,
327 IntegerValue lb, IntegerValue ub);
328
329 // Reads the new solution from the response and update our state. For an
330 // optimization problem, we only do something if the solution is strictly
331 // improving.
332 void NewSolution(absl::Span<const int64_t> solution_values,
333 const std::string& solution_info, Model* model = nullptr);
334
335 // Changes the solution to reflect the fact that the "improving" problem is
336 // infeasible. This means that if we have a solution, we have proven
337 // optimality, otherwise the global problem is infeasible.
338 //
339 // Note that this shouldn't be called before the solution is actually
340 // reported. We check for this case in NewSolution().
341 void NotifyThatImprovingProblemIsInfeasible(const std::string& worker_info);
342
343 // Adds to the shared response a subset of assumptions that are enough to
344 // make the problem infeasible.
345 void AddUnsatCore(const std::vector<int>& core);
346
347 // Returns true if we found the optimal solution or the problem was proven
348 // infeasible. Note that if the gap limit is reached, we will also report
349 // OPTIMAL and consider the problem solved.
350 bool ProblemIsSolved() const;
351
352 // Returns the underlying solution repository where we keep a set of best
353 // solutions.
355 return solutions_;
356 }
360
361 // Debug only. Set dump prefix for solutions written to file.
362 void set_dump_prefix(absl::string_view dump_prefix) {
363 dump_prefix_ = dump_prefix;
364 }
365
366 // Display improvement stats.
368
369 // Wrapper around our SolverLogger, but protected by mutex.
370 void LogMessage(absl::string_view prefix, absl::string_view message);
371 void LogMessageWithThrottling(const std::string& prefix,
372 const std::string& message);
373 bool LoggingIsEnabled() const;
374
375 void AppendResponseToBeMerged(const CpSolverResponse& response);
376
378 return &first_solution_solvers_should_stop_;
379 }
380
381 // We just store a loaded DebugSolution here. Note that this is supposed to be
382 // stored once and then never change, so we do not need a mutex.
383 void LoadDebugSolution(absl::Span<const int64_t> solution) {
384 debug_solution_.assign(solution.begin(), solution.end());
385 }
386 const std::vector<int64_t>& DebugSolution() const { return debug_solution_; }
387
388 private:
389 void TestGapLimitsIfNeeded() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
390 void FillObjectiveValuesInResponse(CpSolverResponse* response) const
391 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
392 void UpdateGapIntegralInternal() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
393
394 void RegisterSolutionFound(const std::string& improvement_info,
395 int solution_rank)
396 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
397 void RegisterObjectiveBoundImprovement(const std::string& improvement_info)
398 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
399 void UpdateBestStatus(const CpSolverStatus& status)
400 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
401
402 // Generates a response for callbacks and GetResponse().
403 CpSolverResponse GetResponseInternal(
404 absl::Span<const int64_t> variable_values,
405 const std::string& solution_info) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
406
407 const SatParameters& parameters_;
408 const WallTimer& wall_timer_;
409 ModelSharedTimeLimit* shared_time_limit_;
410 CpObjectiveProto const* objective_or_null_ = nullptr;
411
412 mutable absl::Mutex mutex_;
413
414 // Gap limits.
415 double absolute_gap_limit_ ABSL_GUARDED_BY(mutex_) = 0.0;
416 double relative_gap_limit_ ABSL_GUARDED_BY(mutex_) = 0.0;
417
418 CpSolverStatus best_status_ ABSL_GUARDED_BY(mutex_) = CpSolverStatus::UNKNOWN;
419 CpSolverStatus synchronized_best_status_ ABSL_GUARDED_BY(mutex_) =
420 CpSolverStatus::UNKNOWN;
421 std::vector<int> unsat_cores_ ABSL_GUARDED_BY(mutex_);
422 SharedSolutionRepository<int64_t> solutions_; // Thread-safe.
423
424 int num_solutions_ ABSL_GUARDED_BY(mutex_) = 0;
425 int64_t inner_objective_lower_bound_ ABSL_GUARDED_BY(mutex_) =
426 std::numeric_limits<int64_t>::min();
427 int64_t inner_objective_upper_bound_ ABSL_GUARDED_BY(mutex_) =
428 std::numeric_limits<int64_t>::max();
429 int64_t best_solution_objective_value_ ABSL_GUARDED_BY(mutex_) =
430 std::numeric_limits<int64_t>::max();
431
432 bool always_synchronize_ ABSL_GUARDED_BY(mutex_) = true;
433 IntegerValue synchronized_inner_objective_lower_bound_ ABSL_GUARDED_BY(
434 mutex_) = IntegerValue(std::numeric_limits<int64_t>::min());
435 IntegerValue synchronized_inner_objective_upper_bound_ ABSL_GUARDED_BY(
436 mutex_) = IntegerValue(std::numeric_limits<int64_t>::max());
437
438 bool update_integral_on_each_change_ ABSL_GUARDED_BY(mutex_) = false;
439 double gap_integral_ ABSL_GUARDED_BY(mutex_) = 0.0;
440 double last_absolute_gap_ ABSL_GUARDED_BY(mutex_) = 0.0;
441 double last_gap_integral_time_stamp_ ABSL_GUARDED_BY(mutex_) = 0.0;
442
443 int next_callback_id_ ABSL_GUARDED_BY(mutex_) = 0;
444 std::vector<std::pair<int, std::function<void(const CpSolverResponse&)>>>
445 callbacks_ ABSL_GUARDED_BY(mutex_);
446
447 int next_search_log_callback_id_ ABSL_GUARDED_BY(mutex_) = 0;
448 std::vector<
449 std::pair<int, std::function<std::string(const CpSolverResponse&)>>>
450 search_log_callbacks_ ABSL_GUARDED_BY(mutex_);
451
452 int next_best_bound_callback_id_ ABSL_GUARDED_BY(mutex_) = 0;
453 std::vector<std::pair<int, std::function<void(double)>>> best_bound_callbacks_
454 ABSL_GUARDED_BY(mutex_);
455
456 std::vector<std::function<void(std::vector<int64_t>*)>>
457 solution_postprocessors_ ABSL_GUARDED_BY(mutex_);
458 std::vector<std::function<void(CpSolverResponse*)>> postprocessors_
459 ABSL_GUARDED_BY(mutex_);
460 std::vector<std::function<void(CpSolverResponse*)>> final_postprocessors_
461 ABSL_GUARDED_BY(mutex_);
462
463 // Dump prefix.
464 std::string dump_prefix_;
465
466 // Used for statistics of the improvements found by workers.
467 absl::btree_map<std::string, int> primal_improvements_count_
468 ABSL_GUARDED_BY(mutex_);
469 absl::btree_map<std::string, int> primal_improvements_min_rank_
470 ABSL_GUARDED_BY(mutex_);
471 absl::btree_map<std::string, int> primal_improvements_max_rank_
472 ABSL_GUARDED_BY(mutex_);
473
474 absl::btree_map<std::string, int> dual_improvements_count_
475 ABSL_GUARDED_BY(mutex_);
476
477 SolverLogger* logger_ ABSL_GUARDED_BY(mutex_);
478 absl::flat_hash_map<std::string, int> throttling_ids_ ABSL_GUARDED_BY(mutex_);
479
480 int bounds_logging_id_;
481 std::vector<CpSolverResponse> subsolver_responses_ ABSL_GUARDED_BY(mutex_);
482
483 std::atomic<bool> first_solution_solvers_should_stop_ = false;
484
485 std::vector<int64_t> debug_solution_;
486};
487
488// This class manages a pool of lower and upper bounds on a set of variables in
489// a parallel context.
491 public:
492 explicit SharedBoundsManager(const CpModelProto& model_proto);
493
494 // Reports a set of locally improved variable bounds to the shared bounds
495 // manager. The manager will compare these bounds changes against its
496 // global state, and incorporate the improving ones.
497 void ReportPotentialNewBounds(const std::string& worker_name,
498 const std::vector<int>& variables,
499 const std::vector<int64_t>& new_lower_bounds,
500 const std::vector<int64_t>& new_upper_bounds);
501
502 // If we solved a small independent component of the full problem, then we can
503 // in most situation fix the solution on this subspace.
504 //
505 // Note that because there can be more than one optimal solution on an
506 // independent subproblem, it is important to do that in a locked fashion, and
507 // reject future incompatible fixing.
508 void FixVariablesFromPartialSolution(
509 const std::vector<int64_t>& solution,
510 const std::vector<int>& variables_to_fix);
511
512 // Returns a new id to be used in GetChangedBounds(). This is just an ever
513 // increasing sequence starting from zero. Note that the class is not designed
514 // to have too many of these.
515 int RegisterNewId();
516
517 // When called, returns the set of bounds improvements since
518 // the last time this method was called with the same id.
519 void GetChangedBounds(int id, std::vector<int>* variables,
520 std::vector<int64_t>* new_lower_bounds,
521 std::vector<int64_t>* new_upper_bounds);
522
523 // This should not be called too often as it lock the class for
524 // O(num_variables) time.
525 void UpdateDomains(std::vector<Domain>* domains);
526
527 // Publishes any new bounds so that GetChangedBounds() will reflect the latest
528 // state.
529 void Synchronize();
530
531 void LogStatistics(SolverLogger* logger);
532 int NumBoundsExported(const std::string& worker_name);
533
534 // If non-empty, we will check that all bounds update contains this solution.
535 // Note that this might fail once we reach optimality and we might have wrong
536 // bounds, but if it fail before that it can help find bugs.
537 void LoadDebugSolution(absl::Span<const int64_t> solution) {
538 debug_solution_.assign(solution.begin(), solution.end());
539 }
540
541 // Debug only. Set dump prefix for solutions written to file.
542 void set_dump_prefix(absl::string_view dump_prefix) {
543 dump_prefix_ = dump_prefix;
544 }
545
546 private:
547 const int num_variables_;
548 const CpModelProto& model_proto_;
549
550 absl::Mutex mutex_;
551
552 // These are always up to date.
553 std::vector<int64_t> lower_bounds_ ABSL_GUARDED_BY(mutex_);
554 std::vector<int64_t> upper_bounds_ ABSL_GUARDED_BY(mutex_);
555 SparseBitset<int> changed_variables_since_last_synchronize_
556 ABSL_GUARDED_BY(mutex_);
557 int64_t total_num_improvements_ ABSL_GUARDED_BY(mutex_) = 0;
558
559 // These are only updated on Synchronize().
560 std::vector<int64_t> synchronized_lower_bounds_ ABSL_GUARDED_BY(mutex_);
561 std::vector<int64_t> synchronized_upper_bounds_ ABSL_GUARDED_BY(mutex_);
562 std::deque<SparseBitset<int>> id_to_changed_variables_
563 ABSL_GUARDED_BY(mutex_);
564 absl::btree_map<std::string, int> bounds_exported_ ABSL_GUARDED_BY(mutex_);
565
566 std::vector<int64_t> debug_solution_;
567 std::string dump_prefix_;
568 int export_counter_ = 0;
569};
570
571// Emit a stream of clauses in batches without duplicates. Each batch has a
572// fixed number of literals, containing the smallest clauses added.
573// It has a finite size internal buffer that is a small multiple of the batch
574// size.
575//
576// This class is thread-safe, the idea is to have one per worker plus a
577// global one to deduplicate between workers to minimize contention.
578//
579// This uses a finite buffer, so some clauses may be dropped if we generate too
580// many more than we export, but that is rarely a problem because we never
581// overfill the "global" stream, and if we drop a clause on a worker, one of the
582// following will most likely happen:
583// 1. Some other worker learns the clause and shares it later.
584// 2. All other workers also learn and drop the clause.
585// 3. No other worker learns the clause, so it was not that helpful anyway.
586//
587// Note that this uses literals as encoded in a cp_model.proto. Thus, the
588// literals can be negative numbers.
590 public:
591 static constexpr int kMinClauseSize = 3;
592 static constexpr int kMaxClauseSize = 8;
593 // Export 4KiB of clauses per batch.
594 static constexpr int kMaxLiteralsPerBatch = 4096 / sizeof(int);
595 // Bound the total literals we buffer, approximately enforced so shorter
596 // clauses can replace longer ones. This can be larger than
597 // kMaxLiteralsPerBatch (hence the separate constant), but experiments suggest
598 // that this doesn't help.
599 static constexpr int kMaxBufferedLiterals = kMaxLiteralsPerBatch;
600
602 // Move only - this is an expensive class to copy.
605
606 // Adds the clause to a future batch and returns true if the clause was added.
607 // Otherwise returns false. This may return false if the buffer is full.
608 // It will not block the clause if it is dropped to avoid unbounded growth of
609 // the hash table.
610 bool Add(absl::Span<const int> clause) ABSL_LOCKS_EXCLUDED(mutex_);
611
612 // Lazily deletes a clause with the same hash, returns true if it was present.
613 // The deleted clause will not be exported (either via NextBatch or
614 // FillUpstreamBuffer). A clause with the same hash may be re-added after
615 // calling Delete. If another clause with the same hash is added before the
616 // deleted clause is emitted then both clauses may be emitted.
617 bool Delete(absl::Span<const int> clause) ABSL_LOCKS_EXCLUDED(mutex_);
618
619 // Returns a set of clauses totalling up to kMaxLiteralsPerBatch and removes
620 // exported clauses from the internal buffer.
621 CompactVectorVector<int> NextBatch() ABSL_LOCKS_EXCLUDED(mutex_);
622
623 // Adds up to max_clauses_to_export clauses of a given size to upstream and
624 // removes them from the internal buffer.
625 int FillUpstreamBuffer(UniqueClauseStream& upstream, int clause_size,
626 int max_clauses_to_export) ABSL_LOCKS_EXCLUDED(mutex_);
627
628 // Returns the number of literals in the buffer in clauses with size <=
629 // max_size.
630 int NumBufferedLiteralsOfSize(int size) const ABSL_LOCKS_EXCLUDED(mutex_) {
631 absl::MutexLock lock(&mutex_);
632 return NumLiteralsOfSize(size);
633 }
634 int NumBufferedLiterals() const ABSL_LOCKS_EXCLUDED(mutex_);
635
636 // Returns true if the stream can accept a clause of the specified size and
637 // LBD without dropping it.
638 bool CanAccept(int size, int lbd) const;
639
640 // Delete longest clauses while keeping at least kMaxBufferedLiterals.
641 // This guarantees that CanAccept will return the same result as before, and
642 // at least the next batch will contain the same clauses, but we will emit
643 // fewer old, long clauses in the future.
644 void RemoveWorstClauses();
645
646 int lbd_threshold() const ABSL_LOCKS_EXCLUDED(mutex_) {
647 absl::MutexLock lock(&mutex_);
648 return lbd_threshold_;
649 }
650 void set_lbd_threshold(int lbd) ABSL_LOCKS_EXCLUDED(mutex_);
651
652 // Computes a hash that is independent of the order of literals in the clause.
653 static size_t HashClause(absl::Span<const int> clause, size_t hash_seed = 0);
654
655 private:
656 bool BlockClause(absl::Span<const int> clause)
657 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
658 std::vector<int>* MutableBufferForSize(int size)
659 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
660 return &clauses_by_size_[size - kMinClauseSize];
661 }
662 absl::Span<const int> BufferForSize(int size) const
663 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
664 return clauses_by_size_[size - kMinClauseSize];
665 }
666 absl::Span<const int> NextClause(int size) const
667 ABSL_SHARED_LOCKS_REQUIRED(mutex_);
668 void PopClause(int size) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
669 // Computes the number of clauses of a given size.
670 int NumClausesOfSize(int size) const ABSL_SHARED_LOCKS_REQUIRED(mutex_);
671 int NumLiteralsOfSize(int size) const ABSL_SHARED_LOCKS_REQUIRED(mutex_);
672
673 mutable absl::Mutex mutex_;
674 int lbd_threshold_ ABSL_GUARDED_BY(mutex_) = 2;
675 absl::flat_hash_set<size_t> fingerprints_ ABSL_GUARDED_BY(mutex_);
676 std::array<std::vector<int>, kMaxClauseSize - kMinClauseSize + 1>
677 clauses_by_size_ ABSL_GUARDED_BY(mutex_);
678};
679
680// This class holds clauses found and shared by workers.
681// It is exact for binary clauses, but approximate for longer ones.
682//
683// It is thread-safe.
684//
685// Note that this uses literal as encoded in a cp_model.proto. Thus, the
686// literals can be negative numbers.
688 public:
689 explicit SharedClausesManager(bool always_synchronize,
690 absl::Duration share_frequency);
691 void AddBinaryClause(int id, int lit1, int lit2);
692
693 // Returns new glue clauses.
694 // The spans are guaranteed to remain valid until the next call to
695 // SyncClauses().
696 std::vector<absl::Span<const int>> GetUnseenClauses(int id);
697
698 // Fills new_clauses with
699 // {{lit1 of clause1, lit2 of clause1},
700 // {lit1 of clause2, lit2 of clause2},
701 // ...}
702 void GetUnseenBinaryClauses(int id,
703 std::vector<std::pair<int, int>>* new_clauses);
704
705 // Ids are used to identify which worker is exporting/importing clauses.
706 int RegisterNewId();
707 void SetWorkerNameForId(int id, const std::string& worker_name);
708
709 // A worker can add or remove clauses from its own clause set.
710 // Retains ownership of the returned ClauseFilter.
712 absl::ReaderMutexLock mutex_lock(&mutex_);
713 return &id_to_clause_stream_[id];
714 }
715
716 // Search statistics.
717 void LogStatistics(SolverLogger* logger);
718
719 // Unlocks waiting binary clauses for workers if always_synchronize is false.
720 // Periodically starts a new sharing round, making glue clauses visible.
721 void Synchronize();
722
723 private:
724 static constexpr int kMinBatches = 10;
725 absl::Mutex mutex_;
726
727 // Binary clauses:
728 // Cache to avoid adding the same binary clause twice.
729 absl::flat_hash_set<std::pair<int, int>> added_binary_clauses_set_
730 ABSL_GUARDED_BY(mutex_);
731 std::vector<std::pair<int, int>> added_binary_clauses_
732 ABSL_GUARDED_BY(mutex_);
733 std::vector<int> id_to_last_processed_binary_clause_ ABSL_GUARDED_BY(mutex_);
734 int last_visible_binary_clause_ ABSL_GUARDED_BY(mutex_) = 0;
735
736 // Longer clauses:
737 UniqueClauseStream all_clauses_ ABSL_GUARDED_BY(mutex_);
738 // This is slightly subtle - we need to track the batches that might be
739 // currently being processed by each worker.
740 std::vector<int> id_to_last_returned_batch_ ABSL_GUARDED_BY(mutex_);
741 std::vector<int> id_to_last_finished_batch_ ABSL_GUARDED_BY(mutex_);
742 std::deque<CompactVectorVector<int>> batches_ ABSL_GUARDED_BY(mutex_);
743 std::deque<UniqueClauseStream> id_to_clause_stream_ ABSL_GUARDED_BY(mutex_);
744 WallTimer share_timer_ ABSL_GUARDED_BY(mutex_);
745
746 const bool always_synchronize_ = true;
747 const absl::Duration share_frequency_;
748
749 // Stats:
750 std::vector<int64_t> id_to_clauses_exported_;
751 absl::flat_hash_map<int, std::string> id_to_worker_name_;
752};
753
754// Simple class to add statistics by name and print them at the end.
756 public:
757 SharedStatistics() = default;
758
759 // Adds a bunch of stats, adding count for the same key together.
760 void AddStats(absl::Span<const std::pair<std::string, int64_t>> stats);
761
762 // Logs all the added stats.
763 void Log(SolverLogger* logger);
764
765 private:
766 absl::Mutex mutex_;
767 absl::flat_hash_map<std::string, int64_t> stats_ ABSL_GUARDED_BY(mutex_);
768};
769
770template <typename ValueType>
772 absl::MutexLock mutex_lock(&mutex_);
773 return solutions_.size();
774}
775
776template <typename ValueType>
779 absl::MutexLock mutex_lock(&mutex_);
780 ++num_queried_;
781 return solutions_[i];
782}
783
784template <typename ValueType>
786 absl::MutexLock mutex_lock(&mutex_);
787 CHECK_GT(solutions_.size(), 0);
788 return solutions_[0].rank;
789}
790
791template <typename ValueType>
793 int var_index, int solution_index) const {
794 absl::MutexLock mutex_lock(&mutex_);
795 return solutions_[solution_index].variable_values[var_index];
796}
797
798// TODO(user): Experiments on the best distribution.
799template <typename ValueType>
802 absl::BitGenRef random) const {
803 absl::MutexLock mutex_lock(&mutex_);
804 ++num_queried_;
805 const int64_t best_rank = solutions_[0].rank;
806
807 // As long as we have solution with the best objective that haven't been
808 // explored too much, we select one uniformly. Otherwise, we select a solution
809 // from the pool uniformly.
810 //
811 // Note(user): Because of the increase of num_selected, this is dependent on
812 // the order of call. It should be fine for "determinism" because we do
813 // generate the task of a batch always in the same order.
814 const int kExplorationThreshold = 100;
815
816 // Select all the best solution with a low enough selection count.
817 tmp_indices_.clear();
818 for (int i = 0; i < solutions_.size(); ++i) {
819 const auto& solution = solutions_[i];
820 if (solution.rank == best_rank &&
821 solution.num_selected <= kExplorationThreshold) {
822 tmp_indices_.push_back(i);
823 }
824 }
825
826 int index = 0;
827 if (tmp_indices_.empty()) {
828 index = absl::Uniform<int>(random, 0, solutions_.size());
829 } else {
830 index = tmp_indices_[absl::Uniform<int>(random, 0, tmp_indices_.size())];
831 }
832 solutions_[index].num_selected++;
833 return solutions_[index];
834}
835
836template <typename ValueType>
838 if (num_solutions_to_keep_ <= 0) return;
839 absl::MutexLock mutex_lock(&mutex_);
840 ++num_added_;
841 new_solutions_.push_back(solution);
842}
843
844template <typename ValueType>
846 absl::MutexLock mutex_lock(&mutex_);
847 if (new_solutions_.empty()) return;
848
849 solutions_.insert(solutions_.end(), new_solutions_.begin(),
850 new_solutions_.end());
851 new_solutions_.clear();
852
853 // We use a stable sort to keep the num_selected count for the already
854 // existing solutions.
855 //
856 // TODO(user): Introduce a notion of orthogonality to diversify the pool?
858 if (solutions_.size() > num_solutions_to_keep_) {
859 solutions_.resize(num_solutions_to_keep_);
860 }
861
862 if (!solutions_.empty()) {
863 VLOG(2) << "Solution pool update:" << " num_solutions=" << solutions_.size()
864 << " min_rank=" << solutions_[0].rank
865 << " max_rank=" << solutions_.back().rank;
866 }
867
868 num_synchronization_++;
869}
870
871} // namespace sat
872} // namespace operations_research
873
874#endif // OR_TOOLS_SAT_SYNCHRONIZATION_H_
IntegerValue size
int64_t max
int64_t min
The model "singleton" shared time limit.
Definition util.h:331
void LoadDebugSolution(absl::Span< const int64_t > solution)
void set_dump_prefix(absl::string_view dump_prefix)
Debug only. Set dump prefix for solutions written to file.
std::vector< double > PopLast()
If there are no solution, this return an empty vector.
void AddSolution(const std::vector< double > &lp_solution)
void NewLPSolution(std::vector< double > lp_solution)
void AppendResponseToBeMerged(const CpSolverResponse &response)
void AddUnsatCore(const std::vector< int > &core)
const std::vector< int64_t > & DebugSolution() const
void LogMessageWithThrottling(const std::string &prefix, const std::string &message)
SharedSolutionRepository< int64_t > * MutableSolutionsRepository()
void LogMessage(absl::string_view prefix, absl::string_view message)
Wrapper around our SolverLogger, but protected by mutex.
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)
void DisplayImprovementStatistics()
Display improvement stats.
void InitializeObjective(const CpModelProto &cp_model)
void SetGapLimitsFromParameters(const SatParameters &parameters)
void AddFinalResponsePostprocessor(std::function< void(CpSolverResponse *)> postprocessor)
void NotifyThatImprovingProblemIsInfeasible(const std::string &worker_info)
void set_dump_prefix(absl::string_view dump_prefix)
Debug only. Set dump prefix for solutions written to file.
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 NewSolution(absl::Span< const int64_t > solution_values, const std::string &solution_info, Model *model=nullptr)
const SharedSolutionRepository< int64_t > & SolutionsRepository() const
void UpdateInnerObjectiveBounds(const std::string &update_info, IntegerValue lb, IntegerValue ub)
Updates the inner objective bounds.
Solution GetSolution(int index) const
Returns the solution i where i must be smaller than NumSolutions().
int64_t num_queried_ ABSL_GUARDED_BY(mutex_)=0
std::vector< int > tmp_indices_ ABSL_GUARDED_BY(mutex_)
Solution GetRandomBiasedSolution(absl::BitGenRef random) const
Returns a random solution biased towards good solutions.
SharedSolutionRepository(int num_solutions_to_keep, absl::string_view name="")
std::vector< Solution > new_solutions_ ABSL_GUARDED_BY(mutex_)
std::vector< std::string > TableLineStats() const
std::vector< Solution > solutions_ ABSL_GUARDED_BY(mutex_)
ValueType GetVariableValueInSolution(int var_index, int solution_index) const
int64_t num_synchronization_ ABSL_GUARDED_BY(mutex_)=0
Simple class to add statistics by name and print them at the end.
UniqueClauseStream(const UniqueClauseStream &)=delete
Move only - this is an expensive class to copy.
UniqueClauseStream(UniqueClauseStream &&)=default
SatParameters parameters
const std::string name
A name for logging purposes.
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
MPCallback * callback
int index
double solution
void STLStableSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:75
void FillSolveStatsInResponse(Model *model, CpSolverResponse *response)
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Definition util.cc:49
std::string FormatName(absl::string_view name)
This is used to format our table first row entry.
Definition util.h:196
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
int var_index
Definition search.cc:3268
std::vector< std::function< void(CpSolverResponse *)> > callbacks
std::string message
Definition trace.cc:397