88#ifndef __PBeFS_ParallelBranchAndBound
89 #define __PBeFS_ParallelBranchAndBound
104#include <type_traits>
108#include "ADS/parallel/Channels.h"
110#include "ADS/parallel/Mailbox.h"
133template<
class V =
double >
168template<
class V ,
bool = std::is_trivially_copyable_v< V > >
169struct bb_lock_free : std::false_type {};
172struct bb_lock_free< V , true >
173 : std::bool_constant< std::atomic< V >::is_always_lock_free > {};
187template<
class Node ,
class V =
double >
200 [[nodiscard]] V
value(
void )
const {
201 if constexpr( c_atomic )
202 return( f_value.load( std::memory_order_relaxed ) );
204 std::lock_guard< std::mutex > guard( f_m );
215 if( c_atomic && ( v >=
value() ) )
217 std::lock_guard< std::mutex > guard( f_m );
220 if constexpr( c_atomic )
221 f_value.store( v , std::memory_order_relaxed );
233 std::optional< Node >
take_node(
void ) {
return( std::move( f_node ) ); }
238 static constexpr bool c_atomic = detail::bb_lock_free< V >::value;
241 [[nodiscard]] V stored(
void )
const {
242 if constexpr( c_atomic )
243 return( f_value.load( std::memory_order_relaxed ) );
248 alignas(
cache_line ) std::conditional_t< c_atomic , std::atomic< V > , V >
250 mutable std::mutex f_m;
251 std::optional< Node > f_node;
264template< BranchAndBound P >
265void bb_finish( BBSolution<
typename P::node_type ,
bb_value_t< P > > & sol ,
268 bool limit ,
bool time )
271 using Tr = BBValueTraits< V >;
272 const V z = inc.value();
277 sol.node = inc.take_node();
278 if( Tr::finite( z ) )
279 sol.value = bb_signed< P >( z );
280 const V lb = std::min( z , fr.low );
281 sol.bound = bb_signed< P >( lb );
282 sol.gap = Tr::finite( z ) && Tr::finite( lb ) ? V( z - lb )
284 fr.template take< P >( sol.frontier );
293template<
class Item ,
class V >
294std::size_t bb_dive(
const std::vector< Item > & kids , V best , V inc ,
295 const BasicBBOptions< V > & o )
298 return( kids.size() - 1 );
300 return( kids.size() );
302 for( std::size_t j = 1 ; j < kids.size() ; ++j )
303 if( kids[ j ].bound <= kids[ c ].bound )
305 using Tr = BBValueTraits< V >;
306 const V b0 = std::min( best , kids[ c ].bound );
307 if( ! Tr::finite( inc ) ||
308 Tr::within( kids[ c ].bound , b0 , inc , o.plunge_gap ) )
310 return( kids.size() );
319bool bb_limit(
const BasicBBOptions< V > & o , std::uint64_t evaluated ,
320 const Stopwatch & clock , std::atomic< bool > & limit ,
321 std::atomic< bool > & time )
323 const bool nodes = o.node_limit && ( evaluated >= o.node_limit );
324 const bool late = ( o.time_limit > 0 ) && ( clock.seconds() >= o.time_limit );
325 if( ! ( nodes || late ) )
347template< BranchAndBound P , ADS::WorkerExecutor Executor = ADS::ThreadPool >
356 using Node =
typename P::node_type;
368 : f_p( problem ) , f_options( options ) {
383 return( run( std::nullopt ) );
391 return( run( std::move( seeds ) ) );
398 SolutionT run( std::optional< std::vector< Leaf > > seeds )
const
401 const unsigned p = f_options.
threads;
405 detail::bb_signed< P >( *f_options.
incumbent ) : inf;
406 Shared sh( start , sel );
407 for(
auto & l : detail::bb_starts( f_p , std::move( seeds ) ) ) {
408 const Value key = detail::bb_key( f_p , l.node , l.bound , l.depth ,
410 sh.q.push( std::move( l.node ) , l.bound , l.depth , key );
412 sh.open.store( sh.q.size() );
413 sh.max_open = sh.q.size();
415 std::vector< std::unique_ptr< Worker > > w( p );
416 for(
unsigned i = 0 ; i < p ; ++i )
417 w[ i ] = std::make_unique< Worker >( *
this , sh , clock );
418 run_workers( f_ex , p , [ & ](
unsigned i ) { w[ i ]->run( i ); } );
421 detail::BBFrontier< Node , Value > fr( f_options.keep_frontier );
423 for(
auto & x : w ) {
424 sol.stats.add( x->f_st );
425 fr.merge( std::move( x->f_fr ) );
427 sol.stats.max_open = sh.max_open;
428 detail::bb_finish< P >( sol , sh.inc , fr , sh.limit.load() ,
430 sol.stats.seconds = clock.
seconds();
436 using Lock =
typename Executor::lock_type;
437 using Item =
typename BBQueue< Node , Value >::Item;
446 BBIncumbent< Node , Value > inc;
448 BBQueue< Node , Value > q;
449 std::size_t max_open = 0;
450 alignas(
cache_line ) std::atomic< std::size_t > open{ 0 };
451 alignas(
cache_line ) std::atomic< unsigned > busy{ 0 };
452 alignas(
cache_line ) std::atomic< std::uint64_t > evaluated{ 0 };
453 alignas(
cache_line ) std::atomic< bool > done{
false };
454 std::atomic< bool > limit{
false };
455 std::atomic< bool > time{
false };
461 struct alignas( cache_line ) Worker {
464 : f_a( a ) , f_sh( s ) , f_clock( c ) ,
465 f_fr( a.f_options.keep_frontier ) {}
468 void run(
unsigned i ) {
469 auto w = detail::bb_worker( f_a.f_p , i );
470 const auto & o = f_a.f_options;
472 std::optional< Item > dive;
473 std::vector< Item > kids;
474 const auto inc = [ & ] {
return( f_sh.inc.value() ); };
475 const auto improve = [ & ](
Value v ,
const Node & n ) {
476 return( f_sh.inc.template improve< P >( v , n , o ) );
480 std::optional< Item > it;
482 it.emplace( std::move( *dive ) );
486 if( ! ( it = next() ) )
490 f_fr.add( std::move( it->node ) , it->bound , it->depth ,
492 f_sh.busy.fetch_sub( 1 );
497 detail::bb_process< P >( it->node , it->bound , it->depth , w , inc ,
500 kids.push_back( Item{ std::move( c ) , b , d } );
501 } , o , f_st , f_fr );
502 f_sh.evaluated.fetch_add( 1 );
504 std::lock_guard< Lock > guard( f_sh.lock );
505 const std::size_t k = detail::bb_dive( kids , f_sh.q.best_bound() ,
507 for( std::size_t j = 0 ; j < kids.size() ; ++j )
509 dive.emplace( std::move( kids[ j ] ) );
511 const Value key = detail::bb_key( f_a.f_p , kids[ j ].node ,
512 kids[ j ].bound , kids[ j ].depth ,
514 f_sh.q.push( std::move( kids[ j ].node ) , kids[ j ].bound ,
515 kids[ j ].depth , key );
517 f_sh.open.store( f_sh.q.size() );
518 f_sh.max_open = std::max( f_sh.max_open , f_sh.q.size() );
520 f_sh.busy.fetch_sub( 1 );
526 std::optional< Item > next(
void ) {
527 ADS::Backoff backoff;
529 if( f_sh.done.load( std::memory_order_acquire ) )
530 return( std::nullopt );
531 if( f_sh.open.load() == 0 ) {
532 if( f_sh.busy.load() == 0 ) {
533 std::lock_guard< Lock > guard( f_sh.lock );
534 if( f_sh.q.empty() && ( f_sh.busy.load() == 0 ) )
535 f_sh.done.store(
true , std::memory_order_release );
541 std::lock_guard< Lock > guard( f_sh.lock );
544 const Value best = f_sh.q.best_bound();
545 if( detail::bb_prunable( best , f_sh.inc.value() , f_a.f_options ) ) {
547 f_sh.open.store( 0 );
550 f_sh.busy.fetch_add( 1 );
551 Item it = f_sh.q.pop();
552 f_sh.open.store( f_sh.q.size() );
559 if( ! detail::bb_limit( f_a.f_options , f_sh.evaluated.load() , f_clock ,
560 f_sh.limit , f_sh.time ) )
562 f_sh.done.store(
true , std::memory_order_release );
568 const Stopwatch & f_clock;
570 detail::BBFrontier< Node , Value > f_fr;
577 Executor * f_ex =
nullptr;
594template< BranchAndBound P , ADS::WorkerExecutor Executor = ADS::ThreadPool >
603 using Node =
typename P::node_type;
622 : f_p( problem ) , f_options( options ) {
623 f_options.normalize();
637 return( run( std::nullopt ) );
647 return( run( std::move( seeds ) ) );
654 SolutionT run( std::optional< std::vector< Leaf > > seeds )
const
657 const unsigned p = f_options.threads;
659 const Value start = f_options.incumbent ?
660 detail::bb_signed< P >( *f_options.incumbent ) : inf;
661 Shared sh( p , start , f_options.batch );
665 for(
auto & l : detail::bb_starts( f_p , std::move( seeds ) ) ) {
668 dst =
unsigned( std::uint64_t( f_p.hash( l.node ) ) % p );
670 dst = unsigned( i % p );
672 b[ dst ].push_back( Msg{ std::move( l.node ) , l.bound , l.depth } );
676 std::vector< std::unique_ptr< Worker > > w( p );
677 for(
unsigned i = 0 ; i < p ; ++i )
678 w[ i ] = std::make_unique< Worker >( *
this , sh , i , clock );
679 run_workers( f_ex , p , [ & ](
unsigned i ) { w[ i ]->run(); } );
683 detail::BBFrontier< Node , Value > fr( f_options.keep_frontier );
684 const auto open = [ & ]( Msg && m ) {
685 fr.add( std::move( m.node ) , m.bound , m.depth , BBLeafKind::open );
687 for(
unsigned i = 0 ; i < p ; ++i ) {
688 Worker & x = *w[ i ];
689 x.f_st.messages = sh.mail.batches( i );
690 sol.stats.add( x.f_st );
691 fr.merge( std::move( x.f_fr ) );
692 fr.add( x.f_q , BBLeafKind::open );
694 fr.add( std::move( x.f_dive->node ) , x.f_dive->bound ,
695 x.f_dive->depth , BBLeafKind::open );
697 sh.mail.leftovers( open );
698 detail::bb_finish< P >( sol , sh.inc , fr , sh.limit.load() ,
700 sol.stats.seconds = clock.
seconds();
706 using Mailbox = ADS::Mailbox< Msg , ADS::DefaultChannels< Msg , Executor > >;
707 using Item =
typename BBQueue< Node , Value >::Item;
714 Shared(
unsigned np , Value z , std::size_t batch )
715 : p( np ) , mail( np , batch ) , seeds( np ) , inc( z ) {}
719 std::vector< std::vector< Msg > > seeds;
720 BBIncumbent< Node , Value > inc;
721 alignas(
cache_line ) std::atomic< std::uint64_t > evaluated{ 0 };
722 std::atomic< bool > limit{
false };
723 std::atomic< bool > time{
false };
729 struct alignas( cache_line ) Worker {
731 Worker(
const DistributedBranchAndBound & a , Shared & s ,
unsigned i ,
732 const Stopwatch & c )
733 : f_a( a ) , f_sh( s ) , f_id( i ) , f_clock( c ) ,
734 f_q( a.f_options.selection ) ,
735 f_gen( a.f_options.seed + i ) ,
736 f_fr( a.f_options.keep_frontier ) {}
740 auto w = detail::bb_worker( f_a.f_p , f_id );
741 const auto & o = f_a.f_options;
742 const auto inc = [ & ] {
return( f_sh.inc.value() ); };
743 const auto improve = [ & ]( Value v ,
const Node & n ) {
744 return( f_sh.inc.template improve< P >( v , n , o ) );
746 for( Msg & m : f_sh.seeds[ f_id ] )
747 push( Item{ std::move( m.node ) , m.bound , m.depth } );
748 unsigned since_flush = 0;
749 ADS::Backoff backoff;
751 while( ! f_sh.mail.stopped() ) {
752 f_sh.mail.poll( f_id , [ & ]( Msg && m ) {
753 push( Item{ std::move( m.node ) , m.bound , m.depth } );
756 if( std::optional< Item > it = next() ) {
759 f_fr.add( std::move( it->node ) , it->bound , it->depth ,
764 detail::bb_process< P >( it->node , it->bound , it->depth , w , inc ,
765 improve , [ & ]( Node && c , Value b ,
767 v_kids.push_back( Item{ std::move( c ) , b ,
769 } , o , f_st , f_fr );
770 f_sh.evaluated.fetch_add( 1 );
771 const std::size_t k = detail::bb_dive( v_kids , f_q.best_bound() ,
773 for( std::size_t j = 0 ; j < v_kids.size() ; ++j )
775 f_dive.emplace( std::move( v_kids[ j ] ) );
777 send( std::move( v_kids[ j ] ) );
778 if( ++since_flush >= f_a.f_options.flush_period ) {
780 f_sh.mail.flush( f_id );
787 if( f_sh.mail.quiescent( f_id ) )
794 void push( Item && it ) {
795 const Value key = detail::bb_key( f_a.f_p , it.node , it.bound ,
796 it.depth , f_a.f_options.selection );
797 f_q.push( std::move( it.node ) , it.bound , it.depth , key );
798 f_st.max_open = std::max( f_st.max_open , f_q.size() );
803 std::optional< Item > next(
void ) {
805 std::optional< Item > it( std::move( f_dive ) );
810 return( std::nullopt );
811 const Value best = f_q.best_bound();
812 if( detail::bb_prunable( best , f_sh.inc.value() , f_a.f_options ) ) {
813 f_st.pruned_extracted += f_fr.add( f_q , BBLeafKind::pruned );
814 return( std::nullopt );
820 void send( Item && it ) {
821 const unsigned p = f_sh.p;
823 if constexpr( HashedBranchAndBound< P > )
824 dst =
unsigned( std::uint64_t( f_a.f_p.hash( it.node ) ) % p );
828 push( std::move( it ) );
831 f_sh.mail.send( f_id , dst , Msg{ std::move( it.node ) , it.bound ,
838 if( ! detail::bb_limit( f_a.f_options , f_sh.evaluated.load() , f_clock ,
839 f_sh.limit , f_sh.time ) )
845 const DistributedBranchAndBound & f_a;
848 const Stopwatch & f_clock;
849 BBQueue< Node , Value > f_q;
850 std::optional< Item > f_dive;
851 std::mt19937_64 f_gen;
852 std::vector< Item > v_kids;
854 detail::BBFrontier< Node , Value > f_fr;
861 Executor * f_ex =
nullptr;
Header file for the best-first search with an incumbent, i.e., for Branch-and-Bound (B&B): the concep...
BBIncumbent(V v)
constructor, taking the initial value (infinity() of BBValueTraits if none)
Definition ParallelBranchAndBound.h:195
bool improve(V v, const Node &n, const BasicBBOptions< V > &o)
offers the value v of the node n of the problem P; true if it is a new incumbent
Definition ParallelBranchAndBound.h:214
V value(void) const
the current value
Definition ParallelBranchAndBound.h:200
std::optional< Node > take_node(void)
moves out the node of the incumbent, if any
Definition ParallelBranchAndBound.h:233
BasicParallelBBOptions< Value > Options
the options
Definition ParallelBranchAndBound.h:605
typename P::node_type Node
the node type
Definition ParallelBranchAndBound.h:603
BBLeaf< Node, Value > Leaf
the leaf type
Definition ParallelBranchAndBound.h:606
SolutionT solve(std::vector< Leaf > seeds) const
searches the subtrees of the given nodes, returning the Solution [see BranchAndBoundSearch::solve()];...
Definition ParallelBranchAndBound.h:646
DistributedBranchAndBound(const P &problem, const Options &options={})
constructor, taking the problem (which must outlive the search)
Definition ParallelBranchAndBound.h:620
SolutionT solve(void) const
searches the tree from the root, returning the Solution
Definition ParallelBranchAndBound.h:636
BBSolution< Node, Value > SolutionT
the solution type
Definition ParallelBranchAndBound.h:607
void set_executor(Executor &ex) noexcept
sets the Executor that runs the workers, which must outlive the searches; without it,...
Definition ParallelBranchAndBound.h:631
bb_value_t< P > Value
the value type
Definition ParallelBranchAndBound.h:604
BasicParallelBBOptions< Value > Options
the options
Definition ParallelBranchAndBound.h:358
typename P::node_type Node
the node type
Definition ParallelBranchAndBound.h:356
SharedBranchAndBound(const P &problem, const Options &options={})
constructor, taking the problem (which must outlive the search)
Definition ParallelBranchAndBound.h:366
BBLeaf< Node, Value > Leaf
the leaf type
Definition ParallelBranchAndBound.h:359
SolutionT solve(std::vector< Leaf > seeds) const
searches the subtrees of the given nodes, returning the Solution [see BranchAndBoundSearch::solve()]
Definition ParallelBranchAndBound.h:390
SolutionT solve(void) const
searches the tree from the root, returning the Solution
Definition ParallelBranchAndBound.h:382
BBSolution< Node, Value > SolutionT
the solution type
Definition ParallelBranchAndBound.h:360
void set_executor(Executor &ex) noexcept
sets the Executor that runs the workers, which must outlive the searches; without it,...
Definition ParallelBranchAndBound.h:377
bb_value_t< P > Value
the value type
Definition ParallelBranchAndBound.h:357
a wall-clock stopwatch, started at construction
Definition SearchUtils.h:175
double seconds(void) const
the seconds elapsed since the construction
Definition SearchUtils.h:182
a B&B problem that gives a hash of its nodes
Definition BranchAndBound.h:353
namespace for the Parallel Best-First Search library
Definition AStar.h:83
@ open
left open by a limit
Definition BranchAndBound.h:167
@ pruned
its bound cannot improve the incumbent
Definition BranchAndBound.h:168
Selection
the order in which the open nodes are extracted
Definition BranchAndBound.h:137
@ plunging
a dive into the children, then best-first
Definition BranchAndBound.h:142
@ depth_first
the deepest node first
Definition BranchAndBound.h:139
constexpr std::size_t cache_line
the size of a cache line, used to keep the data of the workers apart
Definition SearchUtils.h:66
std::uint64_t random_below(Gen &gen, std::uint64_t n)
an integer in [ 0 , n ) from the next word of a 64-bit generator, n > 0
Definition SearchUtils.h:165
typename detail::bb_value< P >::type bb_value_t
the type of the values of the B&B problem P: its value_type, or double
Definition BranchAndBound.h:293
@ optimal
the incumbent is optimal within the tolerance
Definition BranchAndBound.h:147
@ time_limit
the time limit was reached first
Definition BranchAndBound.h:150
@ node_limit
the limit on the evaluated nodes was reached first
Definition BranchAndBound.h:149
@ infeasible
no feasible solution exists (nor was given)
Definition BranchAndBound.h:148
void run_workers(Executor *ex, unsigned p, F &&f)
runs the workers f( 0 ) , ... , f( p - 1 ) at once on an Executor
Definition SearchUtils.h:121
BasicParallelBBOptions<> ParallelBBOptions
the options of the parallel B&B with values of type double
Definition ParallelBranchAndBound.h:163
a leaf of the tree explored, or a node to start a search from
Definition BranchAndBound.h:181
the outcome of a B&B
Definition BranchAndBound.h:472
static constexpr V infinity(void) noexcept
the infinite value
Definition BranchAndBound.h:212
the options of a B&B with values of type V
Definition BranchAndBound.h:383
std::optional< V > incumbent
a known feasible value, if any
Definition BranchAndBound.h:389
std::function< void(V) > on_incumbent
called with every new incumbent value (in a parallel search, by the worker that finds it,...
Definition BranchAndBound.h:402
Selection selection
the order of the nodes
Definition BranchAndBound.h:385
the options of the parallel B&B with values of type V
Definition ParallelBranchAndBound.h:135
BasicParallelBBOptions(void)
Definition ParallelBranchAndBound.h:137
std::size_t round
Definition ParallelBranchAndBound.h:144
bool split_shared
Definition ParallelBranchAndBound.h:158
bool split_dive
Definition ParallelBranchAndBound.h:153
std::size_t split
Definition ParallelBranchAndBound.h:148
a node travelling to its owner
Definition ParallelBranchAndBound.h:610
Value bound
Definition ParallelBranchAndBound.h:612
unsigned depth
Definition ParallelBranchAndBound.h:613
Node node
Definition ParallelBranchAndBound.h:611
a node of the search graph
Definition Node.h:74
the parameters shared by the parallel searches
Definition SearchUtils.h:94
std::size_t batch
nodes packed in a message
Definition SearchUtils.h:97
unsigned flush_period
steps between flushes of outboxes
Definition SearchUtils.h:98
unsigned threads
the number of workers
Definition SearchUtils.h:96
void normalize(void) noexcept
sets to 1 any parameter that is 0
Definition SearchUtils.h:102