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