PBeFS
Parallel Best-First Search
Loading...
Searching...
No Matches
MultiQueueBranchAndBound.h
Go to the documentation of this file.
1/*--------------------------------------------------------------------------*/
2/*-------------------- File MultiQueueBranchAndBound.h ---------------------*/
3/*--------------------------------------------------------------------------*/
55/*--------------------------------------------------------------------------*/
56/*------------------------------ DEFINITIONS -------------------------------*/
57/*--------------------------------------------------------------------------*/
58
59#ifndef __PBeFS_MultiQueueBranchAndBound
60 #define __PBeFS_MultiQueueBranchAndBound
61 /* self-identification: #endif at the end of the file */
62
63/*--------------------------------------------------------------------------*/
64/*-------------------------------- INCLUDES --------------------------------*/
65/*--------------------------------------------------------------------------*/
66
67#include <algorithm>
68
69#include <atomic>
70
71#include <cstdint>
72
73#include <iterator>
74
75#include <limits>
76
77#include <memory>
78
79#include <optional>
80
81#include <utility>
82
83#include <vector>
84
86
87#include "ADS/concurrent/MultiQueue.h"
88
89/*--------------------------------------------------------------------------*/
90/*------------------------------- NAMESPACE --------------------------------*/
91/*--------------------------------------------------------------------------*/
92
94namespace PBeFS_di_unipi_it
95{
96
97/*--------------------------------------------------------------------------*/
98/*------------------------ CONCEPT MultiQueueValue -------------------------*/
99/*--------------------------------------------------------------------------*/
102
103template< class V >
104concept MultiQueueValue = std::numeric_limits< V >::is_specialized &&
105 std::atomic< V >::is_always_lock_free;
106
107/*--------------------------------------------------------------------------*/
108/*--------------------- CLASS MultiQueueBranchAndBound ---------------------*/
109/*--------------------------------------------------------------------------*/
111
124
125template< BranchAndBound P , ADS::WorkerExecutor Executor = ADS::ThreadPool >
128
129/*------------------------ PUBLIC PART OF THE CLASS ------------------------*/
130
131 public:
132
133/*------------------------------ PUBLIC TYPES ------------------------------*/
134
135 using Node = typename P::node_type;
140
141/*------------------------------ CONSTRUCTOR -------------------------------*/
142
144
145 explicit MultiQueueBranchAndBound( const P & problem ,
146 const Options & options = {} )
147 : f_p( problem ) , f_options( options ) {
148 f_options.normalize();
149 }
150
151/*------------------------- OTHER INITIALIZATIONS --------------------------*/
155
156 void set_executor( Executor & ex ) noexcept { f_ex = &ex; }
157
158/*-------------------------------- SOLVING ---------------------------------*/
160
161 [[nodiscard]] SolutionT solve( void ) const {
162 return( run( std::nullopt ) );
163 }
164
165/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
168
169 [[nodiscard]] SolutionT solve( std::vector< Leaf > seeds ) const {
170 return( run( std::move( seeds ) ) );
171 }
172
173/*----------------------- PRIVATE PART OF THE CLASS ------------------------*/
174
175 private:
176
177 // a node in the queue: the node and its depth, its bound beside them
178 struct Held {
179 Node node; // the node
180 Value bound; // its bound, in the minimization
181 unsigned depth; // its depth in the tree
182 };
183
184 using Mq = ADS::MultiQueue< Value , Held >;
185 using Item = typename BBQueue< Node , Value >::Item;
186
187/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
188
189 SolutionT run( std::optional< std::vector< Leaf > > seeds ) const
190 {
192 const unsigned p = f_options.threads;
193 Stopwatch clock;
194 const Value start = f_options.incumbent ?
195 detail::bb_signed< P >( *f_options.incumbent ) : inf;
196 Shared sh( p , start , f_options.seed );
197 {
198 auto hd = sh.q.handle();
199 std::size_t n = 0;
200 for( auto & l : detail::bb_starts( f_p , std::move( seeds ) ) ) {
201 const Value key = detail::bb_key( f_p , l.node , l.bound , l.depth ,
202 f_options.selection );
203 hd.push( key , Held{ std::move( l.node ) , l.bound , l.depth } );
204 ++n;
205 }
206 sh.held.store( n );
207 }
208
209 std::vector< std::unique_ptr< Worker > > w( p );
210 for( unsigned i = 0 ; i < p ; ++i )
211 w[ i ] = std::make_unique< Worker >( *this , sh , clock );
212 run_workers( f_ex , p , [ & ]( unsigned i ) { w[ i ]->run( i ); } );
213
214 // the nodes left open by a limit bound the optimum too
215 SolutionT sol;
216 detail::BBFrontier< Node , Value > fr( f_options.keep_frontier );
217 for( auto & x : w ) {
218 sol.stats.add( x->f_st );
219 fr.merge( std::move( x->f_fr ) );
220 }
221 auto hd = sh.q.handle();
222 for( auto e = hd.tryPop() ; e ; e = hd.tryPop() )
223 fr.add( std::move( e->item.node ) , e->item.bound , e->item.depth ,
225 detail::bb_finish< P >( sol , sh.inc , fr , sh.limit.load() ,
226 sh.time.load() );
227 sol.stats.seconds = clock.seconds();
228 return( sol );
229 }
230
231/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
232 // the data shared by the workers
233
234 struct Shared {
235
236 Shared( unsigned np , Value z , std::uint64_t seed )
237 : q( 2 * std::size_t( np ) , 1 , seed ) , inc( z ) {}
238
239 Mq q; // the open nodes
240 BBIncumbent< Node , Value > inc; // the incumbent
241 // the nodes in q or held by a worker
242 alignas( cache_line ) std::atomic< std::size_t > held{ 0 };
243 alignas( cache_line ) std::atomic< std::uint64_t > evaluated{ 0 };
244 alignas( cache_line ) std::atomic< bool > done{ false };
245 std::atomic< bool > limit{ false }; // stopped by a limit
246 std::atomic< bool > time{ false }; // ... the time limit
247 };
248
249/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
250 // a worker: its counters and its leaves
251
252 struct alignas( cache_line ) Worker {
253
254 Worker( const MultiQueueBranchAndBound & a , Shared & s ,
255 const Stopwatch & c )
256 : f_a( a ) , f_sh( s ) , f_clock( c ) ,
257 f_fr( a.f_options.keep_frontier ) {}
258
259 // the main loop
260 void run( unsigned i ) {
261 auto w = detail::bb_worker( f_a.f_p , i );
262 const auto & o = f_a.f_options;
263 const Selection sel = o.selection;
264 auto hd = f_sh.q.handle();
265 ADS::Backoff backoff;
266 std::optional< Item > dive; // the node of the dive
267 std::vector< Item > kids; // the children of a node
268 std::vector< typename Mq::Entry > out; // the children pushed
269 const auto inc = [ & ] { return( f_sh.inc.value() ); };
270 const auto improve = [ & ]( Value v , const Node & n ) {
271 return( f_sh.inc.template improve< P >( v , n , o ) );
272 };
273
274 while( ! f_sh.done.load( std::memory_order_acquire ) ) {
275 std::optional< Item > it;
276 if( dive ) {
277 it.emplace( std::move( *dive ) );
278 dive.reset();
279 }
280 else {
281 auto e = hd.tryPop();
282 if( ! e ) {
283 if( f_sh.held.load( std::memory_order_acquire ) == 0 ) {
284 f_sh.done.store( true , std::memory_order_release );
285 break;
286 }
287 backoff.pause();
288 continue;
289 }
290 backoff.reset();
291 if( detail::bb_prunable( e->item.bound , inc() , o ) ) {
292 f_fr.add( std::move( e->item.node ) , e->item.bound , e->item.depth ,
294 ++f_st.pruned_extracted;
295 f_sh.held.fetch_sub( 1 , std::memory_order_acq_rel );
296 continue;
297 }
298 it.emplace( Item{ std::move( e->item.node ) , e->item.bound ,
299 e->item.depth } );
300 }
301
302 if( stop() ) { // the node stays open
303 f_fr.add( std::move( it->node ) , it->bound , it->depth ,
305 return;
306 }
307
308 kids.clear();
309 detail::bb_process< P >( it->node , it->bound , it->depth , w , inc ,
310 improve , [ & ]( Node && c , Value b ,
311 unsigned d ) {
312 kids.push_back( Item{ std::move( c ) , b , d } );
313 } , o , f_st , f_fr );
314 if( o.node_limit )
315 f_sh.evaluated.fetch_add( 1 );
316
317 const std::size_t k = detail::bb_dive( kids , it->bound , inc() , o );
318 out.clear();
319 for( std::size_t j = 0 ; j < kids.size() ; ++j )
320 if( j == k )
321 dive.emplace( std::move( kids[ j ] ) );
322 else
323 out.push_back( typename Mq::Entry{
324 detail::bb_key( f_a.f_p , kids[ j ].node , kids[ j ].bound ,
325 kids[ j ].depth , sel ) ,
326 Held{ std::move( kids[ j ].node ) , kids[ j ].bound ,
327 kids[ j ].depth } } );
328 // the children counted and the node processed dropped in one step,
329 // before the pushes, so that held never misses a node [see line 16]
330 if( kids.size() != 1 ) {
331 const std::size_t m = f_sh.held.fetch_add( kids.size() - 1 ,
332 std::memory_order_acq_rel );
333 f_st.max_open = std::max( f_st.max_open , m + kids.size() - 1 );
334 }
335 hd.pushAll( std::make_move_iterator( out.begin() ) ,
336 std::make_move_iterator( out.end() ) );
337 }
338 if( dive ) // stopped by a limit of another
339 f_fr.add( std::move( dive->node ) , dive->bound , dive->depth ,
341 }
342
343 // true if a limit is reached, which ends the search
344 bool stop( void ) {
345 if( ! detail::bb_limit( f_a.f_options , f_sh.evaluated.load() , f_clock ,
346 f_sh.limit , f_sh.time ) )
347 return( false );
348 f_sh.done.store( true , std::memory_order_release );
349 return( true );
350 }
351
352 const MultiQueueBranchAndBound & f_a; // the search
353 Shared & f_sh; // the shared data
354 const Stopwatch & f_clock; // the clock of the search
355 BBStats f_st; // its counters
356 detail::BBFrontier< Node , Value > f_fr; // its leaves
357 };
358
359/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
360
361 const P & f_p;
362 Options f_options;
363 Executor * f_ex = nullptr;
364
365 }; // end( class( MultiQueueBranchAndBound ) )
366
367/*--------------------------------------------------------------------------*/
368
369} // end( namespace PBeFS_di_unipi_it )
370
371/*--------------------------------------------------------------------------*/
372
373#endif /* __PBeFS_MultiQueueBranchAndBound */
374
375/*--------------------------------------------------------------------------*/
376/*------------------ End File MultiQueueBranchAndBound.h -------------------*/
377/*--------------------------------------------------------------------------*/
Header file for the parallel best-first searches with an incumbent, i.e., the parallel Branch-and-Bou...
BasicParallelBBOptions< Value > Options
the options
Definition MultiQueueBranchAndBound.h:137
typename P::node_type Node
the node type
Definition MultiQueueBranchAndBound.h:135
BBLeaf< Node, Value > Leaf
the leaf type
Definition MultiQueueBranchAndBound.h:138
SolutionT solve(std::vector< Leaf > seeds) const
searches the subtrees of the given nodes, returning the Solution [see BranchAndBoundSearch::solve()]
Definition MultiQueueBranchAndBound.h:169
SolutionT solve(void) const
searches the tree from the root, returning the Solution
Definition MultiQueueBranchAndBound.h:161
BBSolution< Node, Value > SolutionT
the solution type
Definition MultiQueueBranchAndBound.h:139
MultiQueueBranchAndBound(const P &problem, const Options &options={})
constructor, taking the problem (which must outlive the search)
Definition MultiQueueBranchAndBound.h:145
void set_executor(Executor &ex) noexcept
sets the Executor that runs the workers, which must outlive the searches; without it,...
Definition MultiQueueBranchAndBound.h:156
bb_value_t< P > Value
the value type
Definition MultiQueueBranchAndBound.h:136
the values that the MultiQueue of ADS takes as keys: arithmetic, with a greatest value,...
Definition MultiQueueBranchAndBound.h:104
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
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
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
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
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
std::optional< V > incumbent
a known feasible value, if any
Definition BranchAndBound.h:389
Selection selection
the order of the nodes
Definition BranchAndBound.h:385
a node of the search graph
Definition Node.h:74
std::uint64_t seed
seed of the owners
Definition SearchUtils.h:99
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