Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
subsolver.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// 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
29#include "absl/strings/string_view.h"
30#include "ortools/sat/util.h"
31#include "ortools/util/stats.h"
32
33#if !defined(__PORTABLE_PLATFORM__)
34#endif // __PORTABLE_PLATFORM__
35
36namespace operations_research {
37namespace sat {
38
39// The API used for distributing work. Each subsolver can generate tasks and
40// synchronize itself with the rest of the world.
41//
42// Note that currently only the main thread interact with subsolvers. Only the
43// tasks generated by GenerateTask() are executed in parallel in a threadpool.
44class SubSolver {
45 public:
47
48 SubSolver(absl::string_view name, SubsolverType type)
49 : name_(name), type_(type) {}
50 virtual ~SubSolver() = default;
51
52 // Synchronizes with the external world from this SubSolver point of view.
53 // Also incorporate the results of the latest completed tasks if any.
54 //
55 // Note(user): The intended implementation for determinism is that tasks
56 // update asynchronously (and so non-deterministically) global "shared"
57 // classes, but this global state is incorporated by the Subsolver only when
58 // Synchronize() is called.
59 //
60 // This is only called by the main thread in Subsolver creation order.
61 virtual void Synchronize() = 0;
62
63 // Returns true if this SubSolver is done and its memory can be freed. Note
64 // that the *Loop(subsolvers) functions below takes a reference in order to be
65 // able to clear the memory of a SubSolver as soon as it is done. Once this is
66 // true, the subsolver in question will be deleted and never used again.
67 //
68 // This is needed since some subsolve can be done before the overal Solve() is
69 // finished. This is the case for first solution subsolvers for instances.
70 //
71 // This is only called by the main thread in a sequential fashion.
72 // Important: This is only called when there is currently no task from that
73 // SubSolver in flight.
74 virtual bool IsDone() { return false; }
75
76 // Returns true iff GenerateTask() can be called.
77 // This is only called by the main thread in a sequential fashion.
78 virtual bool TaskIsAvailable() = 0;
79
80 // Returns a task to run. The task_id is just an ever increasing counter that
81 // correspond to the number of total calls to GenerateTask().
82 //
83 // TODO(user): We could use a more complex selection logic and pass in the
84 // deterministic time limit this subtask should run for. Unclear at this
85 // stage.
86 //
87 // This is only called by the main thread.
88 virtual std::function<void()> GenerateTask(int64_t task_id) = 0;
89
90 // Returns the total deterministic time spend by the completed tasks before
91 // the last Synchronize() call.
92 double deterministic_time() const { return deterministic_time_; }
93
94 // Returns the name of this SubSolver. Used in logs.
95 std::string name() const { return name_; }
96
97 // Returns the type of the subsolver.
98 SubsolverType type() const { return type_; }
99
100 // Note that this is protected by the global execution mutex and so it is
101 // called sequentially. Subclasses do not need to call this.
102 void AddTaskDuration(double duration_in_seconds) {
103 ++num_finished_tasks_;
104 wall_time_ += duration_in_seconds;
105 timing_.AddTimeInSec(duration_in_seconds);
106 }
107
108 // Note that this is protected by the global execution mutex and so it is
109 // called sequentially. Subclasses do not need to call this.
110 void NotifySelection() { ++num_scheduled_tasks_; }
111
112 // This one need to be called by the Subclasses. Usually from Synchronize(),
113 // or from the task itself it we execute a single task at the same time.
114 void AddTaskDeterministicDuration(double deterministic_duration) {
115 if (deterministic_duration <= 0) return;
116 deterministic_time_ += deterministic_duration;
117 dtiming_.AddTimeInSec(deterministic_duration);
118 }
119
120 std::string TimingInfo() const {
121 // TODO(user): remove trailing "\n" from ValueAsString() or just build the
122 // table line directly.
123 std::string data = timing_.ValueAsString();
124 if (!data.empty()) data.pop_back();
125 return data;
126 }
127
128 std::string DeterministicTimingInfo() const {
129 // TODO(user): remove trailing "\n" from ValueAsString().
130 std::string data = dtiming_.ValueAsString();
131 if (!data.empty()) data.pop_back();
132 return data;
133 }
134
135 // Returns a score used to compare which tasks to schedule next.
136 // We will schedule the LOWER score.
137 //
138 // Tricky: Note that this will only be called sequentially. The deterministic
139 // time should only be used with the DeterministicLoop() because otherwise it
140 // can be updated at the same time as this is called.
141 double GetSelectionScore(bool deterministic) const {
142 const double time = deterministic ? deterministic_time_ : wall_time_;
143 const double divisor = num_scheduled_tasks_ > 0
144 ? static_cast<double>(num_scheduled_tasks_)
145 : 1.0;
146
147 // If we have little data, we strongly limit the number of task in flight.
148 // This is needed if some LNS are stuck for a long time to not just only
149 // schedule this type at the beginning.
150 const int64_t in_flight = num_scheduled_tasks_ - num_finished_tasks_;
151 const double confidence_factor =
152 num_finished_tasks_ > 10 ? 1.0 : std::exp(in_flight);
153
154 // We assume a "minimum time per task" which will be our base etimation for
155 // the average running time of this task.
156 return num_scheduled_tasks_ * std::max(0.1, time / divisor) *
157 confidence_factor;
158 }
159
160 private:
161 const std::string name_;
162 const SubsolverType type_;
163
164 int64_t num_scheduled_tasks_ = 0;
165 int64_t num_finished_tasks_ = 0;
166
167 // Sum of wall_time / deterministic_time.
168 double wall_time_ = 0.0;
169 double deterministic_time_ = 0.0;
170
171 TimeDistribution timing_ = TimeDistribution("task time");
172 TimeDistribution dtiming_ = TimeDistribution("task dtime");
173};
174
175// A simple wrapper to add a synchronization point in the list of subsolvers.
177 public:
178 explicit SynchronizationPoint(absl::string_view name, std::function<void()> f)
179 : SubSolver(name, HELPER), f_(std::move(f)) {}
180 bool TaskIsAvailable() final { return false; }
181 std::function<void()> GenerateTask(int64_t /*task_id*/) final {
182 return nullptr;
183 }
184 void Synchronize() final { f_(); }
185
186 private:
187 std::function<void()> f_;
188};
189
190// Executes the following loop:
191// 1/ Synchronize all in given order.
192// 2/ generate and schedule one task from the current "best" subsolver.
193// 3/ repeat until no extra task can be generated and all tasks are done.
194//
195// The complexity of each selection is in O(num_subsolvers), but that should
196// be okay given that we don't expect more than 100 such subsolvers.
197//
198// Note that it is okay to incorporate "special" subsolver that never produce
199// any tasks. This can be used to synchronize classes used by many subsolvers
200// just once for instance.
201void NonDeterministicLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers,
202 int num_threads, ModelSharedTimeLimit* time_limit);
203
204// Similar to NonDeterministicLoop() except this should result in a
205// deterministic solver provided that all SubSolver respect the Synchronize()
206// contract.
207//
208// Executes the following loop:
209// 1/ Synchronize all in given order.
210// 2/ generate and schedule up to batch_size tasks using an heuristic to select
211// which one to run.
212// 3/ wait for all task to finish.
213// 4/ repeat until no task can be generated in step 2.
214//
215// If max_num_batches is > 0, stop after that many batches.
216void DeterministicLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers,
217 int num_threads, int batch_size,
218 int max_num_batches = 0);
219
220// Same as above, but specialized implementation for the case num_threads=1.
221// This avoids using a Threadpool altogether. It should have the same behavior
222// than the functions above with num_threads=1 and batch_size=1. Note that an
223// higher batch size will not behave in the same way, even if num_threads=1.
224void SequentialLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers);
225
226} // namespace sat
227} // namespace operations_research
228
229#endif // OR_TOOLS_SAT_SUBSOLVER_H_
SubSolver(absl::string_view name, SubsolverType type)
Definition subsolver.h:48
void AddTaskDeterministicDuration(double deterministic_duration)
Definition subsolver.h:114
std::string DeterministicTimingInfo() const
Definition subsolver.h:128
SubsolverType type() const
Returns the type of the subsolver.
Definition subsolver.h:98
virtual std::function< void()> GenerateTask(int64_t task_id)=0
std::string name() const
Returns the name of this SubSolver. Used in logs.
Definition subsolver.h:95
double GetSelectionScore(bool deterministic) const
Definition subsolver.h:141
void AddTaskDuration(double duration_in_seconds)
Definition subsolver.h:102
SynchronizationPoint(absl::string_view name, std::function< void()> f)
Definition subsolver.h:178
std::function< void()> GenerateTask(int64_t) final
Definition subsolver.h:181
time_limit
Definition solve.cc:22
void DeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, int num_threads, int batch_size, int max_num_batches)
Definition subsolver.cc:128
void NonDeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, const int num_threads, ModelSharedTimeLimit *time_limit)
Definition subsolver.cc:194
void SequentialLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers)
Definition subsolver.cc:95
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.