File Coverage

blib/lib/Bio/MUST/Core/Tree.pm
Criterion Covered Total %
statement 176 319 55.1
branch 13 52 25.0
condition 6 21 28.5
subroutine 29 42 69.0
pod 16 17 94.1
total 240 451 53.2


line stmt bran cond sub pod time code
1             package Bio::MUST::Core::Tree;
2             # ABSTRACT: Thin wrapper around Bio::Phylo trees
3             # CONTRIBUTOR: Valerian LUPO <valerian.lupo@doct.uliege.be>
4             $Bio::MUST::Core::Tree::VERSION = '0.212530';
5 17     17   140 use Moose;
  17         41  
  17         144  
6             # use MooseX::SemiAffordanceAccessor;
7 17     17   121132 use namespace::autoclean;
  17         47  
  17         191  
8              
9 17     17   1750 use autodie;
  17         42  
  17         165  
10 17     17   94274 use feature qw(say);
  17         51  
  17         1759  
11              
12 17     17   156 use Smart::Comments '####';
  17         40  
  17         208  
13              
14 17     17   124135 use Carp;
  17         54  
  17         1478  
15 17     17   132 use Const::Fast;
  17         61  
  17         177  
16 17     17   1150 use File::Basename;
  17         40  
  17         1129  
17 17     17   113 use List::AllUtils qw(uniq);
  17         40  
  17         803  
18 17     17   107 use Statistics::Descriptive;
  17         41  
  17         395  
19 17     17   149 use Tie::IxHash;
  17         47  
  17         527  
20              
21 17     17   10134 use Bio::Phylo::IO qw(parse);
  17         877480  
  17         1221  
22              
23 17     17   180 use Bio::MUST::Core::Types;
  17         43  
  17         540  
24 17     17   116 use Bio::MUST::Core::Constants qw(:files);
  17         44  
  17         2758  
25 17     17   137 use Bio::MUST::Core::Utils qw(:filenames);
  17         48  
  17         1853  
26 17     17   143 use aliased 'Bio::MUST::Core::SeqId';
  17         40  
  17         175  
27 17     17   4428 use aliased 'Bio::MUST::Core::IdList';
  17         56  
  17         77  
28             with 'Bio::MUST::Core::Roles::Commentable',
29             'Bio::MUST::Core::Roles::Listable';
30              
31              
32             has 'tree' => (
33             is => 'ro',
34             isa => 'Maybe[Bio::Phylo::Forest::Tree]',
35             default => undef,
36             writer => '_set_tree',
37             );
38              
39              
40             # color for uncolored taxon (see Taxonomy::ColorScheme)
41             const my $BLACK => '#000000';
42              
43             # Note: we don't store SeqId objects in the tree but dynamically build them
44             # to benefit from SeqId methods (e.g., auto-removal of first '_'). This is
45             # the most flexible approach without costing too much in CPU-time.
46              
47              
48             sub all_seq_ids {
49 5     5 1 25 my $self = shift;
50              
51             # old code:
52             # my @tips = @{ $self->tree->get_terminals };
53             # my @full_ids = map { $_->get_name } @tips;
54             # return map { SeqId->new(full_id => $_) } @full_ids;
55              
56             # Note1: we use a slower visitor method to ensure that the id array
57             # is sorted as when displayed by TreeDrawer methods
58             # Note2: this order is consistent with FigTree display as well, but not
59             # with Seaview (and njplot) renderings
60              
61 5         23 my @seq_ids;
62              
63             $self->tree->visit_depth_first(
64             # collect tip names and convert them to SeqIds
65             -pre => sub {
66 180     180   37704 my $node = shift;
67 180 100       478 if ($node->is_terminal) {
68 94         12537 push @seq_ids, SeqId->new( full_id => $node->get_name );
69             }
70 180         12045 return;
71             },
72 5         212 );
73              
74 5         1309 return @seq_ids;
75             }
76              
77              
78             # NODE-LABEL EDITING METHODS
79              
80              
81             sub shorten_ids { ## no critic (RequireArgUnpacking)
82 0     0 1 0 return shift->_change_ids_(1, @_);
83             }
84              
85              
86             sub restore_ids { ## no critic (RequireArgUnpacking)
87 10     10 1 48 return shift->_change_ids_(0, @_);
88             }
89              
90              
91             sub _change_ids_ {
92 10     10   29 my $self = shift;
93 10         18 my $abbr = shift;
94 10         24 my $id_mapper = shift;
95              
96             # update only terminal nodes
97 10         17 for my $tip ( @{ $self->tree->get_terminals } ) {
  10         305  
98 360         108724 my $seq_id = SeqId->new( full_id => $tip->get_name );
99 360 50       9374 my $new_id = $abbr ? $id_mapper->abbr_id_for( $seq_id->full_id )
100             : $id_mapper->long_id_for( $seq_id->full_id );
101 360 50       1600 $tip->set_name($new_id) if $new_id;
102             } # Note: leave id alone if not found
103              
104 10         120 return;
105             }
106              
107              
108             sub switch_attributes_and_labels_for_terminals { ## no critic (RequireArgUnpacking)
109 0     0 1 0 return shift->_switch_attributes_and_labels_(0, @_);
110             }
111              
112              
113             sub switch_attributes_and_labels_for_internals { ## no critic (RequireArgUnpacking)
114 0     0 1 0 return shift->_switch_attributes_and_labels_(1, @_);
115             }
116              
117              
118             sub switch_attributes_and_labels_for_entities { ## no critic (RequireArgUnpacking)
119 0     0 1 0 return shift->_switch_attributes_and_labels_(2, @_);
120             }
121              
122              
123             sub _switch_attributes_and_labels_ {
124 0     0   0 my $self = shift;
125 0         0 my $mode = shift;
126 0         0 my $key = shift;
127              
128             # TODO: investigate options of Bio::Phylo::Unparsers::Newick
129              
130             # update either terminal or internal nodes
131 0         0 my $tree = $self->tree;
132             my @nodes = @{
133 0 0       0 $mode == 2 ? $tree->get_entities :
  0 0       0  
134             $mode == 1 ? $tree->get_internals :
135             $tree->get_terminals
136             };
137              
138             # Note: old labels are backuped in specified attributes and vice-versa
139             # TODO: allow appending acc for terminal nodes?
140 0         0 for my $node (@nodes) {
141 0         0 my $label = $node->get_name;
142 0         0 my $attribute = $node->get_generic($key);
143 0         0 $node->set_generic($key => $label);
144 0         0 $node->set_name($attribute);
145             }
146              
147 0         0 return;
148             }
149              
150              
151             sub switch_branch_lengths_and_labels_for_entities {
152 0     0 1 0 my $self = shift;
153 0         0 my $length = shift;
154              
155             # use branch lengths as labels
156 0         0 my $tree = $self->tree;
157 0         0 for my $node ( @{ $tree->get_internals } ) {
  0         0  
158 0         0 $node->set_name($node->get_branch_length);
159             }
160              
161             # delete branch lengths
162 0         0 for my $node ( @{ $tree->get_entities } ) {
  0         0  
163 0         0 $node->set_branch_length($length); # default is undef
164             }
165              
166 0         0 return;
167             }
168              
169              
170             sub collapse_subtrees {
171 0     0 1 0 my $self = shift;
172 0   0     0 my $key = shift // 'taxon_collapse';
173              
174             # compute maximal path length (from root)
175 0         0 my $tree_max_path = $self->tree->get_root->calc_max_path_to_tips;
176              
177             # "balanced"-order tree traversal
178 0         0 my $collapsed; # will be defined when within a collapsed subtree
179             $self->tree->visit_depth_first(
180              
181             # collapse subtrees with identical attributes
182             -pre_daughter => sub {
183 0     0   0 my $node = shift;
184 0 0       0 return if $node->is_terminal;
185              
186             # reset collapsing for robustness
187 0         0 $node->set_generic('!collapse' => undef);
188              
189             # do not further collapse children of a collapsed subtree
190             # to facilitate interactive uncollapsing (e.g., in FigTree)
191 0 0       0 return if $collapsed;
192              
193             # collect children attributes
194 0         0 my @attrs;
195 0         0 for (my $i = 0; my $child = $node->get_child($i); $i++) {
196 0         0 push @attrs, $child->get_generic($key);
197             }
198              
199             # collapse subtree if all attributes are defined and identical
200 0 0       0 return if List::AllUtils::any { not defined $_ } @attrs;
  0         0  
201 0 0       0 return if uniq(@attrs) > 1;
202              
203             # skip black color when collapsing at color
204 0         0 my $color = shift @attrs;
205 0 0       0 return if $color eq $BLACK;
206              
207             # compute and set FigTree's "node height" for collapsed clade
208             # Note: the tallest tip will be 0
209 0         0 my $sub_max_path = $node->calc_max_path_to_tips
210             + $node->calc_path_to_root;
211 0         0 my $node_height = $tree_max_path - $sub_max_path;
212 0         0 $node->set_generic('!collapse' => qq|{"collapsed",$node_height}|);
213              
214             # set "within a collapsed subtree" status
215 0         0 $collapsed = $node->get_id;
216              
217 0         0 return;
218             },
219              
220             -post_daughter => sub {
221 0     0   0 my $node = shift;
222 0 0       0 return if $node->is_terminal;
223              
224             # unset "within a collapsed subtree" status (when leaving subtree)
225 0 0 0     0 $collapsed = undef
226             if defined $collapsed && $collapsed eq $node->get_id;
227              
228 0         0 return;
229             },
230 0         0 );
231              
232 0         0 return;
233             }
234              
235             # Note: very naive approach and not applicable in practice.
236             # Should probably use the derivative of branch length increase in log space.
237             # Meanwhile: use treeshrink
238              
239             sub long_leaf_list {
240 1     1 0 401 my $self = shift;
241 1   50     11 my $fact = shift // 1.5;
242              
243 1         5 my @tips = @{ $self->tree->get_terminals };
  1         43  
244              
245             # compute terminal branch length distribution
246 1         79258 my @lengths = map { $_->get_branch_length } @tips;
  255         2746  
247 1         40 #### list: sort { $a <=> $b } @lengths
  1569         2083  
  1         1921  
248 1         37 my $stat = Statistics::Descriptive::Full->new;
249 1         306 $stat->add_data( \@lengths );
250              
251 1         547 my ($q1, $q3) = ( $stat->quantile(1), $stat->quantile(3) );
252 1         1823 #### $q1
  1         215  
253 1         6 #### $q3
  1         161  
254 1         6 #### iqr: $q3-$q1
  1         161  
255 1         5 #### $fact
  1         151  
256 1         3 my $threshold = $q3 + $fact * ($q3 - $q1);
257 1         6 #### $threshold
  1         162  
258              
259 32         149 my @seq_ids = map { SeqId->new( full_id => $_->get_name ) }
260 1         12 grep { $_->get_branch_length > $threshold } @tips;
  255         2937  
261              
262 1         18 #### n: scalar @seq_ids
  1         327  
263 1         77 return IdList->new( ids => \@seq_ids );
264             }
265              
266             # TREE-MATCHING METHODS
267              
268              
269             # TODO1: need for a taxon pruning sub as it seems that the -keep option
270             # TODO1: of Bio::Phylo Newick parser does not work completely
271             # TODO2: need for a rerooting sub that completely works!
272              
273             sub match_branch_lengths {
274 0     0 1 0 my $self = shift;
275 0         0 my $other = shift; # second tree
276              
277 0         0 my $tree1 = $self->tree;
278 0         0 my $tree2 = $other->tree;
279 0         0 tie my %blens_for, 'Tie::IxHash';
280              
281 0         0 for my $tree ($tree1, $tree2) {
282 0         0 for my $node ( @{ $tree->get_entities } ) {
  0         0  
283              
284             # compute clade key and store corresponding branch length
285             my $clade_key
286             = join '::',
287 0         0 sort { $a cmp $b }
288 0         0 map { $_->get_internal_name } @{ $node->get_terminals }
  0         0  
  0         0  
289             ;
290 0         0 my $branch_length = $node->get_branch_length;
291 0 0       0 push @{ $blens_for{$clade_key} },
  0         0  
292             $branch_length if defined $branch_length;
293             }
294             }
295              
296             # ensure that bipartitions matching proceeded as expected
297             carp '[BMC] Warning: cannot match all bipartitions; returning useless hash!'
298             unless List::AllUtils::all {
299 0     0   0 @{ $blens_for{$_} } == 2
  0         0  
300 0 0       0 } keys %blens_for;
301              
302 0         0 return \%blens_for;
303             }
304              
305              
306             # I/O METHODS
307              
308              
309             sub load {
310 9     9 1 538032 my $class = shift;
311 9         42 my $infile = shift;
312              
313 9         83 open my $in, '<', $infile;
314              
315 9         5846 my $tree = $class->new();
316 9         24 my $newick_str;
317              
318             LINE:
319 9         8368 while (my $line = <$in>) {
320 15         79 chomp $line;
321              
322             # skip empty lines and process comment lines
323 15 100 66     345 next LINE if $line =~ $EMPTY_LINE
324             || $tree->is_comment($line);
325              
326 9         252 $newick_str .= $line;
327             }
328              
329 9         118 my $forest = parse(-format => 'newick', -string => $newick_str);
330 9         20948064 $tree->_set_tree($forest->first);
331              
332 9         68 return $tree;
333             }
334              
335              
336             # Note: it seems that to_newick automatically replace spaces by '_' in node
337             # labels (ids), which is a quite reasonable behavior.
338              
339             # TODO: define better API for outputting branch lengths/support values
340             # TODO: use constants for to_newick parameters
341              
342              
343              
344             sub store {
345 1     1 1 5 my $self = shift;
346 1         4 my $outfile = shift;
347 1   50     14 my $args = shift // {}; # HashRef (should not be empty...)
348              
349             # TODO: consider allowing this hash for all store methods?
350              
351 1   50     17 $args->{-nodelabels} //= 1; # default to nodelabels on
352              
353 1         9 open my $out, '>', $outfile;
354 1         390 say {$out} _clean_newick_str( $self->tree->to_newick( %{$args} ) );
  1         49  
  1         22  
355              
356 1         65 return;
357             }
358              
359              
360             sub store_itol_datasets {
361 0     0 1 0 my $self = shift;
362 0         0 my $outfile = shift;
363 0   0     0 my $key = shift // 'taxon';
364              
365             # name dataset files
366 0         0 my $outbase = change_suffix($outfile, '.txt');
367 0         0 my $color_file = insert_suffix($outbase, '-color');
368 0         0 my $clade_file = insert_suffix($outbase, '-clade');
369 0         0 my $range_file = insert_suffix($outbase, '-range');
370 0         0 my $label_file = insert_suffix($outbase, '-label');
371 0         0 my $colps_file = insert_suffix($outbase, '-collapse');
372              
373             # open and setup dataset files
374 0         0 open my $color_out, '>', $color_file;
375 0         0 say {$color_out} join "\n", 'TREE_COLORS', 'SEPARATOR COMMA', 'DATA';
  0         0  
376 0         0 open my $clade_out, '>', $clade_file;
377 0         0 say {$clade_out} join "\n", 'TREE_COLORS', 'SEPARATOR COMMA', 'DATA';
  0         0  
378 0         0 open my $range_out, '>', $range_file;
379 0         0 say {$range_out} join "\n", 'TREE_COLORS', 'SEPARATOR COMMA', 'DATA';
  0         0  
380 0         0 open my $label_out, '>', $label_file;
381 0         0 say {$label_out} join "\n", 'LABELS', 'SEPARATOR COMMA', 'DATA';
  0         0  
382 0         0 open my $colps_out, '>', $colps_file;
383 0         0 say {$colps_out} join "\n", 'COLLAPSE', 'DATA';
  0         0  
384              
385             # setup format
386 0         0 my $type = 'normal',
387             my $size = 1;
388              
389             NODE:
390 0         0 for my $node ( @{ $self->tree->get_entities } ){
  0         0  
391 0   0     0 my $color = $node->get_generic('!color') // $BLACK;
392 0         0 my $label = $node->get_generic($key);
393 0         0 my $collapse = $node->get_generic('!collapse');
394              
395 0 0       0 if ($node->is_terminal) {
396 0 0       0 next NODE if $color eq $BLACK;
397              
398 0         0 my $id = SeqId->new( full_id => $node->get_name )->foreign_id;
399 0         0 say {$color_out} join q{,}, $id, 'label', $color, $type, $size;
  0         0  
400 0         0 say {$clade_out} join q{,}, $id, 'clade', $color, $type, $size;
  0         0  
401 0         0 say {$range_out} join q{,}, $id, 'range', $color, $type, $size;
  0         0  
402              
403 0         0 next NODE;
404             }
405              
406 0         0 my @descendants;
407 0         0 for (my $i = 0; my $child = $node->get_child($i); $i++) {
408 0         0 push @descendants, @{ $child->get_terminals };
  0         0  
409             }
410              
411             # Note: Simply calling get_terminals on node returns descendants
412             # in an order that leads to mis-selecting the first and last ones.
413             # Instead we store the descendants from node's main two (or more)
414             # children sequentially. This seems to fix the issue.
415              
416             # determine first and last descendants to build node id
417             my $id = join '|', map {
418 0 0       0 SeqId->new( full_id => $_->get_name )->foreign_id
  0         0  
419             } @descendants[ @descendants > 1 ? (0,-1) : (0) ];
420             # Note: should always > 1 but one never knows...
421              
422 0 0       0 say {$label_out} join q{,}, $id, $label if $label;
  0         0  
423 0 0       0 say {$colps_out} $id if $collapse;
  0         0  
424              
425 0 0       0 next NODE if $color eq $BLACK;
426              
427 0         0 say {$color_out} join q{,}, $id, 'label', $color, $type, $size;
  0         0  
428 0         0 say {$clade_out} join q{,}, $id, 'clade', $color, $type, $size;
  0         0  
429 0         0 say {$range_out} join q{,}, $id, 'range', $color, $type, $size;
  0         0  
430             }
431              
432 0         0 return;
433             }
434              
435              
436             sub store_figtree {
437 0     0 1 0 my $self = shift;
438 0         0 my $outfile = shift;
439              
440             # transfer taxon names for internals only
441             # this is needed to avoid double naming of tips (std label + taxon)
442 0         0 for my $node ( @{ $self->tree->get_internals } ) {
  0         0  
443 0         0 my $taxon = $node->get_generic('taxon');
444 0 0       0 $node->set_generic('!name' => qq|"$taxon"|) if $taxon;
445             } # Note: get_generic does not return undef, hence: if $taxon
446              
447             # build mesquite-enabled Newick string
448 0         0 my $newick_str = $self->tree->to_newick(
449             -nodelabels => 1,
450             # -blformat => '%.10f',
451             -nhxkeys => [ '!name', '!color', '!collapse' ],
452             -nhxstyle => 'mesquite',
453             );
454              
455             # ... and adapt it for FigTree
456 0         0 $newick_str =~ s{\[%}{[&}xmsg;
457              
458             # ... then restore zero-valued bootstrap values
459             # since 'false' internal names are converted to 'NodeNNN' strings
460 0         0 $newick_str =~ s{\b Node\d+ \b}{0}xmsg;
461             # TODO: consider doing that also in the standard store?
462              
463 0         0 open my $out, '>', $outfile;
464              
465             # output minimal NEXUS tree file
466 0         0 print {$out} <<"EOF";
  0         0  
467             #NEXUS
468              
469             begin trees;
470             tree tree_1 = [&R] $newick_str
471             end;
472             EOF
473              
474 0         0 return;
475             }
476              
477              
478             sub store_arb {
479 3     3 1 10 my $self = shift;
480 3         7 my $outfile = shift;
481 3   50     12 my $args = shift // {}; # HashRef (should not be empty...)
482              
483 3         8 my $alifile = $args->{alifile};
484              
485             # optionally link to Ali (without path)
486 3 100       11 if ($alifile) {
487 1         98 my ($basename, $dir, $ext) = fileparse($alifile, qr{\.[^.]*}xms);
488 1         53 $self->insert_comment("$basename$ext");
489             }
490              
491             # build standard Newick string
492 3         120 my $newick_str = _clean_newick_str(
493             $self->tree->to_newick( -nodelabels => 0 )
494             );
495              
496 3         31 open my $out, '>', $outfile;
497              
498             # output ARB tree file
499 3         1574 print {$out} $self->header;
  3         42  
500 3         13 say {$out} $newick_str;
  3         140  
501              
502 3         237 return;
503             }
504              
505              
506             sub store_grp {
507 5     5 1 18 my $self = shift;
508 5         17 my $outfile = shift;
509              
510             # extract tip ids, non-root nodes and support values
511 5         50 my @tip_ids = map { $_->foreign_id } $self->all_seq_ids;
  94         264  
512 5         191 my @nodes = grep { not $_->is_root } @{ $self->tree->get_internals };
  86         3272  
  5         203  
513 5         92 my @bp_vals = map { $_->get_name } @nodes;
  81         1156  
514              
515             # determine support value type (BP or PP)
516 5 50   8   142 my $pp = List::AllUtils::all { $_ >= 0.0 && $_ <= 1.0 } @bp_vals;
  8         75  
517              
518 5         57 open my $out, '>', $outfile;
519              
520 5         2486 for my $node (@nodes) {
521              
522             # build bipartition string
523             my %in_bip = map {
524 755         229549 SeqId->new( full_id => $_->get_name )->foreign_id => 1
525 81         160 } @{ $node->get_terminals };
  81         461  
526 81 100       416 my $bip = join q{}, map { $in_bip{$_} ? '*' : '.' } @tip_ids;
  5304         10172  
527              
528             # fetch (and possibly fix) support value for bipartition
529 81         346 my $support = shift @bp_vals;
530 81 100       250 $support = int( $support * 100.0 ) if $pp;
531              
532             # write bipartition line
533 81         151 say {$out} "$bip $support";
  81         716  
534             }
535              
536 5         547 return;
537             }
538              
539              
540             sub store_tpl {
541 1     1 1 24 my $self = shift;
542 1         13 my $outfile = shift;
543              
544             # backup and discard branch lengths
545             # Note: I have to do that since I cannot clone the tree (Bio::Phylo bug?)
546 1         9 my @branch_lengths;
547 1         10 for my $node ( @{ $self->tree->get_entities } ) {
  1         51  
548 914         12777 push @branch_lengths, $node->get_branch_length;
549 914         11499 $node->set_branch_length(undef);
550             }
551              
552 1         51 open my $out, '>', $outfile;
553              
554             # output topology
555 1         599 say {$out} '1'; # TODO: improve this for multiple topologies
  1         23  
556 1         9 say {$out} _clean_newick_str(
  1         47  
557             $self->tree->to_newick( -nodelabels => 0 )
558             );
559              
560             # restore branch lengths
561 1         9 for my $node ( @{ $self->tree->get_entities } ) {
  1         86  
562 914         17313 $node->set_branch_length( shift @branch_lengths );
563             }
564              
565 1         142 return;
566             }
567              
568              
569             sub _clean_newick_str {
570 5     5   1621164 my $newick_str = shift;
571              
572             # remove quotes...
573             # ...and trailing zero-length branch length (RAxML) if any
574 5         209 $newick_str =~ tr{'"}{}d;
575 5         220 $newick_str =~ s{:0\.0+;}{;}xmsg;
576              
577 5         323 return $newick_str;
578             }
579              
580             __PACKAGE__->meta->make_immutable;
581             1;
582              
583             __END__
584              
585             =pod
586              
587             =head1 NAME
588              
589             Bio::MUST::Core::Tree - Thin wrapper around Bio::Phylo trees
590              
591             =head1 VERSION
592              
593             version 0.212530
594              
595             =head1 SYNOPSIS
596              
597             # TODO
598              
599             =head1 DESCRIPTION
600              
601             # TODO
602              
603             =head1 METHODS
604              
605             =head2 all_seq_ids
606              
607             =head2 shorten_ids
608              
609             =head2 restore_ids
610              
611             =head2 switch_attributes_and_labels_for_terminals
612              
613             =head2 switch_attributes_and_labels_for_internals
614              
615             =head2 switch_attributes_and_labels_for_entities
616              
617             =head2 switch_branch_lengths_and_labels_for_entities
618              
619             =head2 collapse_subtrees
620              
621             =head2 match_branch_lengths
622              
623             =head2 load
624              
625             =head2 store
626              
627             =head2 store_itol_datasets
628              
629             =head2 store_figtree
630              
631             =head2 store_arb
632              
633             =head2 store_grp
634              
635             =head2 store_tpl
636              
637             =head1 AUTHOR
638              
639             Denis BAURAIN <denis.baurain@uliege.be>
640              
641             =head1 CONTRIBUTOR
642              
643             =for stopwords Valerian LUPO
644              
645             Valerian LUPO <valerian.lupo@doct.uliege.be>
646              
647             =head1 COPYRIGHT AND LICENSE
648              
649             This software is copyright (c) 2013 by University of Liege / Unit of Eukaryotic Phylogenomics / Denis BAURAIN.
650              
651             This is free software; you can redistribute it and/or modify it under
652             the same terms as the Perl 5 programming language system itself.
653              
654             =cut