2022-06-16 15:21:00 +02:00
|
|
|
// Copyright 2010-2022 Google LLC
|
2019-07-01 15:32:26 +02:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
|
|
#include "ortools/sat/subsolver.h"
|
|
|
|
|
|
2021-03-04 18:26:01 +01:00
|
|
|
#include <cstdint>
|
2022-02-15 18:00:11 +01:00
|
|
|
#include <functional>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
2022-11-29 14:39:27 +01:00
|
|
|
#include <utility>
|
2022-02-15 18:00:11 +01:00
|
|
|
#include <vector>
|
2021-03-04 18:26:01 +01:00
|
|
|
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "absl/flags/flag.h"
|
|
|
|
|
#include "absl/strings/string_view.h"
|
2019-07-03 13:10:22 +02:00
|
|
|
#include "absl/synchronization/mutex.h"
|
|
|
|
|
#include "absl/time/clock.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "absl/time/time.h"
|
|
|
|
|
#include "ortools/base/logging.h"
|
|
|
|
|
#if !defined(__PORTABLE_PLATFORM__)
|
|
|
|
|
#include "ortools/base/threadpool.h"
|
2020-10-22 23:36:58 +02:00
|
|
|
#endif // __PORTABLE_PLATFORM__
|
2019-07-01 15:32:26 +02:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
// Returns the next SubSolver index from which to call GenerateTask(). Note that
|
|
|
|
|
// only SubSolvers for which TaskIsAvailable() is true are considered. Return -1
|
|
|
|
|
// if no SubSolver can generate a new task.
|
|
|
|
|
//
|
|
|
|
|
// For now we use a really basic logic: call the least frequently called.
|
|
|
|
|
int NextSubsolverToSchedule(
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::vector<std::unique_ptr<SubSolver>>& subsolvers,
|
2021-03-04 18:26:01 +01:00
|
|
|
const std::vector<int64_t>& num_generated_tasks) {
|
2019-07-01 15:32:26 +02:00
|
|
|
int best = -1;
|
|
|
|
|
for (int i = 0; i < subsolvers.size(); ++i) {
|
|
|
|
|
if (subsolvers[i]->TaskIsAvailable()) {
|
|
|
|
|
if (best == -1 || num_generated_tasks[i] < num_generated_tasks[best]) {
|
|
|
|
|
best = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-22 23:36:58 +02:00
|
|
|
if (best != -1) VLOG(1) << "Scheduling " << subsolvers[best]->name();
|
2019-07-01 15:32:26 +02:00
|
|
|
return best;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SynchronizeAll(const std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
|
|
|
|
|
for (const auto& subsolver : subsolvers) subsolver->Synchronize();
|
2019-07-01 15:32:26 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
} // namespace
|
2019-07-01 15:32:26 +02:00
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SequentialLoop(const std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
|
2021-03-04 18:26:01 +01:00
|
|
|
int64_t task_id = 0;
|
|
|
|
|
std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
|
2019-07-01 15:32:26 +02:00
|
|
|
while (true) {
|
|
|
|
|
SynchronizeAll(subsolvers);
|
|
|
|
|
const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
|
2020-10-22 23:36:58 +02:00
|
|
|
if (best == -1) break;
|
2019-07-01 15:32:26 +02:00
|
|
|
num_generated_tasks[best]++;
|
|
|
|
|
subsolvers[best]->GenerateTask(task_id++)();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if defined(__PORTABLE_PLATFORM__)
|
|
|
|
|
|
|
|
|
|
// On portable platform, we don't support multi-threading for now.
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
void NonDeterministicLoop(
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::vector<std::unique_ptr<SubSolver>>& subsolvers,
|
2020-10-22 23:36:58 +02:00
|
|
|
int num_threads) {
|
2019-07-01 15:32:26 +02:00
|
|
|
SequentialLoop(subsolvers);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
void DeterministicLoop(
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::vector<std::unique_ptr<SubSolver>>& subsolvers, int num_threads,
|
2020-10-22 23:36:58 +02:00
|
|
|
int batch_size) {
|
2019-07-01 15:32:26 +02:00
|
|
|
SequentialLoop(subsolvers);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
#else // __PORTABLE_PLATFORM__
|
2019-07-01 15:32:26 +02:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
void DeterministicLoop(
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::vector<std::unique_ptr<SubSolver>>& subsolvers, int num_threads,
|
2020-10-22 23:36:58 +02:00
|
|
|
int batch_size) {
|
2019-07-01 15:32:26 +02:00
|
|
|
CHECK_GT(num_threads, 0);
|
|
|
|
|
CHECK_GT(batch_size, 0);
|
|
|
|
|
if (batch_size == 1) {
|
|
|
|
|
return SequentialLoop(subsolvers);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-04 18:26:01 +01:00
|
|
|
int64_t task_id = 0;
|
|
|
|
|
std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
|
2022-11-29 14:39:27 +01:00
|
|
|
std::vector<std::function<void()>> to_run;
|
|
|
|
|
to_run.reserve(batch_size);
|
2019-07-01 15:32:26 +02:00
|
|
|
while (true) {
|
|
|
|
|
SynchronizeAll(subsolvers);
|
|
|
|
|
|
2022-11-29 14:39:27 +01:00
|
|
|
// We first generate all task to run in this batch.
|
|
|
|
|
// Note that we can't start the task right away since if a task finish
|
|
|
|
|
// before we schedule everything, we will not be deterministic.
|
2019-07-01 15:32:26 +02:00
|
|
|
for (int t = 0; t < batch_size; ++t) {
|
|
|
|
|
const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
|
2020-10-22 23:36:58 +02:00
|
|
|
if (best == -1) break;
|
2019-07-01 15:32:26 +02:00
|
|
|
num_generated_tasks[best]++;
|
2022-11-29 14:39:27 +01:00
|
|
|
to_run.push_back(subsolvers[best]->GenerateTask(task_id++));
|
|
|
|
|
}
|
|
|
|
|
if (to_run.empty()) break;
|
|
|
|
|
|
|
|
|
|
// TODO(user): We could reuse the same ThreadPool as long as we wait for all
|
|
|
|
|
// the task in a batch to finish before scheduling new ones. Not sure how
|
|
|
|
|
// to easily do that, so for now we just recreate the pool for each to_run.
|
|
|
|
|
ThreadPool pool("DeterministicLoop", num_threads);
|
|
|
|
|
pool.StartWorkers();
|
|
|
|
|
for (auto& f : to_run) {
|
|
|
|
|
pool.Schedule(std::move(f));
|
2019-07-01 15:32:26 +02:00
|
|
|
}
|
2022-11-29 14:39:27 +01:00
|
|
|
to_run.clear();
|
2019-07-01 15:32:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
void NonDeterministicLoop(
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::vector<std::unique_ptr<SubSolver>>& subsolvers,
|
2020-10-22 23:36:58 +02:00
|
|
|
int num_threads) {
|
2019-07-01 15:32:26 +02:00
|
|
|
CHECK_GT(num_threads, 0);
|
|
|
|
|
if (num_threads == 1) {
|
|
|
|
|
return SequentialLoop(subsolvers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The mutex will protect these two fields. This is used to only keep
|
|
|
|
|
// num_threads task in-flight and detect when the search is done.
|
|
|
|
|
absl::Mutex mutex;
|
|
|
|
|
absl::CondVar thread_available_condition;
|
|
|
|
|
int num_scheduled_and_not_done = 0;
|
|
|
|
|
|
|
|
|
|
ThreadPool pool("NonDeterministicLoop", num_threads);
|
|
|
|
|
pool.StartWorkers();
|
|
|
|
|
|
|
|
|
|
// The lambda below are using little space, but there is no reason
|
|
|
|
|
// to create millions of them, so we use the blocking nature of
|
|
|
|
|
// pool.Schedule() when the queue capacity is set.
|
2021-03-04 18:26:01 +01:00
|
|
|
int64_t task_id = 0;
|
|
|
|
|
std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
|
2019-07-01 15:32:26 +02:00
|
|
|
while (true) {
|
|
|
|
|
bool all_done = false;
|
|
|
|
|
{
|
|
|
|
|
absl::MutexLock mutex_lock(&mutex);
|
|
|
|
|
|
|
|
|
|
// The stopping condition is that we do not have anything else to generate
|
|
|
|
|
// once all the task are done and synchronized.
|
2020-10-22 23:36:58 +02:00
|
|
|
if (num_scheduled_and_not_done == 0) all_done = true;
|
2019-07-01 15:32:26 +02:00
|
|
|
|
|
|
|
|
// Wait if num_scheduled_and_not_done == num_threads.
|
|
|
|
|
if (num_scheduled_and_not_done == num_threads) {
|
|
|
|
|
thread_available_condition.Wait(&mutex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SynchronizeAll(subsolvers);
|
|
|
|
|
const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
|
|
|
|
|
if (best == -1) {
|
2020-10-22 23:36:58 +02:00
|
|
|
if (all_done) break;
|
2019-07-01 15:32:26 +02:00
|
|
|
|
|
|
|
|
// It is hard to know when new info will allows for more task to be
|
|
|
|
|
// scheduled, so for now we just sleep for a bit. Note that in practice We
|
|
|
|
|
// will never reach here except at the end of the search because we can
|
|
|
|
|
// always schedule LNS threads.
|
|
|
|
|
absl::SleepFor(absl::Milliseconds(1));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Schedule next task.
|
|
|
|
|
num_generated_tasks[best]++;
|
|
|
|
|
{
|
|
|
|
|
absl::MutexLock mutex_lock(&mutex);
|
|
|
|
|
num_scheduled_and_not_done++;
|
|
|
|
|
}
|
|
|
|
|
std::function<void()> task = subsolvers[best]->GenerateTask(task_id++);
|
2019-07-05 09:33:04 +02:00
|
|
|
const std::string name = subsolvers[best]->name();
|
|
|
|
|
pool.Schedule([task, num_threads, name, &mutex, &num_scheduled_and_not_done,
|
2019-07-01 15:32:26 +02:00
|
|
|
&thread_available_condition]() {
|
|
|
|
|
task();
|
|
|
|
|
|
|
|
|
|
absl::MutexLock mutex_lock(&mutex);
|
2019-07-05 09:33:04 +02:00
|
|
|
VLOG(1) << name << " done.";
|
2019-07-01 15:32:26 +02:00
|
|
|
num_scheduled_and_not_done--;
|
|
|
|
|
if (num_scheduled_and_not_done == num_threads - 1) {
|
|
|
|
|
thread_available_condition.SignalAll();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
#endif // __PORTABLE_PLATFORM__
|
2019-07-01 15:32:26 +02:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|