File Coverage

blib/lib/AI/Pathfinding/SMAstar/Path.pm
Criterion Covered Total %
statement 157 226 69.4
branch 43 84 51.1
condition 14 39 35.9
subroutine 21 28 75.0
pod 0 20 0.0
total 235 397 59.1


line stmt bran cond sub pod time code
1             #
2             # Representation of a path, used in the SMAstar pathfinding algorithm.
3             #
4             # Author: matthias beebe
5             # Date : June 2008
6             #
7             #
8              
9             package AI::Pathfinding::SMAstar::Path;
10              
11 1     1   7 use strict;
  1         5  
  1         45  
12              
13             BEGIN {
14 1     1   6 use Exporter ();
  1         3  
  1         57  
15 1     1   18 @Path::ISA = qw(Exporter);
16 1         3 @Path::EXPORT = qw();
17 1         22 @Path::EXPORT_OK = qw($d);
18              
19             }
20              
21 1     1   6 use vars qw($d $max_forgotten_nodes); # used to debug destroy method for accounting purposes
  1         2  
  1         2853  
22             $d = 0;
23             $max_forgotten_nodes = 0;
24              
25              
26             ##################################################
27             # Path constructor
28             ##################################################
29             sub new {
30 7     7 0 11 my $invocant = shift;
31 7   33     23 my $class = ref($invocant) || $invocant;
32 7         130 my $self = {
33            
34             _state => undef, # node in the search space
35             _eval_func => undef,
36             _goal_p_func => undef,
37             _num_successors_func => undef,
38             _successors_iterator => undef,
39             _get_data_func => undef,
40              
41             ###########################################
42             #
43             # path stuff
44             #
45             ###########################################
46             _antecedent => undef, # pointer to the antecedent of this obj
47             _f_cost => undef, # g + h where g = cost so far, h = estimated cost to goal.
48              
49             _forgotten_node_fcosts => [], # array to store fcosts of forgotten nodes
50             _forgotten_nodes_num => 0,
51              
52             _forgotten_nodes_offsets => {},
53              
54             _depth => 0, # depth used for memory-bounded search
55             _descendants_produced => [],
56             _descendant_index => undef,
57             _descendant_fcosts => [],
58             _descendants_on_queue => 0,
59              
60             _descendands_deleted => 0,
61             _is_completed => 0,
62             _num_successors => undef,
63             _num_successors_in_mem => 0,
64             _is_on_queue => 0,
65             _iterator_index => 0, # to remember index of iterator for descendants
66             _need_fcost_change => 0, # boolean
67              
68             @_, # attribute override
69             };
70              
71 7         26 return bless $self, $class;
72            
73             }
74              
75             ##############################################
76             # accessors
77             ##############################################
78              
79             sub state{
80 0     0 0 0 my $self = shift;
81 0 0       0 if (@_) { $self->{_state} = shift }
  0         0  
82 0         0 return $self->{_state};
83             }
84              
85             sub antecedent{
86 0     0 0 0 my $self = shift;
87 0 0       0 if (@_) { $self->{_antecedent} = shift }
  0         0  
88 0         0 return $self->{_antecedent};
89             }
90              
91             sub f_cost{
92 2     2 0 4 my $self = shift;
93 2 50       5 if (@_) { $self->{_f_cost} = shift }
  2         3  
94 2         4 return $self->{_f_cost};
95             }
96              
97             sub depth{
98 31     31 0 36 my $self = shift;
99 31 50       54 if (@_) { $self->{_depth} = shift }
  0         0  
100 31         127 return $self->{_depth};
101             }
102              
103             sub is_completed{
104 33     33 0 35 my $self = shift;
105 33 100       59 if (@_) { $self->{_is_completed} = shift }
  5         5  
106 33         75 return $self->{_is_completed};
107             }
108              
109             sub is_on_queue{
110 29     29 0 33 my $self = shift;
111 29 100       52 if (@_) { $self->{_is_on_queue} = shift }
  25         32  
112 29         83 return $self->{_is_on_queue};
113             }
114              
115             sub descendants_deleted{
116 0     0 0 0 my $self = shift;
117 0 0       0 if (@_) { $self->{_descendants_deleted} = shift }
  0         0  
118 0         0 return $self->{_descendants_deleted};
119             }
120              
121             sub need_fval_change{
122 36     36 0 42 my $self = shift;
123 36 100       64 if (@_) { $self->{_need_fcost_change} = shift }
  9         10  
124 36         140 return $self->{_need_fcost_change};
125             }
126              
127              
128              
129              
130             # new version 8
131             sub remember_forgotten_nodes_fcost
132             {
133 0     0 0 0 my ($self, $node) = @_;
134              
135 0         0 my $fcost = $node->{_f_cost};
136 0         0 my $index = $node->{_descendant_index};
137              
138 0         0 $self->{_forgotten_node_fcosts}->[$index] = $fcost;
139            
140 0         0 return;
141             }
142              
143              
144              
145              
146              
147              
148             #----------------------------------------------------------------------------
149             # evaluation function f(n) = g(n) + h(n) where
150             #
151             # g(n) = cost of path through this node
152             # h(n) = distance from this node to goal (optimistic)
153             #
154             # used for A* search.
155             #
156             sub fcost
157             {
158 38     38 0 173 my ($self) = @_;
159            
160 38         49 my $fcost = $self->{_f_cost};
161 38 100       73 if(defined($fcost)){
162 31         79 return $fcost;
163             }
164              
165 7         7 my $eval_func = $self->{_eval_func};
166 7         20 my $result = $eval_func->($self->{_state});
167 7         12 $self->{_f_cost} = $result;
168              
169 7         15 return $result;
170             }
171              
172              
173              
174              
175              
176             sub is_goal
177             {
178 6     6 0 8 my ($self) = @_;
179            
180 6         11 my $goal_p_func = $self->{_goal_p_func};
181 6         15 my $result = $goal_p_func->($self->{_state});
182              
183 6         28 return $result;
184             }
185              
186              
187              
188             sub get_num_successors
189             {
190 7     7 0 9 my ($self) = @_;
191            
192 7         11 my $num_successors_func = $self->{_num_successors_func};
193 7         19 my $result = $num_successors_func->($self->{_state});
194              
195 7         13 return $result;
196             }
197              
198              
199             sub get_successors_iterator
200             {
201 10     10 0 21 my ($self) = @_;
202            
203 10         14 my $successors_iterator = $self->{_successors_iterator};
204              
205 10         23 my $iterator = $successors_iterator->($self->{_state});
206            
207 10         19 return $iterator;
208             }
209              
210              
211            
212            
213              
214             #-----------------------------------------------------------------------------------------------
215             #
216             # Check whether we need to backup the fvals for a node when it is completed (recursive)
217             # Sets flags throughout path object's lineage, indicating whether fvals need to be updated.
218             #
219             #-----------------------------------------------------------------------------------------------
220             sub check_need_fval_change
221             {
222 9     9 0 11 my ($self, $descendant_fcost, $descendant_ind) = @_;
223            
224              
225 9         13 my $descendant_index = $self->{_descendant_index};
226              
227 9 50       12 if(!$self->is_completed()){
228             # node not completed. no need to update fcost.
229 0         0 $self->need_fval_change(0);
230 0         0 return;
231             }
232              
233 9         10 my $fcost = $self->{_f_cost};
234 9         9 my $least_fcost2 = 99;
235            
236            
237             my $min = sub {
238 0     0   0 my ($n1, $n2) = @_;
239 0 0       0 return ($n1 < $n2 ? $n1 : $n2);
240 9         27 };
241              
242 9 50       19 if($self->{_forgotten_nodes_num} != 0){
243 0         0 foreach my $ind (keys %{$self->{_forgotten_nodes_offsets}}){
  0         0  
244 0         0 my $cost = $self->{_forgotten_node_fcosts}->[$ind];
245 0 0 0     0 if($cost != -1 && $cost < $least_fcost2){
246 0         0 $least_fcost2 = $cost;
247             }
248             }
249             }
250            
251 9         11 my $j = 0;
252 9         51 foreach my $fc (@{$self->{_descendant_fcosts}}){
  9         16  
253 18 100 100     51 if(defined($descendant_ind) && $j != $descendant_ind){
254 4 50 33     11 if($fc != -1 && $fc < $least_fcost2){
255 0         0 $least_fcost2 = $fc;
256             }
257             }
258             else{
259             # special case for index $j: it is the caller's index.
260 14 100 66     58 if(defined($descendant_fcost)){
    100          
261 4 50       9 if($descendant_fcost < $least_fcost2) {
262 4         5 $least_fcost2 = $descendant_fcost;
263             }
264             }
265             elsif($fc != -1 && $fc < $least_fcost2){
266 5         14 $least_fcost2 = $fc;
267             }
268             }
269 18         24 $j++;
270             }
271            
272             # if no successors, this node cannot lead to
273             # goal, so set fcost to infinity.
274 9 50       23 if($self->{_num_successors} == 0){
275 0         0 $least_fcost2 = 99;
276             }
277            
278 9 50       18 if($least_fcost2 != $fcost){
279             # setting need_fcost_change to 1
280 9         17 $self->need_fval_change(1);
281 9         10 my $antecedent = $self->{_antecedent};
282            
283             # recurse on the antecedent
284 9 100       39 if($antecedent){
285 4         12 $antecedent->check_need_fval_change($least_fcost2, $descendant_index);
286             }
287             }
288             }
289              
290              
291              
292              
293              
294             #-----------------------------------------------------------------------------------------------
295             #
296             # Backup the fvals for a node when it is completed.
297             #
298             #-----------------------------------------------------------------------------------------------
299             sub backup_fvals
300             {
301 5     5 0 9 my ($self) = @_;
302            
303 5         17 while($self){
304            
305 9 50       13 if(!$self->is_completed()){
306             # node not completed, return
307 0         0 return;
308             }
309            
310 9         11 my $fcost = $self->{_f_cost};
311 9         9 my $least_fcost = 99;
312              
313             my $min = sub {
314 0     0   0 my ($n1, $n2) = @_;
315 0 0       0 return ($n1 < $n2 ? $n1 : $n2);
316 9         30 };
317            
318 9 50       20 if($self->{_forgotten_nodes_num} != 0){
319 0         0 foreach my $ind (keys %{$self->{_forgotten_nodes_offsets}}){
  0         0  
320 0         0 my $cost = $self->{_forgotten_node_fcosts}->[$ind];
321 0 0 0     0 if($cost != -1 && $cost < $least_fcost){
322 0         0 $least_fcost = $cost;
323             }
324             }
325             }
326              
327 9         11 foreach my $fc (@{$self->{_descendant_fcosts}}){
  9         23  
328 18 100 66     69 if($fc != -1 && $fc < $least_fcost){
329 9         15 $least_fcost = $fc;
330             }
331             }
332              
333             # if no successors, this node cannot lead to
334             # goal, so set fcost to infinity.
335 9 50       20 if($self->{_num_successors} == 0){
336 0         0 $least_fcost = 99;
337             }
338            
339 9 50       17 if($least_fcost != $fcost){
340             # changing fcost from $self->{_f_cost} to $least_fcost
341 9         11 $self->{_f_cost} = $least_fcost;
342            
343 9         12 my $antecedent = $self->{_antecedent};
344 9 100       17 if($antecedent){
345 4         7 my $descendant_index = $self->{_descendant_index};
346 4         8 $antecedent->{_descendant_fcosts}->[$descendant_index] = $least_fcost;
347             }
348             }
349             else{
350             # not changing fcost. current fcost: $self->{_f_cost}, least_fcost: $least_fcost
351 0         0 last;
352             }
353            
354 9         35 $self = $self->{_antecedent};
355            
356             } #end while
357            
358 5         11 return;
359             }
360              
361              
362              
363              
364              
365              
366             #
367             # return 1 if all descendants of this path are in
368             # memory, return 0 otherwise.
369             #
370             sub all_in_memory
371             {
372 5     5 0 8 my ($self) = @_;
373 5         7 my $is_completed = $self->is_completed();
374 5         8 my $num_successors_in_mem = $self->{_num_successors_in_mem};
375 5         8 my $num_successors = $self->{_num_successors};
376              
377 5         6 my $num_forgotten_fcosts = @{$self->{_forgotten_node_fcosts}};
  5         7  
378              
379 5 50 33     13 if($is_completed || $num_successors == 0){
380 5 50       13 if($num_successors == $num_successors_in_mem){
381 0         0 return 1;
382             }
383 5         17 return 0;
384             }
385 0         0 return 0;
386             }
387              
388              
389              
390             #
391             # return 1 if *any* descendants are in memory
392             #
393             sub has_descendants_in_memory
394             {
395 0     0 0 0 my ($self) = @_;
396              
397 0         0 my $num_descendants_on_queue = $self->{_descendants_on_queue};
398            
399 0 0       0 if($num_descendants_on_queue){
400 0         0 return $num_descendants_on_queue;
401             }
402            
403 0         0 return;
404             }
405              
406              
407              
408             #-----------------------------------------------------------------------------
409             # Get descendants iterator function, for for SMA* search. Returns one new
410             # node at a time.
411             #
412             # The SMA* algorithm must handle "forgotten" nodes.
413             #
414             # Generate the next descendant of a path object. Each descendant adds
415             # another node on the path that may lead to the goal.
416             #
417             #-----------------------------------------------------------------------------
418             sub get_descendants_iterator_smastar
419             {
420 5     5 0 7 my ($self) = @_;
421            
422 5         6 my $depth = $self->{_depth};
423 5         5 my $iterator;
424 5         6 my $num_successors = 0;
425 5         3 my $next_descendant;
426              
427             # if we haven't counted the number of successors yet,
428             # count and record the number, so we only have to do
429             # this once.
430 5 50       15 if(!defined($self->{_num_successors})){
431              
432 5         17 $num_successors = $self->get_num_successors();
433              
434 5         7 $self->{_num_successors} = $num_successors;
435              
436 5         5 $#{$self->{_descendants_produced}} = $num_successors;
  5         18  
437 5         6 $#{$self->{_descendant_fcosts}} = $num_successors;
  5         10  
438 5         6 $#{$self->{_forgotten_node_fcosts}} = $num_successors;
  5         11  
439              
440 5         11 for (my $i = 0; $i <= $num_successors; $i++){
441 10         16 $self->{_descendants_produced}->[$i] = 0;
442 10         13 $self->{_descendant_fcosts}->[$i] = -1;
443 10         21 $self->{_forgotten_node_fcosts}->[$i] = -1;
444             }
445             }
446             else{
447             # if number of successors has already been recorded, update
448             # num_successors variable with stored value.
449 0         0 $num_successors = $self->{_num_successors};
450             }
451            
452             return sub{
453 5     5   6 my $i = 0;
454            
455             # entering get_descendants_iterator_smastar() sub
456 5         10 $iterator = $self->get_successors_iterator();
457              
458 5         6 my $descendants_deleted = 0;
459 5         7 my $descendants_found = 0;
460            
461              
462             # loop over nodes returned by iterator
463 5         12 while(my $next_state = $iterator->()){
464              
465 5         22 $next_descendant = AI::Pathfinding::SMAstar::Path->new(
466             _state => $next_state,
467             _eval_func => $self->{_eval_func},
468             _goal_p_func => $self->{_goal_p_func},
469             _get_data_func => $self->{_get_data_func},
470             _num_successors_func => $self->{_num_successors_func},
471             _successors_iterator => $self->{_successors_iterator},
472             _antecedent => $self,
473             _depth => $depth + 1,
474             );
475              
476            
477 5         10 my $start_word = $next_descendant->{_state}->{_start_word};
478 5         10 my $phrase = $next_descendant->{_state}->{_phrase};
479            
480 5   33     28 my $already_produced_p = $self->{_descendants_produced}->[$i] || ($self->{_descendant_fcosts}->[$i] != -1);
481            
482              
483 5 50       8 if($already_produced_p){
484             # have already produced this descendant
485 0         0 $descendants_found++;
486             # found descendant in tree\n";
487              
488 0 0 0     0 if($i == $num_successors - 1 && $descendants_deleted){
489             # !!! resetting iterator index. descendants have been deleted. clearing forgotten_fcosts on next expansion.
490 0         0 $iterator = $self->get_successors_iterator();
491 0         0 $self->{_iterator_index} = 0;
492 0         0 $i = 0;
493              
494             # setting completed to 1 (true)
495 0         0 $self->is_completed(1);
496 0         0 next;
497             }
498             else{
499 0         0 $i++;
500             }
501              
502              
503 0 0       0 if($descendants_found == $num_successors){
504             # setting completed to 1.
505 0         0 $self->is_completed(1);
506             }
507              
508 0         0 $next_descendant = undef; # found this one in list, so undef next descendant.
509            
510             }
511             else{
512             # did not find descendant in descendant's list
513              
514 5 50 33     13 if($i < $self->{_iterator_index} && $self->{_forgotten_nodes_num} != 0){
515             # did not find descendant in list, but may have already produced this
516             # descendant since this node was created.
517 0         0 $i++;
518 0         0 $descendants_deleted++;
519 0         0 next;
520             }
521             # did not find descendant in list, adding now.
522              
523            
524 5         10 $next_descendant->{_descendant_index} = $i;
525 5         8 $self->{_descendants_produced}->[$i] = 1;
526             # new descendant's index is $i
527              
528            
529 5         7 $self->{_iterator_index} = $i + 1;
530            
531 5 50       11 if($self->{_iterator_index} == $self->{_num_successors}){
532 5         7 $iterator = $self->get_successors_iterator();
533 5         117 $self->{_iterator_index} = 0;
534 5         7 $i = 0;
535            
536              
537             # node is completed, setting completed to 1\n";
538 5         11 $self->is_completed(1);
539             }
540            
541             # break out of while() loop
542 5         10 last;
543             }
544             }
545              
546              
547 5 50 33     26 if($i >= $num_successors - 1 && $descendants_deleted && $self->depth() == 0){
      33        
548             # root node. going to reset iterator index. descendants have been deleted. Also, will be
549             # clearing out forgotten_descendants fcost list, since those descendants will be re-generated anyway.
550 0         0 $iterator = $self->get_successors_iterator();
551 0         0 $self->{_iterator_index} = 0;
552 0         0 $i = 0;
553            
554             # setting completed to 1
555 0         0 $self->is_completed(1);
556             }
557            
558 5 50       10 if($next_descendant){
559            
560 5 50       14 if($self->{_forgotten_node_fcosts}->[$next_descendant->{_descendant_index}] != -1){
561             # erase the index of this node in the forgotten_nodes list
562 0         0 $self->{_forgotten_node_fcosts}->[$next_descendant->{_descendant_index}] = -1;
563             # decrement the number of forgotten nodes
564 0         0 $self->{_forgotten_nodes_num} = $self->{_forgotten_nodes_num} - 1;
565 0         0 delete $self->{_forgotten_nodes_offsets}->{$next_descendant->{_descendant_index}};
566             }
567              
568             }
569             else{
570             # no next successor found
571 0         0 $self->is_completed(1);
572             }
573              
574 5         12 return $next_descendant;
575             }
576 5         40 }
577              
578              
579              
580             sub get_data
581             {
582 26     26 0 28 my ($self) = @_;
583              
584 26         33 my $get_data_func = $self->{_get_data_func};
585 26         66 my $data = $get_data_func->($self->{_state});
586            
587 26         68 return $data;
588             }
589              
590              
591              
592             sub DESTROY
593             {
594 3     3   1147 my ($self) = @_;
595              
596             # antecedent is no longer pointing at this object, or else
597             # DESTROY would not have been called.
598 3 100       32 if($self->{_antecedent}){
599 2         10 delete $self->{_antecedent};
600             }
601             }
602              
603              
604              
605              
606              
607              
608              
609              
610              
611              
612              
613              
614              
615              
616              
617              
618              
619              
620              
621              
622              
623              
624              
625              
626              
627              
628              
629              
630              
631              
632              
633              
634              
635             1; # so the require or use succeeds
636