OR-Tools  9.6
subsolver.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 // Simple framework for choosing and distributing a solver "sub-tasks" on a set
15 // of threads.
16 
17 #ifndef OR_TOOLS_SAT_SUBSOLVER_H_
18 #define OR_TOOLS_SAT_SUBSOLVER_H_
19 
20 #include <algorithm>
21 #include <cmath>
22 #include <cstdint>
23 #include <functional>
24 #include <memory>
25 #include <string>
26 #include <utility>
27 #include <vector>
28 
30 
31 #if !defined(__PORTABLE_PLATFORM__)
33 #endif // __PORTABLE_PLATFORM__
34 
35 namespace operations_research {
36 namespace sat {
37 
38 // The API used for distributing work. Each subsolver can generate tasks and
39 // synchronize itself with the rest of the world.
40 //
41 // Note that currently only the main thread interact with subsolvers. Only the
42 // tasks generated by GenerateTask() are executed in parallel in a threadpool.
43 class SubSolver {
44  public:
46 
47  SubSolver(const std::string& name, SubsolverType type)
48  : name_(name), type_(type) {}
49  virtual ~SubSolver() {}
50 
51  // Returns true iff GenerateTask() can be called.
52  //
53  // Note(user): In the current design, a SubSolver is never deleted until the
54  // end of the Solve() that created it. But is is okay to always return false
55  // here and release the memory used by the Subsolver internal if there is no
56  // need to call this Subsolver ever again. The overhead of iterating over it
57  // in the main solver loop should be minimal.
58  virtual bool TaskIsAvailable() = 0;
59 
60  // Returns a task to run. The task_id is just an ever increasing counter that
61  // correspond to the number of total calls to GenerateTask().
62  //
63  // TODO(user): We could use a more complex selection logic and pass in the
64  // deterministic time limit this subtask should run for. Unclear at this
65  // stage.
66  virtual std::function<void()> GenerateTask(int64_t task_id) = 0;
67 
68  // Synchronizes with the external world from this SubSolver point of view.
69  // Also incorporate the results of the latest completed tasks if any.
70  //
71  // Note(user): The intended implementation for determinism is that tasks
72  // update asynchronously (and so non-deterministically) global "shared"
73  // classes, but this global state is incorporated by the Subsolver only when
74  // Synchronize() is called.
75  virtual void Synchronize() = 0;
76 
77  // Returns the score as updated by the completed tasks before the last
78  // Synchronize() call. Everything else being equal, we prefer to run a
79  // SubSolver with the highest score.
80  //
81  // TODO(user): This is unused for now.
82  double score() const { return score_; }
83 
84  // Returns the total deterministic time spend by the completed tasks before
85  // the last Synchronize() call.
86  double deterministic_time() const { return deterministic_time_; }
87 
88  // Returns the name of this SubSolver. Used in logs.
89  std::string name() const { return name_; }
90 
91  // Returns the type of the subsolver.
92  SubsolverType type() const { return type_; }
93 
94  // Returns search statistics.
95  virtual std::string StatisticsString() const { return std::string(); }
96 
97  protected:
98  const std::string name_;
100  double score_ = 0.0;
101  double deterministic_time_ = 0.0;
102 };
103 
104 // A simple wrapper to add a synchronization point in the list of subsolvers.
106  public:
107  explicit SynchronizationPoint(const std::string& name,
108  std::function<void()> f)
109  : SubSolver(name, HELPER), f_(std::move(f)) {}
110  bool TaskIsAvailable() final { return false; }
111  std::function<void()> GenerateTask(int64_t /*task_id*/) final {
112  return nullptr;
113  }
114  void Synchronize() final { f_(); }
115 
116  private:
117  std::function<void()> f_;
118 };
119 
120 // Executes the following loop:
121 // 1/ Synchronize all in given order.
122 // 2/ generate and schedule one task from the current "best" subsolver.
123 // 3/ repeat until no extra task can be generated and all tasks are done.
124 //
125 // The complexity of each selection is in O(num_subsolvers), but that should
126 // be okay given that we don't expect more than 100 such subsolvers.
127 //
128 // Note that it is okay to incorporate "special" subsolver that never produce
129 // any tasks. This can be used to synchronize classes used by many subsolvers
130 // just once for instance.
132  const std::vector<std::unique_ptr<SubSolver>>& subsolvers, int num_threads);
133 
134 // Similar to NonDeterministicLoop() except this should result in a
135 // deterministic solver provided that all SubSolver respect the Synchronize()
136 // contract.
137 //
138 // Executes the following loop:
139 // 1/ Synchronize all in given order.
140 // 2/ generate and schedule up to batch_size tasks using an heuristic to select
141 // which one to run.
142 // 3/ wait for all task to finish.
143 // 4/ repeat until no task can be generated in step 2.
144 void DeterministicLoop(
145  const std::vector<std::unique_ptr<SubSolver>>& subsolvers, int num_threads,
146  int batch_size);
147 
148 // Same as above, but specialized implementation for the case num_threads=1.
149 // This avoids using a Threadpool altogether. It should have the same behavior
150 // than the functions above with num_threads=1 and batch_size=1. Note that an
151 // higher batch size will not behave in the same way, even if num_threads=1.
152 void SequentialLoop(const std::vector<std::unique_ptr<SubSolver>>& subsolvers);
153 
154 } // namespace sat
155 } // namespace operations_research
156 
157 #endif // OR_TOOLS_SAT_SUBSOLVER_H_
SubsolverType type() const
Definition: subsolver.h:92
virtual std::function< void()> GenerateTask(int64_t task_id)=0
virtual std::string StatisticsString() const
Definition: subsolver.h:95
SubSolver(const std::string &name, SubsolverType type)
Definition: subsolver.h:47
std::function< void()> GenerateTask(int64_t) final
Definition: subsolver.h:111
SynchronizationPoint(const std::string &name, std::function< void()> f)
Definition: subsolver.h:107
void DeterministicLoop(const std::vector< std::unique_ptr< SubSolver >> &subsolvers, int num_threads, int batch_size)
Definition: subsolver.cc:94
void NonDeterministicLoop(const std::vector< std::unique_ptr< SubSolver >> &subsolvers, int num_threads)
Definition: subsolver.cc:133
void SequentialLoop(const std::vector< std::unique_ptr< SubSolver >> &subsolvers)
Definition: subsolver.cc:64
Collection of objects used to extend the Constraint Solver library.