PBeFS
Parallel Best-First Search
Loading...
Searching...
No Matches
PASE.h
Go to the documentation of this file.
1/*--------------------------------------------------------------------------*/
2/*------------------------------ File PASE.h -------------------------------*/
3/*--------------------------------------------------------------------------*/
70/*--------------------------------------------------------------------------*/
71/*------------------------------ DEFINITIONS -------------------------------*/
72/*--------------------------------------------------------------------------*/
73
74#ifndef __PBeFS_PASE
75 #define __PBeFS_PASE
76 /* self-identification: #endif at the end of the file */
77
78/*--------------------------------------------------------------------------*/
79/*-------------------------------- INCLUDES --------------------------------*/
80/*--------------------------------------------------------------------------*/
81
82#include <algorithm>
83
84#include <cmath>
85
86#include <compare>
87
88#include <condition_variable>
89
90#include <cstdint>
91
92#include <memory>
93
94#include <mutex>
95
96#include <tuple>
97
98#include <type_traits>
99
100#include <utility>
101
102#include <vector>
103
104#include "PBeFS/astar/HDAStar.h"
105
106#include "ADS/tree/BTreeMap.h"
107
108/*--------------------------------------------------------------------------*/
109/*------------------------------- NAMESPACE --------------------------------*/
110/*--------------------------------------------------------------------------*/
111
113namespace PBeFS_di_unipi_it
114{
115
116/*--------------------------------------------------------------------------*/
117/*--------------------------- CLASS PASEOptions ----------------------------*/
118/*--------------------------------------------------------------------------*/
120
122
126 double eps = 1;
127
128 }; // end( struct( PASEOptions ) )
129
130/*--------------------------------------------------------------------------*/
131/*------------------------------- CLASS PASE -------------------------------*/
132/*--------------------------------------------------------------------------*/
134
152
153template< PairwiseDomain D , ADS::WorkerExecutor Executor = ADS::ThreadPool >
154class PASE {
155
156/*------------------------ PUBLIC PART OF THE CLASS ------------------------*/
157
158 public:
159
160/*------------------------------ PUBLIC TYPES ------------------------------*/
161
166
167/*------------------------------ CONSTRUCTOR -------------------------------*/
168
170
171 explicit PASE( const D & domain , const PASEOptions & options = {} )
172 : f_domain( domain ) , f_options( options ) {
173 f_options.normalize();
174 if( f_options.eps < 1 )
175 f_options.eps = 1;
176 }
177
178/*------------------------- OTHER INITIALIZATIONS --------------------------*/
182
183 void set_executor( Executor & ex ) noexcept { f_ex = &ex; }
184
185/*-------------------------------- SOLVING ---------------------------------*/
187
188 [[nodiscard]] SolutionT solve( void ) const
189 {
190 const unsigned p = f_options.threads;
191 Stopwatch clock;
192 Shared sh;
193 std::vector< std::unique_ptr< Worker > > w( p );
194 for( unsigned i = 0 ; i < p ; ++i )
195 w[ i ] = std::make_unique< Worker >( *this , sh );
196
197 {
198 const State s0 = f_domain.initial_state();
199 NodeT * root = w[ 0 ]->f_pool.allocate();
200 *root = NodeT{ s0 , Cost( 0 ) , Cost( f_domain.heuristic( s0 ) ) ,
201 nullptr , false , false };
202 sh.table.find_or_insert( s0 , f_domain.hash( s0 ) ,
203 [ & ] { return( root ); } );
204 sh.open.insert( Key( root ) , true );
205 }
206
207 run_workers( f_ex , p , [ & ]( unsigned i ) { w[ i ]->run(); } );
208
209 SolutionT sol;
210 SearchStats & st = sol.stats;
211 for( auto & x : w ) {
212 st.expanded += x->f_st.expanded;
213 st.generated += x->f_st.generated;
214 st.duplicates += x->f_st.duplicates;
215 st.stored += x->f_pool.size();
216 st.expanded_by.push_back( x->f_st.expanded );
217 st.stored_by.push_back( x->f_pool.size() );
218 }
219 if( sh.limit_hit )
220 sol.status = Status::node_limit;
221 else
222 if( sh.goal ) {
223 sol.status = Status::solved;
224 sol.cost = sh.goal->g;
225 sol.path = trace_path( sh.goal );
226 }
227 st.seconds = clock.seconds();
228 return( sol );
229 }
230
231/*----------------------- PRIVATE PART OF THE CLASS ------------------------*/
232
233 private:
234
235 using Lock = typename Executor::lock_type;
236
237 // OPEN: by f, then by larger g, then by address; with integer costs f
238 // and g are one word [see packed_fg], compared at once
239 struct Key {
240 std::conditional_t< std::is_integral_v< Cost > , std::uint64_t ,
241 std::pair< Cost , Cost > > fg{};
242 std::uintptr_t node = 0;
243
244 Key( void ) = default;
245
246 explicit Key( const NodeT * x )
247 : node( reinterpret_cast< std::uintptr_t >( x ) ) {
248 if constexpr( std::is_integral_v< Cost > )
249 fg = packed_fg( x->g , x->h );
250 else
251 fg = { x->f() , - x->g };
252 }
253
254 NodeT * get( void ) const { return( reinterpret_cast< NodeT * >( node ) ); }
255
256 friend auto operator<=>( const Key & , const Key & ) = default;
257 };
258
259/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
260 // the data shared by the workers, all protected by lock
261
262 struct Shared {
263 Lock lock; // protects everything
264 std::condition_variable_any changed; // OPEN or BE changed
265 ADS::BTreeMap< Key , bool > open; // OPEN
266 std::vector< NodeT * > be; // being expanded
267 NodeTable< NodeT > table; // all the states
268 bool done = false; // the search is over
269 bool limit_hit = false; // the node limit is hit
270 NodeT * goal = nullptr; // the goal, once safe
271 };
272
273/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
274 // a worker: its pool of nodes and its counters
275
276 struct alignas( cache_line ) Worker {
277
278 Worker( const PASE & a , Shared & s ) : f_a( a ) , f_sh( s ) {}
279
280 // the states of OPEN that a worker examines for a safe one
281 static constexpr std::size_t c_candidates = 64;
282
283 // true if s is independent of t: g( s ) - g( t ) <= eps h( t , s ), up
284 // to a relative 1e-9 for real costs, whose rounding would otherwise make
285 // the goal dependent on the states with f = f( goal )
286 bool independent( const NodeT * s , const NodeT * t ) const {
287 const double lhs = double( s->g - t->g );
288 const double rhs = f_a.f_options.eps *
289 double( f_a.f_domain.pairwise_heuristic( t->state , s->state ) );
290 if constexpr( std::is_floating_point_v< Cost > )
291 return( lhs <= rhs + 1e-9 * std::max( 1.0 ,
292 std::abs( double( s->g ) ) ) );
293 else
294 return( lhs <= rhs );
295 }
296
297 // true if s is safe to expand (lock held): independent of BE and of the
298 // states of OPEN with a smaller f, which come first in OPEN, as in the
299 // definition of safety of the paper; the states of equal f are not
300 // checked, so that a layer of equal f costs a scan of BE per state
301 // instead of a scan of the layer
302 bool safe( const NodeT * s ) const {
303 for( const NodeT * t : f_sh.be )
304 if( ! independent( s , t ) )
305 return( false );
306 const Cost fs = s->f();
307 for( const auto & k : f_sh.open ) {
308 const NodeT * t = k.first.get();
309 if( ! ( t->f() < fs ) )
310 break;
311 if( ! independent( s , t ) )
312 return( false );
313 }
314 return( true );
315 }
316
317 // Algorithm 1 of the paper
318 void run( void ) {
319 std::vector< std::tuple< State , Cost , Cost , std::uint64_t ,
320 std::uint8_t > > succ;
321 std::unique_lock< Lock > guard( f_sh.lock );
322 while( ! f_sh.done ) {
323 // the safe state of smallest f among the first c_candidates of OPEN,
324 // if any: further down, each candidate would have to be checked
325 // against the larger and larger part of OPEN before it, under the lock;
326 // with BE empty the first state is safe, and the search goes on
327 NodeT * s = nullptr;
328 std::size_t n = 0;
329 for( const auto & k : f_sh.open ) {
330 if( n++ == c_candidates )
331 break;
332 if( safe( k.first.get() ) ) {
333 s = k.first.get();
334 break;
335 }
336 }
337 if( ! s ) {
338 if( f_sh.open.empty() && f_sh.be.empty() ) {
339 f_sh.done = true;
340 f_sh.changed.notify_all();
341 break;
342 }
343 f_sh.changed.wait( guard );
344 continue;
345 }
346 f_sh.open.remove( Key( s ) );
347 if( f_a.f_domain.is_goal( s->state ) ) {
348 f_sh.goal = s; // safe, hence of optimal g
349 f_sh.done = true;
350 f_sh.changed.notify_all();
351 break;
352 }
353 f_sh.be.push_back( s );
354 s->closed = s->expanded = true;
355 const State st = s->state;
356 const Cost g = s->g , h = s->h;
357 const std::uint8_t op = s->op;
358 guard.unlock();
359
360 ++f_st.expanded;
361 succ.clear();
362 for_each_child( f_a.f_domain , st , h , op ,
363 [ & ]( const State & t , Cost c , Cost ht ,
364 std::uint8_t ot ) {
365 succ.emplace_back( t , g + c , ht , f_a.f_domain.hash( t ) , ot );
366 } );
367
368 // the hashes computed without the lock, and the places of all the
369 // successors prefetched under it before any lookup, so that their
370 // misses overlap and the lock is held for about one of them
371 guard.lock();
372 for( const auto & sc : succ )
373 f_sh.table.prefetch( std::get< 3 >( sc ) );
374 for( const auto & sc : succ ) { // no structured binding: OpenMP
375 const State & t = std::get< 0 >( sc ); // cannot capture one
376 const Cost gt = std::get< 1 >( sc ) , ht = std::get< 2 >( sc );
377 ++f_st.generated;
378 auto [ x , fresh ] = f_sh.table.find_or_insert( t ,
379 std::get< 3 >( sc ) ,
380 [ & ] {
381 NodeT * y = f_pool.allocate();
382 *y = NodeT{ t , gt , ht , s , false , false , std::get< 4 >( sc ) };
383 return( y );
384 } );
385 if( fresh )
386 f_sh.open.insert( Key( x ) , true );
387 else
388 if( ! x->closed && ( gt < x->g ) ) {
389 f_sh.open.remove( Key( x ) );
390 x->g = gt;
391 x->parent = s;
392 x->op = std::get< 4 >( sc );
393 f_sh.open.insert( Key( x ) , true );
394 }
395 else
396 ++f_st.duplicates;
397 }
398 f_sh.be.erase( std::find( f_sh.be.begin() , f_sh.be.end() , s ) );
399 if( f_a.f_options.node_limit &&
400 ( f_pool.size() >= f_a.f_options.node_limit ) ) {
401 f_sh.limit_hit = true;
402 f_sh.done = true;
403 }
404 f_sh.changed.notify_all();
405 }
406 }
407
408 const PASE & f_a; // the search
409 Shared & f_sh; // the shared data
410 NodePool< NodeT > f_pool; // the nodes it allocated
411 SearchStats f_st; // its counters
412 };
413
414/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
415
416 const D & f_domain;
417 PASEOptions f_options;
418 Executor * f_ex = nullptr;
419
420 }; // end( class( PASE ) )
421
422/*--------------------------------------------------------------------------*/
423
424} // end( namespace PBeFS_di_unipi_it )
425
426/*--------------------------------------------------------------------------*/
427
428#endif /* __PBeFS_PASE */
429
430/*--------------------------------------------------------------------------*/
431/*---------------------------- End File PASE.h -----------------------------*/
432/*--------------------------------------------------------------------------*/
Header file for the class template HDAStar, Hash Distributed A* on a shared-memory machine,...
state_t< D > State
the state type
Definition PASE.h:162
Solution< State, Cost > SolutionT
the solution type
Definition PASE.h:165
Node< State, Cost > NodeT
the node type
Definition PASE.h:164
cost_t< D > Cost
the cost type
Definition PASE.h:163
PASE(const D &domain, const PASEOptions &options={})
constructor, taking the Domain (which must outlive the PASE)
Definition PASE.h:171
SolutionT solve(void) const
searches the Domain, returning the Solution
Definition PASE.h:188
void set_executor(Executor &ex) noexcept
sets the Executor that runs the workers, which must outlive the searches; without it,...
Definition PASE.h:183
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
@ 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 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
auto trace_path(const NodeT *goal)
the states from the root to goal, following the parent pointers
Definition Node.h:126
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
the options of PASE
Definition PASE.h:121
double eps
the inflation of the independence rule: with the search is optimal, otherwise the cost is within a ...
Definition PASE.h:126
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 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