From 1d707d127f1fac79dd866a79cf244ff52cf941b6 Mon Sep 17 00:00:00 2001 From: hmunozb Date: Wed, 14 Aug 2024 16:45:48 -0700 Subject: [PATCH 1/2] Add templated option to stop branching on first solution. --- include/pysa/branching/branching.hpp | 4 ++-- include/pysa/branching/thread.hpp | 2 +- include/pysa/dpll/dpll.hpp | 17 ++++++++++++----- tests/test_branching.cpp | 1 + tests/test_branching.hpp | 26 ++++++++++++++++---------- 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/include/pysa/branching/branching.hpp b/include/pysa/branching/branching.hpp index 7743d0b..9a72809 100644 --- a/include/pysa/branching/branching.hpp +++ b/include/pysa/branching/branching.hpp @@ -78,7 +78,7 @@ template typename Vector = std::vector> void branching_impl(const Function &fn, Branches &branches, const std::size_t n_threads, const Time &sleep_time, - ConstStopPtr stop) { + StopPtr stop) { // Single-threaded version if (n_threads == 1) { #ifndef NDEBUG @@ -179,7 +179,7 @@ struct Brancher : ThreadHandle> { Time &&sleep_time) : branches(n_threads), _core{[fn, n_threads, sleep_time, this]() { return submit( - [&fn, &n_threads, &sleep_time, this](ConstStopPtr stop) { + [&fn, &n_threads, &sleep_time, this](StopPtr stop) { branching_impl(fn, this->branches, n_threads, sleep_time, stop); }); }} { diff --git a/include/pysa/branching/thread.hpp b/include/pysa/branching/thread.hpp index 59fd3f3..7b913b2 100644 --- a/include/pysa/branching/thread.hpp +++ b/include/pysa/branching/thread.hpp @@ -152,7 +152,7 @@ auto submit(Function &&fn, Params &&...params) { // Return handle return ThreadHandle( std::async(std::forward(fn), std::forward(params)..., - ConstStopPtr{stop_}), + StopPtr{stop_}), std::move(stop_)); } diff --git a/include/pysa/dpll/dpll.hpp b/include/pysa/dpll/dpll.hpp index 882070e..03abdf4 100644 --- a/include/pysa/dpll/dpll.hpp +++ b/include/pysa/dpll/dpll.hpp @@ -23,8 +23,8 @@ specific language governing permissions and limitations under the License. namespace pysa::branching { -template -void DPLL_(Branches &&branches, Collect &&collect, ConstStopPtr stop) { +template +void DPLL_(Branches &&branches, Collect &&collect, StopPtr stop) { // While there are still branches ... while (std::size(branches) && !*stop) { // Get last branch (depth first) @@ -45,11 +45,18 @@ void DPLL_(Branches &&branches, Collect &&collect, ConstStopPtr stop) { branches.splice(std::end(branches), branch_.branch()); // Collect - collect(std::move(branch_)); + if constexpr (exit_on_first){ + if(collect(std::move(branch_))){ + *stop = true; + break; + } + } else { + collect(std::move(branch_)); + } } } -template auto DPLL(Branches &&branches, Collect &&collect, Args &&...args) { /* @@ -59,7 +66,7 @@ auto DPLL(Branches &&branches, Collect &&collect, Args &&...args) { // Get brancher return branching( [collect](auto &&branches, auto &&stop) { - DPLL_(branches, collect, stop); + DPLL_(branches, collect, stop); }, std::forward(branches), std::forward(args)...); } diff --git a/tests/test_branching.cpp b/tests/test_branching.cpp index 88d0744..52f9267 100644 --- a/tests/test_branching.cpp +++ b/tests/test_branching.cpp @@ -53,6 +53,7 @@ int main() { { // Use one thread pysa::branching::TestBranching(28, 1, true); + pysa::branching::TestBranching(28, 1, true); // Use number of threads provided by the implementation pysa::branching::TestBranching(30, 0, true); diff --git a/tests/test_branching.hpp b/tests/test_branching.hpp index 5580f6d..14062fa 100644 --- a/tests/test_branching.hpp +++ b/tests/test_branching.hpp @@ -62,6 +62,7 @@ struct Branch { using Branches = std::list; +template void TestBranching(const std::size_t n, const std::size_t n_threads = 0, const bool verbose = false) { // How to collect the branches @@ -72,11 +73,14 @@ void TestBranching(const std::size_t n, const std::size_t n_threads = 0, if (CheckBranch(branch)) { const std::scoped_lock lock_(mutex_); collected_.push_back(branch.state); + return true; + } else { + return false; } }; // Get branches - auto brancher_ = DPLL(Branches{Branch{n, 0, 0}}, collect_, n_threads); + auto brancher_ = DPLL(Branches{Branch{n, 0, 0}}, collect_, n_threads); // Start brancher auto it_ = std::chrono::high_resolution_clock::now(); @@ -98,18 +102,20 @@ void TestBranching(const std::size_t n, const std::size_t n_threads = 0, .count() << std::endl; - // Sort collected numbers - std::sort(std::begin(collected_), std::end(collected_)); + if constexpr (!stop_on_first){ + // Sort collected numbers + std::sort(std::begin(collected_), std::end(collected_)); - // Get head - auto head_ = std::cbegin(collected_); + // Get head + auto head_ = std::cbegin(collected_); - // Check results - for (std::size_t i_ = 0, end_ = std::size_t{1} << n; i_ < end_; ++i_) - if (CheckBranch(Branch{n, i_, 0})) assert(*head_++ == i_); + // Check results + for (std::size_t i_ = 0, end_ = std::size_t{1} << n; i_ < end_; ++i_) + if (CheckBranch(Branch{n, i_, 0})) assert(*head_++ == i_); - // All numbers should have been checked at this point - assert(head_ == std::cend(collected_)); + // All numbers should have been checked at this point + assert(head_ == std::cend(collected_)); + } } #ifdef USE_MPI From 83ac22dad544d776c9b4657aa75baba21fb6b4ff Mon Sep 17 00:00:00 2001 From: hmunozb Date: Wed, 14 Aug 2024 19:14:05 -0700 Subject: [PATCH 2/2] Add templated option to stop branching on first solution. --- include/pysa/branching/branching.hpp | 28 +++++++++++++++++++++++++--- include/pysa/branching/thread.hpp | 2 +- include/pysa/dpll/dpll.hpp | 8 ++++---- tests/test_branching.cpp | 2 +- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/include/pysa/branching/branching.hpp b/include/pysa/branching/branching.hpp index 9a72809..e8a53c3 100644 --- a/include/pysa/branching/branching.hpp +++ b/include/pysa/branching/branching.hpp @@ -40,6 +40,10 @@ specific language governing permissions and limitations under the License. namespace pysa::branching { +enum struct BranchResult{ + BRANCHOK, // default exit status + BRANCHEXIT // terminate all threads +}; /** * @brief Split a collection of branches into two. * Branches is a container that supports efficient .front() and .pop_front() @@ -78,7 +82,7 @@ template typename Vector = std::vector> void branching_impl(const Function &fn, Branches &branches, const std::size_t n_threads, const Time &sleep_time, - StopPtr stop) { + ConstStopPtr stop) { // Single-threaded version if (n_threads == 1) { #ifndef NDEBUG @@ -96,7 +100,7 @@ void branching_impl(const Function &fn, Branches &branches, // Define core auto core_ = [fn = fn, &branches](std::size_t idx, auto &&stop) { - fn(branches[idx], stop); + return fn(branches[idx], stop); }; // Initialize threads @@ -133,12 +137,30 @@ void branching_impl(const Function &fn, Branches &branches, return std::tuple{min_, max_}; }; + bool exit_all = false; // Avoid a race condition where count_n_branches_() may be 0 temporarily at // start std::this_thread::sleep_for(sleep_time); // Keep going if there are still branches or the stop signal is off while (count_n_branches_() && !*stop) { + for (auto& t: threads_){ + if(t.is_ready()){ + if(t.get() == BranchResult::BRANCHEXIT){ + exit_all = true; + } + } + } + if(exit_all){ +#ifndef NDEBUG + std::cerr << "# Brancher exit " << std::endl; +#endif + for (auto& t: threads_){ + if(t.is_running()) + t.stop(); + } + return; + } // Propagate branches between two threads if (const auto [ei_, ni_] = balance_indexes_(); ei_ && ni_) { const auto e_idx_ = ei_.value(); @@ -179,7 +201,7 @@ struct Brancher : ThreadHandle> { Time &&sleep_time) : branches(n_threads), _core{[fn, n_threads, sleep_time, this]() { return submit( - [&fn, &n_threads, &sleep_time, this](StopPtr stop) { + [&fn, &n_threads, &sleep_time, this](ConstStopPtr stop) { branching_impl(fn, this->branches, n_threads, sleep_time, stop); }); }} { diff --git a/include/pysa/branching/thread.hpp b/include/pysa/branching/thread.hpp index 7b913b2..59fd3f3 100644 --- a/include/pysa/branching/thread.hpp +++ b/include/pysa/branching/thread.hpp @@ -152,7 +152,7 @@ auto submit(Function &&fn, Params &&...params) { // Return handle return ThreadHandle( std::async(std::forward(fn), std::forward(params)..., - StopPtr{stop_}), + ConstStopPtr{stop_}), std::move(stop_)); } diff --git a/include/pysa/dpll/dpll.hpp b/include/pysa/dpll/dpll.hpp index 03abdf4..547661b 100644 --- a/include/pysa/dpll/dpll.hpp +++ b/include/pysa/dpll/dpll.hpp @@ -24,7 +24,7 @@ specific language governing permissions and limitations under the License. namespace pysa::branching { template -void DPLL_(Branches &&branches, Collect &&collect, StopPtr stop) { +BranchResult DPLL_(Branches &&branches, Collect &&collect, ConstStopPtr stop) { // While there are still branches ... while (std::size(branches) && !*stop) { // Get last branch (depth first) @@ -47,13 +47,13 @@ void DPLL_(Branches &&branches, Collect &&collect, StopPtr stop) { // Collect if constexpr (exit_on_first){ if(collect(std::move(branch_))){ - *stop = true; - break; + return BranchResult::BRANCHEXIT; } } else { collect(std::move(branch_)); } } + return BranchResult::BRANCHOK; } template (branches, collect, stop); + return DPLL_(branches, collect, stop); }, std::forward(branches), std::forward(args)...); } diff --git a/tests/test_branching.cpp b/tests/test_branching.cpp index 52f9267..0940f78 100644 --- a/tests/test_branching.cpp +++ b/tests/test_branching.cpp @@ -54,9 +54,9 @@ int main() { // Use one thread pysa::branching::TestBranching(28, 1, true); pysa::branching::TestBranching(28, 1, true); - // Use number of threads provided by the implementation pysa::branching::TestBranching(30, 0, true); + pysa::branching::TestBranching(30, 0, true); } #ifdef USE_MPI MPI_Barrier(mpi_comm_world);