PBeFS
Parallel Best-First Search
Loading...
Searching...
No Matches
SelfSplitBranchAndBound.h
Go to the documentation of this file.
1/*--------------------------------------------------------------------------*/
2/*--------------------- File SelfSplitBranchAndBound.h ---------------------*/
3/*--------------------------------------------------------------------------*/
66/*--------------------------------------------------------------------------*/
67/*------------------------------ DEFINITIONS -------------------------------*/
68/*--------------------------------------------------------------------------*/
69
70#ifndef __PBeFS_SelfSplitBranchAndBound
71 #define __PBeFS_SelfSplitBranchAndBound
72 /* self-identification: #endif at the end of the file */
73
74/*--------------------------------------------------------------------------*/
75/*-------------------------------- INCLUDES --------------------------------*/
76/*--------------------------------------------------------------------------*/
77
78#include <exception>
79
80#include <mutex>
81
82#include <optional>
83
84#include <utility>
85
86#include <variant>
87
88#include <vector>
89
91
92/*--------------------------------------------------------------------------*/
93/*------------------------------- NAMESPACE --------------------------------*/
94/*--------------------------------------------------------------------------*/
95
97namespace PBeFS_di_unipi_it
98{
99
100/*--------------------------------------------------------------------------*/
101/*--------------------- CLASS SelfSplitBranchAndBound ----------------------*/
102/*--------------------------------------------------------------------------*/
104
128
129template< BranchAndBound P , ADS::WorkerExecutor Executor = ADS::ThreadPool >
131
132/*------------------------ PUBLIC PART OF THE CLASS ------------------------*/
133
134 public:
135
136/*------------------------------ PUBLIC TYPES ------------------------------*/
137
138 using Node = typename P::node_type;
143
144/*------------------------------ CONSTRUCTOR -------------------------------*/
145
147
148 explicit SelfSplitBranchAndBound( const P & problem ,
149 const Options & options = {} )
150 : f_p( problem ) , f_options( options ) {
151 f_options.normalize();
152 if( f_options.split == 0 )
153 f_options.split = 4;
154 }
155
156/*------------------------- OTHER INITIALIZATIONS --------------------------*/
160
161 void set_executor( Executor & ex ) noexcept { f_ex = &ex; }
162
163/*-------------------------------- SOLVING ---------------------------------*/
165
166 [[nodiscard]] SolutionT solve( void ) const {
167 return( run( std::nullopt ) );
168 }
169
170/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
173
174 [[nodiscard]] SolutionT solve( std::vector< Leaf > seeds ) const {
175 return( run( std::move( seeds ) ) );
176 }
177
178/*----------------------- PRIVATE PART OF THE CLASS ------------------------*/
179
180 private:
181
182 using Item = typename BBQueue< Node , Value >::Item;
183
184/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
185
186 SolutionT run( std::optional< std::vector< Leaf > > seeds ) const
187 {
188 using Tr = BBValueTraits< Value >;
189 const Value inf = Tr::infinity();
190 const Options & o = f_options;
191 const unsigned p = o.threads;
192 const bool sample = p > 1; // one worker has nothing to split
193 Stopwatch clock;
194
195 // the sampling, by the evaluator of the worker 0
196 SolutionT sol;
197 BBStats & st = sol.stats;
198 const Selection sel = o.selection == Selection::plunging ?
199 Selection::best_first : o.selection;
201 const auto push = [ & ]( Item && it ) {
202 const Value key = detail::bb_key( f_p , it.node , it.bound , it.depth ,
203 sel );
204 q.push( std::move( it.node ) , it.bound , it.depth , key );
205 st.max_open = std::max( st.max_open , q.size() );
206 };
207 for( auto & l : detail::bb_starts( f_p , std::move( seeds ) ) )
208 push( Item{ std::move( l.node ) , l.bound , l.depth } );
209
210 Value inc = o.incumbent ? detail::bb_signed< P >( *o.incumbent ) : inf;
211 std::optional< Node > inc_node;
212 detail::BBFrontier< Node , Value > fr( o.keep_frontier );
213 bool limit = false , late = false;
214 {
215 auto w = detail::bb_worker( f_p , 0u );
216 const auto improve = [ & ]( Value v , const Node & n ) {
217 if( v >= inc )
218 return( false );
219 inc = v;
220 inc_node = n;
221 if( o.on_incumbent )
222 o.on_incumbent( detail::bb_signed< P >( v ) );
223 return( true );
224 };
225 std::vector< Item > kids;
226 while( sample && ! q.empty() && ( q.size() < o.split * p ) ) {
227 if( o.node_limit && ( st.evaluated >= o.node_limit ) ) {
228 limit = true;
229 break;
230 }
231 if( ( o.time_limit > 0 ) && ( clock.seconds() >= o.time_limit ) ) {
232 limit = late = true;
233 break;
234 }
235 if( detail::bb_prunable( q.best_bound() , inc , o ) ) {
236 st.pruned_extracted += fr.add( q , BBLeafKind::pruned );
237 break; // nothing can improve
238 }
239 Item it = q.pop();
240 kids.clear();
241 detail::bb_process< P >( it.node , it.bound , it.depth , w ,
242 [ & ] { return( inc ); } , improve ,
243 [ & ]( Node && c , Value b , unsigned d ) {
244 kids.push_back( Item{ std::move( c ) , b ,
245 d } );
246 } , o , st , fr );
247 for( Item & k : kids )
248 push( std::move( k ) );
249 }
250
251 // the dive, from a copy of the first open node: its leaves are not
252 // leaves of the search, whose open nodes still cover them
253 if( sample && o.split_dive && ! limit && ! q.empty() &&
254 ! detail::bb_prunable( q.best_bound() , inc , o ) ) {
255 detail::BBFrontier< Node , Value > none( false );
256 const Item & top = q.top();
257 Item d{ Node( top.node ) , top.bound , top.depth };
258 for( ;; ) {
259 if( o.node_limit && ( st.evaluated >= o.node_limit ) )
260 break;
261 if( ( o.time_limit > 0 ) && ( clock.seconds() >= o.time_limit ) )
262 break;
263 kids.clear();
264 detail::bb_process< P >( d.node , d.bound , d.depth , w ,
265 [ & ] { return( inc ); } , improve ,
266 [ & ]( Node && c , Value b , unsigned dp ) {
267 kids.push_back( Item{ std::move( c ) , b ,
268 dp } );
269 } , o , st , none );
270 if( kids.empty() )
271 break;
272 std::size_t c = 0;
273 Value best = inf;
274 for( std::size_t j = 0 ; j < kids.size() ; ++j ) {
275 const Value e = detail::bb_key( f_p , kids[ j ].node , kids[ j ].bound ,
276 kids[ j ].depth ,
278 if( ( j == 0 ) || ( e < best ) ) {
279 c = j;
280 best = e;
281 }
282 }
283 d = std::move( kids[ c ] );
284 }
285 }
286 }
287
288 // the split: the k-th open node, in the order of the selection, to the
289 // worker k mod p, unless a limit or the incumbent ends the search here
290 std::vector< std::vector< Leaf > > part( p );
291 if( ! limit && ! q.empty() &&
292 detail::bb_prunable( q.best_bound() , inc , o ) )
293 st.pruned_extracted += fr.add( q , BBLeafKind::pruned );
294 std::uint64_t rest = 0; // the evaluations left
295 double time = 0; // the time left
296 if( o.node_limit && ! limit )
297 rest = o.node_limit - st.evaluated;
298 if( ( o.time_limit > 0 ) && ! limit ) {
299 time = o.time_limit - clock.seconds();
300 if( time <= 0 )
301 limit = late = true;
302 }
303 if( ! limit )
304 for( std::size_t k = 0 ; ! q.empty() ; ++k ) {
305 Item it = q.pop();
306 const unsigned i = unsigned( k % p );
307 if( o.node_limit && ( share( rest , p , i ) == 0 ) ) {
308 limit = true; // a node left open by the limit
309 fr.add( std::move( it.node ) , it.bound , it.depth , BBLeafKind::open );
310 }
311 else
312 part[ i ].push_back( Leaf{ std::move( it.node ) ,
313 detail::bb_signed< P >( it.bound ) ,
314 it.depth , BBLeafKind::open } );
315 }
316 fr.add( q , BBLeafKind::open ); // left open by a limit
317 st.evaluated_by.assign( p , 0 );
318 st.evaluated_by[ 0 ] = st.evaluated;
319
320 // the workers, each with its share of the limits and an incumbent that
321 // calls on_incumbent only when it improves on all of them, and that with
322 // split_shared prunes the nodes of all of them
323 std::vector< std::optional< SolutionT > > out( p );
324 std::mutex m;
325 BBIncumbent< std::monostate , Value > best( inc ); // the best value
326 std::exception_ptr error;
327 run_workers( f_ex , p , [ & ]( unsigned i ) {
328 if( part[ i ].empty() )
329 return;
330 try {
331 BasicBBOptions< Value > wo = o;
332 if( Tr::finite( inc ) )
333 wo.incumbent = detail::bb_signed< P >( inc );
334 wo.node_limit = o.node_limit ? share( rest , p , i ) : 0;
335 wo.time_limit = time;
336 wo.on_incumbent = [ & ]( Value v ) {
337 ( void ) best.template improve< P >( detail::bb_signed< P >( v ) ,
338 std::monostate() , o );
339 };
340 if( o.split_shared )
341 wo.shared_incumbent = [ & ] {
342 return( detail::bb_signed< P >( best.value() ) );
343 };
344 out[ i ] = BranchAndBoundSearch< P >( f_p , wo , i ).solve(
345 std::move( part[ i ] ) );
346 }
347 catch( ... ) {
348 std::lock_guard< std::mutex > guard( m );
349 if( ! error )
350 error = std::current_exception();
351 }
352 } );
353 if( error )
354 std::rethrow_exception( error );
355
356 // the merge, in the order of the workers
357 Value low = std::min( inc , fr.low );
358 fr.template take< P >( sol.frontier );
359 for( unsigned i = 0 ; i < p ; ++i ) {
360 if( ! out[ i ] )
361 continue;
362 SolutionT & s = *out[ i ];
363 if( s.node && ( detail::bb_signed< P >( s.value ) < inc ) ) {
364 inc = detail::bb_signed< P >( s.value );
365 inc_node = std::move( s.node );
366 }
367 low = std::min( low , detail::bb_signed< P >( s.bound ) );
368 if( s.status == BBStatus::time_limit )
369 limit = late = true;
370 else
371 if( s.status == BBStatus::node_limit )
372 limit = true;
373 st.add( s.stats ); // which appends its evaluations
374 st.evaluated_by.pop_back();
375 st.evaluated_by[ i ] += s.stats.evaluated;
376 sol.frontier.insert( sol.frontier.end() ,
377 std::make_move_iterator( s.frontier.begin() ) ,
378 std::make_move_iterator( s.frontier.end() ) );
379 }
380
381 if( limit )
382 sol.status = late ? BBStatus::time_limit : BBStatus::node_limit;
383 else
384 sol.status = Tr::finite( inc ) ? BBStatus::optimal
385 : BBStatus::infeasible;
386 sol.node = std::move( inc_node );
387 if( Tr::finite( inc ) )
388 sol.value = detail::bb_signed< P >( inc );
389 low = std::min( low , inc );
390 sol.bound = detail::bb_signed< P >( low );
391 sol.gap = Tr::finite( inc ) && Tr::finite( low ) ? Value( inc - low )
392 : inf;
393 st.seconds = clock.seconds();
394 return( sol );
395 }
396
397/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
398 // the evaluations of the worker i of p out of the rest of the node limit
399
400 static std::uint64_t share( std::uint64_t rest , unsigned p , unsigned i )
401 noexcept {
402 return( rest / p + ( i < rest % p ? 1 : 0 ) );
403 }
404
405/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
406
407 const P & f_p;
408 Options f_options;
409 Executor * f_ex = nullptr;
410
411 }; // end( class( SelfSplitBranchAndBound ) )
412
413/*--------------------------------------------------------------------------*/
414
415} // end( namespace PBeFS_di_unipi_it )
416
417/*--------------------------------------------------------------------------*/
418
419#endif /* __PBeFS_SelfSplitBranchAndBound */
420
421/*--------------------------------------------------------------------------*/
422/*------------------- End File SelfSplitBranchAndBound.h -------------------*/
423/*--------------------------------------------------------------------------*/
Header file for the parallel best-first searches with an incumbent, i.e., the parallel Branch-and-Bou...
the open nodes of a B&B, ordered by a key
Definition BranchAndBound.h:501
BasicParallelBBOptions< Value > Options
the options
Definition SelfSplitBranchAndBound.h:140
typename P::node_type Node
the node type
Definition SelfSplitBranchAndBound.h:138
BBLeaf< Node, Value > Leaf
the leaf type
Definition SelfSplitBranchAndBound.h:141
SelfSplitBranchAndBound(const P &problem, const Options &options={})
constructor, taking the problem (which must outlive the search)
Definition SelfSplitBranchAndBound.h:148
SolutionT solve(std::vector< Leaf > seeds) const
searches the subtrees of the given nodes, returning the Solution [see BranchAndBoundSearch::solve()]
Definition SelfSplitBranchAndBound.h:174
SolutionT solve(void) const
searches the tree from the root, returning the Solution
Definition SelfSplitBranchAndBound.h:166
BBSolution< Node, Value > SolutionT
the solution type
Definition SelfSplitBranchAndBound.h:142
void set_executor(Executor &ex) noexcept
sets the Executor that runs the workers, which must outlive the searches; without it,...
Definition SelfSplitBranchAndBound.h:161
bb_value_t< P > Value
the value type
Definition SelfSplitBranchAndBound.h:139
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
namespace for the Parallel Best-First Search library
Definition AStar.h:83
@ 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
@ best_first
the least bound first
Definition BranchAndBound.h:138
@ best_estimate
the least estimate( n ) of the problem first
Definition BranchAndBound.h:141
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
an open node
Definition BranchAndBound.h:508
the outcome of a B&B
Definition BranchAndBound.h:472
the counters of a B&B
Definition BranchAndBound.h:417
std::size_t max_open
the most open nodes of a queue
Definition BranchAndBound.h:429
std::uint64_t evaluated
nodes evaluated
Definition BranchAndBound.h:419
std::uint64_t pruned_extracted
nodes pruned when extracted
Definition BranchAndBound.h:424
what the B&B needs to know of the type V of its values
Definition BranchAndBound.h:205
std::size_t split
the open nodes per worker at which the sampling of the SelfSplit search stops, 0 for 4 [see SelfSplit...
Definition ParallelBranchAndBound.h:148
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