PBeFS
Parallel Best-First Search
Loading...
Searching...
No Matches
MPIBranchAndBound.h
Go to the documentation of this file.
1/*--------------------------------------------------------------------------*/
2/*------------------------ File MPIBranchAndBound.h ------------------------*/
3/*--------------------------------------------------------------------------*/
58/*--------------------------------------------------------------------------*/
59/*------------------------------ DEFINITIONS -------------------------------*/
60/*--------------------------------------------------------------------------*/
61
62#ifndef __PBeFS_MPIBranchAndBound
63 #define __PBeFS_MPIBranchAndBound
64 /* self-identification: #endif at the end of the file */
65
66/*--------------------------------------------------------------------------*/
67/*-------------------------------- INCLUDES --------------------------------*/
68/*--------------------------------------------------------------------------*/
69
70#include <cstdint>
71
72#include <cstring>
73
74#include <random>
75
76#include <span>
77
78#include <type_traits>
79
80#include <vector>
81
82#include <mpi.h>
83
84#include "ADS/distributed/MPIMailbox.h"
85
87
88/*--------------------------------------------------------------------------*/
89/*------------------------------- NAMESPACE --------------------------------*/
90/*--------------------------------------------------------------------------*/
91
93namespace PBeFS_di_unipi_it
94{
95
96/*--------------------------------------------------------------------------*/
97/*-------------------------------- CONCEPTS --------------------------------*/
98/*--------------------------------------------------------------------------*/
100
105
106template< class P >
108 std::is_trivially_copyable_v< bb_value_t< P > > &&
109 ( std::is_trivially_copyable_v< typename P::node_type > ||
110 requires( const P & p , const typename P::node_type & n ,
111 std::vector< char > & out , const char * & in ) {
112 p.serialize( n , out );
113 { p.deserialize( in ) } -> std::convertible_to< typename P::node_type >;
114 } );
115
116/*--------------------------------------------------------------------------*/
117/*------------------------------- FUNCTIONS --------------------------------*/
118/*--------------------------------------------------------------------------*/
119
120namespace detail {
121
123
124template< class T >
125void bb_put_bytes( const T & x , std::vector< char > & out )
126{
127 const auto * b = reinterpret_cast< const char * >( & x );
128 out.insert( out.end() , b , b + sizeof( T ) );
129 }
130
131/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
133
134template< class T >
135T bb_get_bytes( const char * & in )
136{
137 T x;
138 std::memcpy( static_cast< void * >( & x ) , in , sizeof( T ) );
139 in += sizeof( T );
140 return( x );
141 }
142
143/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
145
146template< MPIBranchAndBound P >
147void bb_put_node( const P & p , const typename P::node_type & n ,
148 std::vector< char > & out )
149{
150 if constexpr( requires { p.serialize( n , out ); } )
151 p.serialize( n , out );
152 else
153 bb_put_bytes( n , out );
154 }
155
156/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
158
159template< MPIBranchAndBound P >
160typename P::node_type bb_get_node( const P & p , const char * & in )
161{
162 if constexpr( requires { p.deserialize( in ); } )
163 return( p.deserialize( in ) );
164 else
165 return( bb_get_bytes< typename P::node_type >( in ) );
166 }
167
168/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
170
171template< class V >
172std::vector< V > bb_all_values( const V & v , MPI_Comm comm )
173{
174 int np;
175 MPI_Comm_size( comm , & np );
176 std::vector< V > all( std::size_t( np ) , v );
177 MPI_Allgather( & v , int( sizeof( V ) ) , MPI_BYTE , all.data() ,
178 int( sizeof( V ) ) , MPI_BYTE , comm );
179 return( all );
180 }
181
182/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
185
186template< class V >
187std::pair< V , int > bb_all_min( const V & v , MPI_Comm comm )
188{
189 const std::vector< V > all = bb_all_values( v , comm );
190 int r = 0;
191 for( int q = 1 ; q < int( all.size() ) ; ++q )
192 if( all[ q ] < all[ r ] )
193 r = q;
194 return( std::pair( all[ r ] , r ) );
195 }
196
197} // end( namespace detail )
198
199/*--------------------------------------------------------------------------*/
200/*------------------------ CLASS MPIBranchAndBound -------------------------*/
201/*--------------------------------------------------------------------------*/
203
223
224template< MPIBranchAndBound P >
226
227/*------------------------ PUBLIC PART OF THE CLASS ------------------------*/
228
229 public:
230
231/*------------------------------ PUBLIC TYPES ------------------------------*/
232
233 using Node = typename P::node_type;
238
239/*------------------------------ CONSTRUCTOR -------------------------------*/
240
243
244 explicit MPIBranchAndBoundSearch( const P & problem ,
245 const Options & options = {} ,
246 MPI_Comm comm = MPI_COMM_WORLD )
247 : f_p( problem ) , f_options( options ) , f_comm( comm ) {
248 f_options.normalize();
249 }
250
251/*-------------------------------- SOLVING ---------------------------------*/
254
255 [[nodiscard]] SolutionT solve( void ) const {
256 int rank;
257 MPI_Comm_rank( f_comm , & rank );
258 if( rank == 0 )
259 return( run( std::nullopt ) );
260 return( run( std::vector< Leaf >() ) );
261 }
262
263/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
266
267 [[nodiscard]] SolutionT solve( std::vector< Leaf > seeds ) const {
268 return( run( std::move( seeds ) ) );
269 }
270
271/*----------------------- PRIVATE PART OF THE CLASS ------------------------*/
272
273 private:
274
275 SolutionT run( std::optional< std::vector< Leaf > > seeds ) const
276 {
277 Stopwatch clock;
278 Process w( *this , clock );
279 w.run( detail::bb_starts( f_p , std::move( seeds ) ) );
280 SolutionT sol;
281 w.gather( sol );
282 sol.stats.seconds = clock.seconds();
283 return( sol );
284 }
285
286 using Item = typename BBQueue< Node , Value >::Item;
287
288 // the tags of the records: a batch of nodes, an incumbent value
289 enum Tag : std::uint32_t { c_tag_work = 1 , c_tag_incumbent = 2 };
290
291/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
292 // the search on one process
293
294 struct Process {
295
296 Process( const MPIBranchAndBoundSearch & a , const Stopwatch & c )
297 : f_a( a ) , f_comm( a.f_comm ) , f_mail( a.f_comm , 1 ) , f_clock( c ) ,
298 f_q( a.f_options.selection ) , f_fr( a.f_options.keep_frontier ) {
299 MPI_Comm_rank( f_comm , & f_rank );
300 int np;
301 MPI_Comm_size( f_comm , & np );
302 f_np = unsigned( np );
303 v_out.resize( f_np );
304 v_count.assign( f_np , 0 );
305 f_gen.seed( a.f_options.seed + unsigned( f_rank ) );
306 f_inc = a.f_options.incumbent ?
307 detail::bb_signed< P >( *a.f_options.incumbent ) :
308 BBValueTraits< Value >::infinity();
309 const std::size_t lim = a.f_options.node_limit;
310 f_limit = lim ? ( lim + f_np - 1 ) / f_np : 0;
311 }
312
313 // the main loop, from the given nodes
314 void run( std::vector< Leaf > && starts ) {
315 auto w = detail::bb_worker( f_a.f_p , unsigned( f_rank ) );
316 const auto & o = f_a.f_options;
317 const auto inc = [ & ] { return( f_inc ); };
318 const auto improve = [ & ]( Value v , const Node & n ) {
319 if( v >= f_inc )
320 return( false );
321 f_inc = f_node_value = v;
322 f_node = n;
323 if( o.on_incumbent )
324 o.on_incumbent( detail::bb_signed< P >( v ) );
325 std::vector< char > b;
326 detail::bb_put_bytes( v , b );
327 for( unsigned q = 0 ; q < f_np ; ++q )
328 if( q != unsigned( f_rank ) )
329 f_mail.send( q , c_tag_incumbent , std::as_bytes( std::span( b ) ) );
330 return( true );
331 };
332
333 for( auto & l : starts )
334 push( Item{ std::move( l.node ) , l.bound , l.depth } );
335 starts.clear();
336 unsigned since_flush = 0;
337 for( ;; ) {
338 receive();
339 if( std::optional< Item > it = next() ) {
340 v_kids.clear();
341 detail::bb_process< P >( it->node , it->bound , it->depth , w , inc ,
342 improve , [ & ]( Node && c , Value b ,
343 unsigned d ) {
344 v_kids.push_back( Item{ std::move( c ) , b ,
345 d } );
346 } , o , f_st , f_fr );
347 const std::size_t k = detail::bb_dive( v_kids , f_q.best_bound() ,
348 f_inc , o );
349 for( std::size_t j = 0 ; j < v_kids.size() ; ++j )
350 if( j == k )
351 f_dive.emplace( std::move( v_kids[ j ] ) );
352 else
353 send( std::move( v_kids[ j ] ) );
354 if( ++since_flush >= f_a.f_options.flush_period ) {
355 since_flush = 0;
356 flush();
357 }
358 check_limits();
359 continue;
360 }
361 flush(); // nothing to evaluate: send everything
362 if( f_mail.quiescent() )
363 break;
364 }
365 // after a stop, the nodes still travelling are left open
366 f_mail.leftovers( [ & ]( std::uint32_t t ,
367 std::span< const std::byte > b ) {
368 take( t , b , true );
369 } );
370 }
371
372 // pushes a node in the queue
373 void push( Item && it ) {
374 const Value key = detail::bb_key( f_a.f_p , it.node , it.bound ,
375 it.depth , f_a.f_options.selection );
376 f_q.push( std::move( it.node ) , it.bound , it.depth , key );
377 f_st.max_open = std::max( f_st.max_open , f_q.size() );
378 }
379
380 // true if no node of the queue can improve the incumbent; then, without
381 // an abort, the queue is emptied
382 bool prunable( void ) {
383 if( f_q.empty() )
384 return( true );
385 const Value best = f_q.best_bound();
386 if( ! detail::bb_prunable( best , f_inc , f_a.f_options ) )
387 return( false );
388 f_st.pruned_extracted += f_fr.add( f_q , BBLeafKind::pruned );
389 return( true );
390 }
391
392 // the next node: the dive, or the best of the queue; none if there is
393 // none that can improve the incumbent, or after an abort
394 std::optional< Item > next( void ) {
395 if( f_mail.stopped() )
396 return( std::nullopt );
397 if( f_dive ) {
398 std::optional< Item > it( std::move( f_dive ) );
399 f_dive.reset();
400 return( it );
401 }
402 if( prunable() )
403 return( std::nullopt );
404 return( f_q.pop() );
405 }
406
407 // stops the evaluations of all the processes if a limit is reached
408 void check_limits( void ) {
409 const auto & o = f_a.f_options;
410 const bool nodes = f_limit && ( f_st.evaluated >= f_limit );
411 const bool time = ( o.time_limit > 0 ) &&
412 ( f_clock.seconds() >= o.time_limit );
413 if( f_mail.stopped() || ! ( nodes || time ) )
414 return;
415 f_limit_hit = true;
416 f_time_hit = time;
417 f_mail.stop();
418 }
419
420 // sends a child to its owner, or keeps it if the process owns it
421 void send( Item && it ) {
422 unsigned dst;
423 if constexpr( HashedBranchAndBound< P > )
424 dst = unsigned( std::uint64_t( f_a.f_p.hash( it.node ) ) % f_np );
425 else
426 dst = unsigned( random_below( f_gen , f_np ) );
427 if( dst == unsigned( f_rank ) ) {
428 push( std::move( it ) );
429 return;
430 }
431 auto & o = v_out[ dst ];
432 detail::bb_put_bytes( it.bound , o );
433 detail::bb_put_bytes( it.depth , o );
434 detail::bb_put_node( f_a.f_p , it.node , o );
435 ++f_st.sent;
436 if( ++v_count[ dst ] >= f_a.f_options.batch )
437 post( dst );
438 }
439
440 // sends the outbox of dst
441 void post( unsigned dst ) {
442 f_mail.send( dst , c_tag_work ,
443 std::as_bytes( std::span( v_out[ dst ] ) ) );
444 v_out[ dst ].clear();
445 v_count[ dst ] = 0;
446 ++f_st.messages;
447 }
448
449 // sends the non-empty outboxes
450 void flush( void ) {
451 for( unsigned d = 0 ; d < f_np ; ++d )
452 if( ! v_out[ d ].empty() )
453 post( d );
454 }
455
456 // receives and processes all the messages that have arrived
457 void receive( void ) {
458 f_mail.poll( [ & ]( std::uint32_t t , std::span< const std::byte > b ) {
459 take( t , b , false );
460 } );
461 }
462
463 // a record of tag t: its nodes in the queue, or among the open leaves
464 // if open, or an incumbent value
465 void take( std::uint32_t t , std::span< const std::byte > b , bool open ) {
466 const char * in = reinterpret_cast< const char * >( b.data() );
467 const char * end = in + b.size();
468 if( t == c_tag_incumbent ) {
469 f_inc = std::min( f_inc , detail::bb_get_bytes< Value >( in ) );
470 return;
471 }
472 while( in < end ) {
473 const Value v = detail::bb_get_bytes< Value >( in );
474 const unsigned d = detail::bb_get_bytes< unsigned >( in );
475 Node n = detail::bb_get_node( f_a.f_p , in );
476 if( open )
477 f_fr.add( std::move( n ) , v , d , BBLeafKind::open );
478 else
479 push( Item{ std::move( n ) , v , d } );
480 }
481 }
482
483 // fills the Solution on every process: counters, status, bound, node
484 void gather( SolutionT & sol ) {
486 BBStats & st = sol.stats;
487 unsigned long long mine[ 11 ] = {
488 f_st.evaluated , f_st.branched , f_st.generated , f_st.infeasible ,
489 f_st.pruned_generated , f_st.pruned_extracted , f_st.pruned_evaluated ,
490 f_st.incumbents , f_st.sent , f_st.messages , f_st.max_open };
491 std::vector< unsigned long long > all( 11 * std::size_t( f_np ) );
492 MPI_Allgather( mine , 11 , MPI_UNSIGNED_LONG_LONG , all.data() , 11 ,
493 MPI_UNSIGNED_LONG_LONG , f_comm );
494 for( unsigned q = 0 ; q < f_np ; ++q ) {
495 const auto * x = all.data() + 11 * std::size_t( q );
496 BBStats s;
497 s.evaluated = x[ 0 ];
498 s.branched = x[ 1 ];
499 s.generated = x[ 2 ];
500 s.infeasible = x[ 3 ];
501 s.pruned_generated = x[ 4 ];
502 s.pruned_extracted = x[ 5 ];
503 s.pruned_evaluated = x[ 6 ];
504 s.incumbents = x[ 7 ];
505 s.sent = x[ 8 ];
506 s.messages = x[ 9 ];
507 s.max_open = std::size_t( x[ 10 ] );
508 st.add( s );
509 }
510
511 int flags[ 2 ] = { f_limit_hit ? 1 : 0 , f_time_hit ? 1 : 0 } , any[ 2 ];
512 MPI_Allreduce( flags , any , 2 , MPI_INT , MPI_MAX , f_comm );
513
514 // the bound: the nodes pruned by the tolerance and those left open
515 f_fr.add( f_q , BBLeafKind::open );
516 if( f_dive ) {
517 f_fr.add( std::move( f_dive->node ) , f_dive->bound , f_dive->depth ,
519 f_dive.reset();
520 }
521 using Tr = BBValueTraits< Value >;
522 const Value glow = detail::bb_all_min( f_fr.low , f_comm ).first;
523
524 // the incumbent and the process holding its node
525 const auto [ wv , wr ] = detail::bb_all_min( f_node ? f_node_value : inf ,
526 f_comm );
527 const Value z = detail::bb_all_min( f_inc , f_comm ).first;
528 if( Tr::finite( wv ) && ( wv <= z ) ) {
529 std::vector< char > bytes;
530 if( f_rank == wr )
531 detail::bb_put_node( f_a.f_p , *f_node , bytes );
532 unsigned long long n = bytes.size();
533 MPI_Bcast( & n , 1 , MPI_UNSIGNED_LONG_LONG , wr , f_comm );
534 bytes.resize( std::size_t( n ) );
535 MPI_Bcast( bytes.data() , int( n ) , MPI_BYTE , wr , f_comm );
536 const char * in = bytes.data();
537 sol.node.emplace( detail::bb_get_node( f_a.f_p , in ) );
538 }
539
540 if( any[ 0 ] )
541 sol.status = any[ 1 ] ? BBStatus::time_limit : BBStatus::node_limit;
542 else
543 sol.status = Tr::finite( z ) ? BBStatus::optimal : BBStatus::infeasible;
544 if( Tr::finite( z ) )
545 sol.value = detail::bb_signed< P >( z );
546 const Value lb = std::min( z , glow );
547 sol.bound = detail::bb_signed< P >( lb );
548 sol.gap = Tr::finite( z ) && Tr::finite( lb ) ? Value( z - lb ) : inf;
549 f_fr.template take< P >( sol.frontier );
550 }
551
552 const MPIBranchAndBoundSearch & f_a; // the search
553 MPI_Comm f_comm; // the communicator
554 ADS::MPIByteMailbox f_mail; // its messages
555 const Stopwatch & f_clock; // the clock of the search
556 int f_rank = 0; // the rank of the process
557 unsigned f_np = 1; // the number of processes
558 BBQueue< Node , Value > f_q; // the open nodes
559 std::optional< Item > f_dive; // the node of the dive
560 std::vector< Item > v_kids; // the children of a node
561 std::vector< std::vector< char > > v_out; // the outboxes
562 std::vector< std::size_t > v_count; // the nodes in each one
563 std::mt19937_64 f_gen; // the random owners
564 Value f_inc; // the incumbent value
565 std::optional< Node > f_node; // its node, if found here
566 Value f_node_value = Value( 0 ); // the value of f_node
567 std::size_t f_limit; // its share of node_limit
568 bool f_limit_hit = false; // a limit was reached here
569 bool f_time_hit = false; // ... the time limit
570 BBStats f_st; // the counters
571 detail::BBFrontier< Node , Value > f_fr; // the leaves owned here
572 };
573
574/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
575
576 const P & f_p;
577 Options f_options;
578 MPI_Comm f_comm;
579
580 }; // end( class( MPIBranchAndBoundSearch ) )
581
582/*--------------------------------------------------------------------------*/
583
584} // end( namespace PBeFS_di_unipi_it )
585
586/*--------------------------------------------------------------------------*/
587
588#endif /* __PBeFS_MPIBranchAndBound */
589
590/*--------------------------------------------------------------------------*/
591/*---------------------- End File MPIBranchAndBound.h ----------------------*/
592/*--------------------------------------------------------------------------*/
Header file for the parallel best-first searches with an incumbent, i.e., the parallel Branch-and-Bou...
MPIBranchAndBoundSearch(const P &problem, const Options &options={}, MPI_Comm comm=MPI_COMM_WORLD)
constructor, taking the problem (which must outlive the search), the options (threads is ignored) and...
Definition MPIBranchAndBound.h:244
BasicParallelBBOptions< Value > Options
the options
Definition MPIBranchAndBound.h:235
typename P::node_type Node
the node type
Definition MPIBranchAndBound.h:233
BBLeaf< Node, Value > Leaf
the leaf type
Definition MPIBranchAndBound.h:236
SolutionT solve(std::vector< Leaf > seeds) const
searches the subtrees of the given nodes of this process, returning the Solution (on every process) [...
Definition MPIBranchAndBound.h:267
SolutionT solve(void) const
searches the tree from the root, returning the Solution (on every process)
Definition MPIBranchAndBound.h:255
BBSolution< Node, Value > SolutionT
the solution type
Definition MPIBranchAndBound.h:237
bb_value_t< P > Value
the value type
Definition MPIBranchAndBound.h:234
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
Definition BranchAndBound.h:337
a B&B problem whose nodes and values can travel between processes
Definition MPIBranchAndBound.h:107
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
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
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
BBStats stats
the counters
Definition BranchAndBound.h:479
double seconds
wall-clock time of the search
Definition BranchAndBound.h:430
static constexpr V infinity(void) noexcept
the infinite value
Definition BranchAndBound.h:212
void normalize(void) noexcept
sets to 1 any parameter that is 0
Definition SearchUtils.h:102