PBeFS
Parallel Best-First Search
Loading...
Searching...
No Matches
MQAStar.h
Go to the documentation of this file.
1/*--------------------------------------------------------------------------*/
2/*----------------------------- File MQAStar.h -----------------------------*/
3/*--------------------------------------------------------------------------*/
54/*--------------------------------------------------------------------------*/
55/*------------------------------ DEFINITIONS -------------------------------*/
56/*--------------------------------------------------------------------------*/
57
58#ifndef __PBeFS_MQAStar
59 #define __PBeFS_MQAStar
60 /* self-identification: #endif at the end of the file */
61
62/*--------------------------------------------------------------------------*/
63/*-------------------------------- INCLUDES --------------------------------*/
64/*--------------------------------------------------------------------------*/
65
66#include <atomic>
67
68#include <cstdint>
69
70#include <memory>
71
72#include <mutex>
73
74#include <optional>
75
76#include <ranges>
77
78#include <type_traits>
79
80#include <vector>
81
83
84#include "PBeFS/astar/HDAStar.h"
85
86#include "ADS/concurrent/MultiQueue.h"
87
88/*--------------------------------------------------------------------------*/
89/*------------------------------- NAMESPACE --------------------------------*/
90/*--------------------------------------------------------------------------*/
91
93namespace PBeFS_di_unipi_it
94{
95
96/*--------------------------------------------------------------------------*/
97/*----------------------------- CLASS MQAStar ------------------------------*/
98/*--------------------------------------------------------------------------*/
101
124
125template< Domain D , ADS::WorkerExecutor Executor = ADS::ThreadPool >
126class MQAStar {
127
128/*------------------------ PUBLIC PART OF THE CLASS ------------------------*/
129
130 public:
131
132/*------------------------------ PUBLIC TYPES ------------------------------*/
133
138
139/*------------------------------ CONSTRUCTOR -------------------------------*/
140
142
143 explicit MQAStar( const D & domain , const ParallelOptions & options = {} )
144 : f_domain( domain ) , f_options( options ) {
145 f_options.normalize();
146 }
147
148/*------------------------- OTHER INITIALIZATIONS --------------------------*/
152
153 void set_executor( Executor & ex ) noexcept { f_ex = &ex; }
154
155/*-------------------------------- SOLVING ---------------------------------*/
157
158 [[nodiscard]] SolutionT solve( void ) const
159 {
160 const unsigned p = f_options.threads;
161 Stopwatch clock;
162 Shared sh( f_domain , p , f_options.seed );
163 std::vector< std::unique_ptr< Worker > > w( p );
164 for( unsigned i = 0 ; i < p ; ++i )
165 w[ i ] = std::make_unique< Worker >( *this , sh );
166
167 { // the root, inserted by the first worker before the start
168 const State s0 = f_domain.initial_state();
169 NodeT * root = w[ 0 ]->update( s0 , Cost( 0 ) ,
170 Cost( f_domain.heuristic( s0 ) ) ,
171 nullptr , no_op );
172 auto hd = sh.open.handle();
173 hd.push( key( root->g , root->h ) , OpenEntry< NodeT >{ root , root->g } );
174 }
175
176 run_workers( f_ex , p , [ & ]( unsigned i ) { w[ i ]->run(); } );
177
178 SolutionT sol;
179 SearchStats & st = sol.stats;
180 for( auto & x : w ) {
181 st.expanded += x->f_st.expanded;
182 st.reexpanded += x->f_st.reexpanded;
183 st.generated += x->f_st.generated;
184 st.duplicates += x->f_st.duplicates;
185 st.stored += x->f_local.stored();
186 st.expanded_by.push_back( x->f_st.expanded );
187 st.stored_by.push_back( x->f_local.stored() );
188 }
189 if( sh.limit_hit.load() )
190 sol.status = Status::node_limit;
191 else
192 if( sh.goal ) {
193 sol.status = Status::solved;
194 sol.cost = sh.goal->g;
195 sol.path = trace_path( sh.goal );
196 }
197 st.seconds = clock.seconds();
198 return( sol );
199 }
200
201/*----------------------- PRIVATE PART OF THE CLASS ------------------------*/
202
203 private:
204
205 using Lock = typename Executor::lock_type;
206
207 using Closed = ConcurrentClosed< D , Lock >;
208
212 static constexpr bool kPacked = std::is_integral_v< Cost >;
213 using Key = std::conditional_t< kPacked , std::uint64_t , Cost >;
214
215 static Key key( Cost g , Cost h ) {
216 if constexpr( kPacked )
217 return( packed_fg( g , h ) );
218 else
219 return( g + h );
220 }
221
222 static Cost f_of( Key k ) {
223 if constexpr( kPacked )
224 return( Cost( k >> 32 ) );
225 else
226 return( k );
227 }
228
230 using Mq = ADS::MultiQueue< Key , OpenEntry< NodeT > >;
231
232/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
233 // the data shared by the workers
234
235 struct Shared {
236 Shared( const D & d , unsigned p , std::uint64_t seed )
237 : open( 2 * std::size_t( p ) , 1 , seed ) , closed( d ) {}
238
239 Mq open; // the OPEN list
240 // the entries in OPEN or being expanded
241 alignas( cache_line ) std::atomic< std::size_t > pending{ 1 };
242 Closed closed; // the CLOSED list
243 alignas( cache_line ) std::atomic< Cost > incumbent{
245 std::atomic< bool > done{ false };
246 std::atomic< bool > limit_hit{ false };
247 std::mutex goal_mutex; // protects goal
248 NodeT * goal = nullptr; // the best goal found
249 };
250
251/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
252 // a worker: its pool of nodes and its counters
253
254 struct alignas( cache_line ) Worker {
255
256 Worker( const MQAStar & a , Shared & s ) : f_a( a ) , f_sh( s ) {}
257
258 // the main loop
259 void run( void ) {
260 ADS::Backoff backoff;
261 auto hd = f_sh.open.handle();
262 std::vector< OpenEntry< NodeT > > fresh;
263 while( ! f_sh.done.load( std::memory_order_acquire ) ) {
264 const auto e = hd.tryPop();
265 if( ! e ) {
266 if( f_sh.pending.load( std::memory_order_acquire ) == 0 ) {
267 f_sh.done.store( true , std::memory_order_release );
268 break;
269 }
270 backoff.pause();
271 continue;
272 }
273 backoff.reset();
274 fresh.clear();
275 if( f_of( e->key ) < f_sh.incumbent.load() )
276 expand( e->item , fresh );
277 // the entries of F counted and the one popped dropped in one step,
278 // before the pushes, so that m never misses an entry [see line 13]
279 if( fresh.size() != 1 )
280 f_sh.pending.fetch_add( fresh.size() - 1 , std::memory_order_acq_rel );
281 auto entries = fresh | std::views::transform(
282 []( const OpenEntry< NodeT > & x ) {
283 return( typename Mq::Entry{
284 key( x.g , x.node->h ) , x } ); } );
285 hd.pushAll( entries.begin() , entries.end() );
286 }
287 }
288
289 // expands the node of e unless the entry is stale; the nodes to put in
290 // OPEN, with the g they had under their lock, are appended to fresh
291 void expand( const OpenEntry< NodeT > & e ,
292 std::vector< OpenEntry< NodeT > > & fresh ) {
293 NodeT * n = e.node;
294 State s;
295 Cost g , h;
296 std::uint8_t op;
297 {
298 std::lock_guard< Lock > guard( f_sh.closed.lock( n ) );
299 if( e.stale() )
300 return;
301 n->closed = true;
302 if( n->expanded )
303 ++f_st.reexpanded;
304 n->expanded = true;
305 s = n->state;
306 g = n->g;
307 h = n->h;
308 op = n->op;
309 }
310 ++f_st.expanded;
311
312 if( f_a.f_domain.is_goal( s ) ) {
313 std::lock_guard< std::mutex > guard( f_sh.goal_mutex );
314 if( g < f_sh.incumbent.load() ) {
315 f_sh.incumbent.store( g );
316 f_sh.goal = n;
317 }
318 return;
319 }
320
321 // the successors first, but the parent [see OperatorDomain], with the
322 // places of CLOSED that their updates read prefetched, so that the
323 // misses of all of them overlap
324 f_succ.clear();
325 for_each_child( f_a.f_domain , s , h , op , [ & ]( const State & t ,
326 Cost c , Cost ht ,
327 std::uint8_t ot ) {
328 ++f_st.generated;
329 const Cost gt = g + c;
330 if( gt + ht >= f_sh.incumbent.load( std::memory_order_relaxed ) )
331 return;
332 const std::uint64_t hash = f_a.f_domain.hash( t );
333 f_sh.closed.prefetch( hash );
334 f_succ.push_back( { t , gt , ht , hash , ot } );
335 } );
336 for( const auto & v : f_succ )
337 if( NodeT * x = f_sh.closed.update( v.state , v.hash , v.g , v.h , n ,
338 v.op , f_local , f_st.duplicates ) )
339 fresh.push_back( OpenEntry< NodeT >{ x , v.g } );
340
341 if( f_a.f_options.node_limit &&
342 ( f_local.stored() >= f_a.f_options.node_limit ) ) {
343 f_sh.limit_hit.store( true );
344 f_sh.done.store( true , std::memory_order_release );
345 }
346 }
347
348 // records the path to t of cost g through parent and op; returns the
349 // node of t if it has to go into OPEN, nullptr if it is a duplicate
350 NodeT * update( const State & t , Cost g , Cost h , NodeT * parent ,
351 std::uint8_t op ) {
352 return( f_sh.closed.update( t , f_a.f_domain.hash( t ) , g , h ,
353 parent , op , f_local , f_st.duplicates ) );
354 }
355
356 const MQAStar & f_a; // the search
357 Shared & f_sh; // the shared data
358 typename Closed::Local f_local; // the nodes it allocated
359 std::vector< typename Closed::Successor > f_succ; // of the expansion
360 SearchStats f_st; // its counters
361 };
362
363/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
364
365 const D & f_domain;
366 ParallelOptions f_options;
367 Executor * f_ex = nullptr;
368
369 }; // end( class( MQAStar ) )
370
371/*--------------------------------------------------------------------------*/
372
373} // end( namespace PBeFS_di_unipi_it )
374
375/*--------------------------------------------------------------------------*/
376
377#endif /* __PBeFS_MQAStar */
378
379/*--------------------------------------------------------------------------*/
380/*--------------------------- End File MQAStar.h ---------------------------*/
381/*--------------------------------------------------------------------------*/
Header file for the class template ConcurrentClosed, the closed list that the workers of SPAStar and ...
Header file for the class template HDAStar, Hash Distributed A* on a shared-memory machine,...
the closed list shared by the workers, a lock-free set of the nodes
Definition ConcurrentClosed.h:76
state_t< D > State
the state type
Definition MQAStar.h:134
Solution< State, Cost > SolutionT
the solution type
Definition MQAStar.h:137
Node< State, Cost > NodeT
the node type
Definition MQAStar.h:136
cost_t< D > Cost
the cost type
Definition MQAStar.h:135
MQAStar(const D &domain, const ParallelOptions &options={})
constructor, taking the Domain (which must outlive the MQAStar)
Definition MQAStar.h:143
SolutionT solve(void) const
searches the Domain, returning the Solution
Definition MQAStar.h:158
void set_executor(Executor &ex) noexcept
sets the Executor that runs the workers, which must outlive the searches; without it,...
Definition MQAStar.h:153
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
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
@ 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
constexpr std::uint64_t packed_fg(Cost g, Cost h) noexcept
with integer costs, f = g + h in the high half of a word and the complement of g in the low one,...
Definition Node.h:116
typename D::state_type state_t
the state type of a Domain
Definition Domain.h:241
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
constexpr std::uint8_t no_op
the operator of the root, which no state was generated by
Definition Domain.h:205
auto trace_path(const NodeT *goal)
the states from the root to goal, following the parent pointers
Definition Node.h:126
the nodes that a worker allocates, and its spare node
Definition ConcurrentClosed.h:88
a node of the search graph
Definition Node.h:74
Cost g
the cost of the best path found from the root
Definition Node.h:77
Cost h
the heuristic estimate of the cost to a goal
Definition Node.h:78
an entry of the OPEN list: the node and the g it had when pushed
Definition OpenList.h:73
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
std::vector< std::uint64_t > expanded_by
expansions of each worker
Definition Solution.h:75
std::uint64_t generated
successors generated
Definition Solution.h:68
std::uint64_t reexpanded
expansions of a node already expanded
Definition Solution.h:67
std::uint64_t stored
distinct nodes stored at the end
Definition Solution.h:73
double seconds
wall-clock time of the search
Definition Solution.h:74
std::uint64_t duplicates
generated states already known with a not larger
Definition Solution.h:69
std::vector< std::uint64_t > stored_by
nodes stored by each worker
Definition Solution.h:76
std::uint64_t expanded
expansions, re-expansions included
Definition Solution.h:66
a path from the initial state to a goal, and its cost
Definition Solution.h:100