PBeFS
Parallel Best-First Search
Loading...
Searching...
No Matches
HDAStarMPI.h
Go to the documentation of this file.
1/*--------------------------------------------------------------------------*/
2/*--------------------------- File HDAStarMPI.h ----------------------------*/
3/*--------------------------------------------------------------------------*/
49/*--------------------------------------------------------------------------*/
50/*------------------------------ DEFINITIONS -------------------------------*/
51/*--------------------------------------------------------------------------*/
52
53#ifndef __PBeFS_HDAStarMPI
54 #define __PBeFS_HDAStarMPI
55 /* self-identification: #endif at the end of the file */
56
57/*--------------------------------------------------------------------------*/
58/*-------------------------------- INCLUDES --------------------------------*/
59/*--------------------------------------------------------------------------*/
60
61#include <algorithm>
62
63#include <stdexcept>
64
65#include <type_traits>
66
67#include <variant>
68
69#include <vector>
70
71#include <mpi.h>
72
73#include "PBeFS/astar/HDAStar.h"
74
76
77#include "ADS/distributed/MPIMailbox.h"
78
79/*--------------------------------------------------------------------------*/
80/*------------------------------- NAMESPACE --------------------------------*/
81/*--------------------------------------------------------------------------*/
82
84namespace PBeFS_di_unipi_it
85{
86
87/*--------------------------------------------------------------------------*/
88/*---------------------------- CLASS HDAStarMPI ----------------------------*/
89/*--------------------------------------------------------------------------*/
91
110
111template< Domain D , class Dist = StateHashDistribution< D > >
113
114 static_assert( std::is_trivially_copyable_v< state_t< D > > ,
115 "HDAStarMPI: the states have to be trivially copyable" );
116
117/*------------------------ PUBLIC PART OF THE CLASS ------------------------*/
118
119 public:
120
121/*------------------------------ PUBLIC TYPES ------------------------------*/
122
126
127/*------------------------------ CONSTRUCTOR -------------------------------*/
128
130
131 HDAStarMPI( const D & domain , const Dist & dist ,
132 const ParallelOptions & options = {} ,
133 MPI_Comm comm = MPI_COMM_WORLD )
134 : f_domain( domain ) , f_dist( dist ) , f_options( options ) ,
135 f_comm( comm ) {
136 f_options.normalize();
137 }
138
139/*-------------------------------- SOLVING ---------------------------------*/
141
142 [[nodiscard]] SolutionT solve( void ) const
143 {
144 Stopwatch clock;
145 Process w( *this );
146 w.run();
147 SolutionT sol;
148 w.gather( sol );
149 sol.stats.seconds = clock.seconds();
150 return( sol );
151 }
152
153/*----------------------- PRIVATE PART OF THE CLASS ------------------------*/
154
155 private:
156
157 // a node: the parent is a state, owned by another process maybe
158 struct MNode {
159 State state;
160 Cost g;
161 Cost h;
162 State parent;
163 bool has_parent;
164 bool closed;
165 bool expanded;
166 [[no_unique_address]] OperatorOf< D > o;
167 std::uint32_t key; // the key of state
168 };
169
170 // a node travelling to its owner
171 struct Msg {
172 State s;
173 Cost g;
174 Cost h;
175 State parent;
176 bool has_parent;
177 [[no_unique_address]] OperatorOf< D > o;
178 std::uint32_t key; // the key of s
179 };
180
181 // a cost of a goal, sent to every process
182 struct Incumbent {
183 Cost c;
184 };
185
186 // the messages: the nodes and the incumbents
187 using Mail = std::variant< Msg , Incumbent >;
188
189 // the OPEN list: buckets for integer costs, the heap otherwise
190 using Open_t = std::conditional_t< std::is_integral_v< Cost > ,
191 BucketOpenList< MNode > ,
192 HeapOpenList< MNode > >;
193
194/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
195 // the search on one process
196
197 struct Process {
198
199 explicit Process( const HDAStarMPI & a )
200 : f_a( a ) , f_comm( a.f_comm ) ,
201 f_mail( a.f_comm , a.f_options.batch ) , f_dist( a.f_dist ) {
202 MPI_Comm_rank( f_comm , & f_rank );
203 int np;
204 MPI_Comm_size( f_comm , & np );
205 f_p = unsigned( np );
206 }
207
208 // the main loop
209 void run( void ) {
210 const State s0 = f_a.f_domain.initial_state();
211 if( f_dist.owner( s0 , f_p ) == unsigned( f_rank ) )
212 insert( Msg{ s0 , Cost( 0 ) , Cost( f_a.f_domain.heuristic( s0 ) ) , s0 ,
213 false , no_op , distribution_key< D >( f_dist , s0 ) } ,
214 f_a.f_domain.hash( s0 ) );
215
216 unsigned since_flush = 0;
217 for( ;; ) {
218 receive();
219 if( MNode * n = next() ) {
220 expand( n );
221 if( ++since_flush >= f_a.f_options.flush_period ) {
222 since_flush = 0;
223 f_mail.flush();
224 }
225 if( f_a.f_options.node_limit && ! f_mail.stopped() &&
226 ( f_pool.size() >= f_a.f_options.node_limit ) ) {
227 f_limit_hit = true;
228 f_mail.stop();
229 }
230 continue;
231 }
232 if( f_mail.quiescent() ) // nothing to expand nor to send
233 break;
234 }
235 }
236
237 // the best node of OPEN with f < incumbent, not removed; nullptr if none
238 MNode * next_peek( void ) {
239 if( f_mail.stopped() )
240 return( nullptr );
241 while( ! f_open.empty() ) {
242 const OpenEntry< MNode > e = f_open.top();
243 if( e.node->closed || ( e.node->g != e.g ) ) {
244 f_open.pop();
245 continue;
246 }
247 if( e.node->g + e.node->h >= f_incumbent )
248 return( nullptr );
249 return( e.node );
250 }
251 return( nullptr );
252 }
253
254 // the best node of OPEN with f < incumbent, removed; nullptr if none
255 MNode * next( void ) {
256 MNode * n = next_peek();
257 if( n )
258 f_open.pop();
259 return( n );
260 }
261
262 // expands n, or records it as the incumbent if it is a goal
263 void expand( MNode * n ) {
264 n->closed = true;
265 ++f_st.expanded;
266 if( n->expanded )
267 ++f_st.reexpanded;
268 n->expanded = true;
269
270 if( f_a.f_domain.is_goal( n->state ) ) {
271 if( n->g < f_incumbent ) {
272 f_incumbent = n->g;
273 f_goal = n;
274 for( unsigned q = 0 ; q < f_p ; ++q )
275 if( q != unsigned( f_rank ) )
276 f_mail.send( q , Mail( Incumbent{ n->g } ) );
277 f_mail.flush();
278 }
279 return;
280 }
281
282 const auto owner = successor_owners< D >( f_dist , n->state , n->key ,
283 f_p );
284 for_each_child( f_a.f_domain , n->state , n->h , n->o.op ,
285 [ & ]( const State & t , Cost c , Cost ht ,
286 std::uint8_t ot ) {
287 ++f_st.generated;
288 const Cost g = n->g + c;
289 if( g + ht >= f_incumbent )
290 return;
291 const auto [ dst , key ] = owner( t );
292 const Msg m{ t , g , ht , n->state , true , ot , key };
293 if( dst == unsigned( f_rank ) ) {
294 insert( m , f_a.f_domain.hash( t ) );
295 return;
296 }
297 f_mail.send( dst , Mail( m ) );
298 ++f_st.sent;
299 } );
300 }
301
302 // adds the node of m, whose state has hash h, to OPEN, unless it is a
303 // duplicate
304 void insert( const Msg & m , std::uint64_t h ) {
305 if( f_mail.stopped() || ( m.g + m.h >= f_incumbent ) )
306 return;
307 auto [ x , fresh ] = f_table.find_or_insert( m.s , h , [ & ] {
308 MNode * y = f_pool.allocate();
309 *y = MNode{ m.s , m.g , m.h , m.parent , m.has_parent , false , false ,
310 m.o , m.key };
311 return( y );
312 } );
313 if( fresh )
314 f_open.push( x );
315 else
316 if( m.g < x->g ) {
317 x->g = m.g;
318 x->parent = m.parent;
319 x->has_parent = m.has_parent;
320 x->o = m.o;
321 x->closed = false;
322 f_open.push( x );
323 }
324 else
325 ++f_st.duplicates;
326 }
327
328 // receives and processes all the messages that have arrived, the nodes
329 // of a batch hashed and prefetched first
330 void receive( void ) {
331 v_msg.clear();
332 f_mail.poll( [ & ]( const Mail & x ) {
333 if( const Msg * m = std::get_if< Msg >( & x ) )
334 v_msg.push_back( *m );
335 else
336 f_incumbent = std::min( f_incumbent , std::get< Incumbent >( x ).c );
337 } );
338 const std::size_t k = v_msg.size();
339 v_hash.resize( k );
340 for( std::size_t i = 0 ; i < k ; ++i ) {
341 v_hash[ i ] = f_a.f_domain.hash( v_msg[ i ].s );
342 f_table.prefetch( v_hash[ i ] );
343 }
344 for( std::size_t i = 0 ; i < k ; ++i )
345 insert( v_msg[ i ] , v_hash[ i ] );
346 }
347
348 // fills the Solution on every process: counters, status, path
349 void gather( SolutionT & sol ) {
350 SearchStats & st = sol.stats;
351 unsigned long long mine[ 7 ] = { f_st.expanded , f_st.reexpanded ,
352 f_st.generated , f_st.duplicates ,
353 f_st.sent , f_mail.batches() ,
354 f_pool.size() };
355 std::vector< unsigned long long > all( 7 * std::size_t( f_p ) );
356 MPI_Allgather( mine , 7 , MPI_UNSIGNED_LONG_LONG , all.data() , 7 ,
357 MPI_UNSIGNED_LONG_LONG , f_comm );
358 for( unsigned q = 0 ; q < f_p ; ++q ) {
359 const auto * x = all.data() + 7 * std::size_t( q );
360 st.expanded += x[ 0 ];
361 st.reexpanded += x[ 1 ];
362 st.generated += x[ 2 ];
363 st.duplicates += x[ 3 ];
364 st.sent += x[ 4 ];
365 st.messages += x[ 5 ];
366 st.stored += x[ 6 ];
367 st.expanded_by.push_back( x[ 0 ] );
368 st.stored_by.push_back( x[ 6 ] );
369 }
370
371 int limit = f_limit_hit ? 1 : 0 , any_limit = 0;
372 MPI_Allreduce( & limit , & any_limit , 1 , MPI_INT , MPI_MAX , f_comm );
373 if( any_limit ) {
374 sol.status = Status::node_limit;
375 return;
376 }
377
378 struct { double c; int r; } best{ f_goal ? double( f_goal->g )
379 : infinite_cost< double >() ,
380 f_rank } , win{};
381 MPI_Allreduce( & best , & win , 1 , MPI_DOUBLE_INT , MPI_MINLOC ,
382 f_comm );
383 if( win.c == infinite_cost< double >() )
384 return; // no goal: unsolvable
385
386 // the path, from the goal back to the root, one owner at a time
387 struct Step {
388 State s;
389 Cost g;
390 State parent;
391 bool has_parent;
392 } step{};
393 if( f_rank == win.r )
394 step = Step{ f_goal->state , f_goal->g , f_goal->parent ,
395 f_goal->has_parent };
396 MPI_Bcast( & step , int( sizeof( Step ) ) , MPI_BYTE , win.r , f_comm );
397 sol.status = Status::solved;
398 sol.cost = step.g;
399 std::vector< State > path{ step.s };
400 while( step.has_parent ) {
401 const State s = step.parent;
402 const int owner = int( f_dist.owner( s , f_p ) );
403 if( f_rank == owner ) {
404 const MNode * x = f_table.find( s , f_a.f_domain.hash( s ) );
405 if( ! x )
406 throw( std::logic_error( "HDAStarMPI::solve: lost node" ) );
407 step = Step{ x->state , x->g , x->parent , x->has_parent };
408 }
409 MPI_Bcast( & step , int( sizeof( Step ) ) , MPI_BYTE , owner , f_comm );
410 path.push_back( step.s );
411 }
412 sol.path.assign( path.rbegin() , path.rend() );
413 }
414
415 const HDAStarMPI & f_a; // the search
416 MPI_Comm f_comm; // the communicator
417 ADS::MPIMailbox< Mail > f_mail; // its messages
418 int f_rank; // this process
419 unsigned f_p; // the number of processes
420 Dist f_dist; // the distribution
421 NodePool< MNode > f_pool; // its nodes
422 NodeTable< MNode > f_table; // its CLOSED list
423 Open_t f_open; // its OPEN list
424 std::vector< Msg > v_msg; // the nodes received
425 std::vector< std::uint64_t > v_hash; // their hashes
426 Cost f_incumbent = infinite_cost< Cost >(); // the best cost known
427 MNode * f_goal = nullptr; // its best goal
428 bool f_limit_hit = false; // it hit the node limit
429 SearchStats f_st; // its counters
430 };
431
432/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
433
434 const D & f_domain;
435 const Dist & f_dist;
436 ParallelOptions f_options;
437 MPI_Comm f_comm;
438
439 }; // end( class( HDAStarMPI ) )
440
441/*--------------------------------------------------------------------------*/
442
443} // end( namespace PBeFS_di_unipi_it )
444
445/*--------------------------------------------------------------------------*/
446
447#endif /* __PBeFS_HDAStarMPI */
448
449/*--------------------------------------------------------------------------*/
450/*------------------------- End File HDAStarMPI.h --------------------------*/
451/*--------------------------------------------------------------------------*/
Header file for the class template HDAStar, Hash Distributed A* on a shared-memory machine,...
Header file for the small helpers shared by the searches: a stopwatch, the placement of the workers o...
Hash Distributed A* with one MPI process per worker.
Definition HDAStarMPI.h:112
state_t< D > State
the state type
Definition HDAStarMPI.h:123
Solution< State, Cost > SolutionT
the solution type
Definition HDAStarMPI.h:125
cost_t< D > Cost
the cost type
Definition HDAStarMPI.h:124
SolutionT solve(void) const
searches the Domain, returning the Solution (on every process)
Definition HDAStarMPI.h:142
HDAStarMPI(const D &domain, const Dist &dist, const ParallelOptions &options={}, MPI_Comm comm=MPI_COMM_WORLD)
constructor, taking the Domain, the distribution and the communicator
Definition HDAStarMPI.h:131
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
void for_each_child(const D &d, const state_t< D > &s, cost_t< D > h, std::uint8_t op, F &&f)
the successors of s but its parent, when D knows the operators
Definition Domain.h:276
typename D::cost_type cost_t
the cost type of a Domain
Definition Domain.h:245
std::uint32_t distribution_key(const X &x, const state_t< D > &s)
the key of s for x, 0 if X is not an IncrementalDistribution
Definition Distribution.h:399
@ solved
an optimal path has been found
Definition Solution.h:47
@ node_limit
the limit on the stored nodes was reached first
Definition Solution.h:49
constexpr Cost infinite_cost(void) noexcept
the largest value of a cost type, used as "no incumbent yet"
Definition Node.h:100
typename D::state_type state_t
the state type of a Domain
Definition Domain.h:241
constexpr std::uint8_t no_op
the operator of the root, which no state was generated by
Definition Domain.h:205
auto successor_owners(const X &x, const state_t< D > &s, std::uint32_t k, unsigned p)
the owner among p workers and the key of each successor of s
Definition Distribution.h:414
Definition Domain.h:260
the options of the parallel searches: those of the sequential search, and the parameters of the paral...
Definition HDAStar.h:114
void normalize(void) noexcept
sets to 1 any parameter that is 0
Definition SearchUtils.h:102
the counters of a search
Definition Solution.h:64
double seconds
wall-clock time of the search
Definition Solution.h:74
a path from the initial state to a goal, and its cost
Definition Solution.h:100
SearchStats stats
the counters of the search
Definition Solution.h:105