File Coverage

blib/lib/Bio/NEXUS/DistancesBlock.pm
Criterion Covered Total %
statement 33 141 23.4
branch 4 60 6.6
condition 1 19 5.2
subroutine 9 15 60.0
pod 4 4 100.0
total 51 239 21.3


line stmt bran cond sub pod time code
1             #################################################################
2             # DistancesBlock.pm
3             #################################################################
4             # Author: Thomas Hladish
5             # $Id: DistancesBlock.pm,v 1.18 2007/09/21 23:09:09 rvos Exp $
6              
7             #################### START POD DOCUMENTATION ##################
8              
9             =head1 NAME
10              
11             Bio::NEXUS::DistancesBlock - Represents DISTANCES block in NEXUS file
12              
13             =head1 SYNOPSIS
14              
15              
16             =head1 DESCRIPTION
17              
18             The DistancesBlock class represents a NEXUS Distances Block and provides methods for reading, writing, and accessing data within these blocks. Distances Blocks contain distance matrices, or a table of calculated distances between each possible pair of taxa.
19              
20             =head1 COMMENTS
21              
22             =head1 FEEDBACK
23              
24             All feedback (bugs, feature enhancements, etc.) are greatly appreciated.
25              
26             =head1 AUTHORS
27              
28             Tom Hladish (tjhladish at yahoo)
29              
30             =head1 VERSION
31              
32             $Revision: 1.18 $
33              
34             =head1 METHODS
35              
36             =cut
37              
38             package Bio::NEXUS::DistancesBlock;
39              
40 34     34   186 use strict;
  34         69  
  34         1358  
41             #use Data::Dumper; # XXX this is not used, might as well not import it!
42             #use Carp; # XXX this is not used, might as well not import it!
43 34     34   185 use Bio::NEXUS::Functions;
  34         206  
  34         8162  
44 34     34   212 use Bio::NEXUS::Matrix;
  34         69  
  34         966  
45 34     34   227 use Bio::NEXUS::Util::Logger;
  34         71  
  34         787  
46 34     34   192 use Bio::NEXUS::Util::Exceptions;
  34         82  
  34         1771  
47 34     34   189 use vars qw(@ISA $VERSION $AUTOLOAD);
  34         69  
  34         2899  
48 34     34   269 use Bio::NEXUS; $VERSION = $Bio::NEXUS::VERSION;
  34         84  
  34         100885  
49              
50             @ISA = qw(Bio::NEXUS::Matrix);
51             my $logger = Bio::NEXUS::Util::Logger->new();
52              
53             =head2 new
54              
55             Title : new
56             Usage : block_object = new Bio::NEXUS::DistancesBlock($block_type, $commands, $verbose, $taxa);
57             Function: Creates a new Bio::NEXUS::DistancesBlock object
58             Returns : Bio::NEXUS::DistancesBlock object
59             Args : type (string), the commands/comments to parse (array ref), and a verbose flag (0 or 1)
60              
61             =cut
62              
63             sub new {
64 1     1 1 4 my ( $class, $type, $commands, $verbose, $taxa ) = @_;
65 1 50       4 if ( not $type) {
66 0         0 ( $type = lc $class ) =~ s/Bio::NEXUS::(.+)Block/$1/i;
67             }
68 1         4 my $self = {
69             'type' => $type
70             };
71 1         3 bless $self, $class;
72 1         10 $self->set_taxlabels($taxa);
73 1 50 33     9 if ( ( defined $commands ) and @$commands ) {
74 1         9 $self->_parse_block( $commands, $verbose )
75             }
76 0         0 return $self;
77             }
78              
79             =begin comment
80              
81             Title : _parse_matrix
82             Usage : $self->_parse_matrix($block);
83             Function: Parses a distance matrix, stores the data
84             Returns : none
85             Args : the distance matrix to parse (string)
86              
87             =end comment
88              
89             =cut
90              
91             sub _parse_matrix {
92 0     0   0 my ( $self, $buffer ) = @_;
93              
94             # Set format values as already parsed, or to NEXUS-specified defaults
95 0         0 my %format = %{ $self->get_format() };
  0         0  
96              
97 0 0       0 my $triangle = defined $format{'triangle'} ? $format{'triangle'} : 'lower';
98 0 0       0 my $diagonal = defined $format{'diagonal'} ? $format{'diagonal'} : 1;
99 0 0       0 my $labels = defined $format{'labels'} ? $format{'labels'} : 1;
100 0 0       0 my $missing = defined $format{'missing'} ? $format{'missing'} : undef;
101 0 0       0 my $interleave = defined $format{'interleave'} ? $format{'interleave'} : 0;
102              
103 0 0 0     0 if ( $triangle =~ /^both$/i && !$diagonal ) {
104 0         0 Bio::NEXUS::Util::Exceptions::BadFormat->throw(
105             'error' => "The Distances Block contains a matrix that has\n"
106             . "both upper and lower halves, but does not have\n"
107             . "diagonal values.\nThis is prohibited by the NEXUS standard"
108             );
109             }
110 0 0 0     0 if ( $interleave && !$labels ) {
111 0         0 Bio::NEXUS::Util::Exceptions::BadFormat->throw(
112             'error' => "This matrix is interleaved and without row labels\n"
113             . "('unlabeled'). Please label rows or use a non-\n"
114             . "interleaved format, to allow for safer parsing"
115             );
116             }
117              
118 0         0 my @rows = split /\n+/, $buffer;
119 0         0 my @taxa_order;
120             my %row_for;
121              
122             # First, we'll deal with whether the matrix is interleaved and labeled
123 0 0 0     0 if ( $interleave || $labels ) {
124 0         0 for my $row (@rows) {
125 0         0 my ( $taxon, @distances ) = @{ _parse_nexus_words($row) };
  0         0  
126 0         0 push( @taxa_order, $taxon );
127 0         0 push @{ $row_for{$taxon} }, @distances;
  0         0  
128             }
129             }
130             else {
131 0         0 @taxa_order = @{ $self->get_taxlabels() };
  0         0  
132 0         0 @rows =
133 0         0 grep { !/^\s+$/ } @rows; # throw out rows that are just blank space
134              
135 0         0 for ( my $i = 0; $i < @rows; $i++ ) {
136 0         0 my $row = $rows[$i];
137 0         0 $row_for{ $taxa_order[$i] } = [ split /\s+/, $row ];
138             }
139             }
140              
141             # It's important to keep track of this so that we know what the columns
142             # are, since they're unlabeled
143 0         0 $self->set_taxlabels( \@taxa_order );
144              
145             # Now everything is stored in %row_for, and the original order
146             # is in @taxa_order
147 0         0 my $matrix;
148 0         0 for ( my $r = 0; $r < @taxa_order; $r++ ) {
149 0         0 my $row_label = $taxa_order[$r];
150 0         0 my @cells = @{ $row_for{$row_label} };
  0         0  
151              
152             # If this is a full matrix (simplest to parse),
153 0 0       0 if ( $triangle =~ /^both$/i ) {
    0          
    0          
154              
155             # iterate through the values
156 0         0 for ( my $c = 0; $c < @cells; $c++ ) {
157 0         0 my $cell_val = $cells[$c];
158 0         0 my $col_label = $taxa_order[$c];
159              
160             # and store them in $matrix.
161 0         0 $matrix->{$row_label}{$col_label} = $cell_val;
162             }
163             }
164              
165             # If it's a lower triangle,
166             elsif ( $triangle =~ /^lower$/i ) {
167              
168             # iterate through the values
169 0         0 for ( my $c = 0; $c < @cells; $c++ ) {
170 0         0 my $cell_val = $cells[$c];
171              
172             # and store them symmetrically in $matrix
173 0         0 my $col_label = $taxa_order[$c];
174 0         0 $matrix->{$row_label}{$col_label} =
175             ( $matrix->{$col_label}{$row_label} = $cell_val );
176             }
177              
178             # In case there are no diagonal values,
179 0 0       0 if ( !$diagonal ) {
180              
181             # make sure they still get stored (as zeroes)
182 0         0 $matrix->{$row_label}{$row_label} = 0;
183             }
184             }
185              
186             # If this is an upper triangle
187             elsif ( $triangle =~ /^upper$/i ) {
188              
189             # iterate through the values
190 0         0 for ( my $c = 0; $c < @cells; $c++ ) {
191 0         0 my $cell_val = $cells[$c];
192              
193             # and make sure the column label is correct,
194             # since everything needs to be shifted over.
195 0 0       0 my $col_label = $diagonal
196             ? $taxa_order[ $r + $c ]
197             : $taxa_order[ $r + $c + 1 ];
198              
199             # Store the values symmetrically in $matrix
200 0         0 $matrix->{$row_label}{$col_label} =
201             ( $matrix->{$col_label}{$row_label} = $cell_val );
202             }
203              
204             # In case there are no diagonal values,
205 0 0       0 if ( !$diagonal ) {
206              
207             # make sure they still get stored (as zeroes)
208 0         0 $matrix->{$row_label}{$row_label} = 0;
209             }
210             }
211             else {
212 0         0 Bio::NEXUS::Util::Exceptions::BadFormat->throw(
213             'error' => "Unknown value '$triangle' for Format:Triangle\n"
214             . "in the DistancesBlock. Expecting 'upper', 'lower', or 'both'."
215             );
216             }
217             }
218              
219 0 0       0 $self->set_ntax( scalar keys %$matrix ) unless $self->get_ntax();
220 0         0 $self->{'matrix'} = $matrix;
221 0         0 return $self->{'matrix'};
222             }
223              
224             =head2 get_matrix
225              
226             Title : get_matrix
227             Usage : $matrix = $self->get_matrix();
228             Function: Retrieves the entire distance matrix
229             Returns : a hashref of hashrefs
230             Args : none
231             Note : Distance values may be retrieved by specifying the row and column keys, e.g. $dist = $matrix->{$row_taxon}{$col_taxon}
232              
233             =cut
234              
235             sub get_matrix {
236 0     0 1 0 my ( $self, $taxon ) = @_;
237 0         0 return $self->{'matrix'};
238             }
239              
240             =head2 get_distances_for
241              
242             Title : get_distances_for
243             Usage : %taxon1_distances = %{ $self->get_distances_for($first_taxon) };
244             Function: Retrieves a row of the distance matrix
245             Returns :
246             Args : the row label (a taxlabel) for the row desired (string)
247              
248             =cut
249              
250             sub get_distances_for {
251 0     0 1 0 my ( $self, $taxon ) = @_;
252 0         0 my $matrix = $self->get_matrix();
253 0         0 my $row = $matrix->{$taxon};
254 0         0 return $row;
255             }
256              
257             =head2 get_distance_between
258              
259             Title : get_distance_between
260             Usage : $distance = $self->get_distance_between($row_taxon, $column_taxon);
261             Function: Retrieves a cell from the matrix
262             Returns : A scalar (number)
263             Args : the row and column labels (both taxa) for the cell desired
264             Note : Generally get_distance_between($A, $B) == get_distance_between($B, $A); however, this need not be true if the distance matrix is not symmetric. Make sure you are asking for the distance you want.
265              
266             =cut
267              
268             sub get_distance_between {
269 0     0 1 0 my ( $self, $tax1, $tax2 ) = @_;
270 0         0 my $matrix = $self->get_matrix();
271 0         0 my $dist = $matrix->{$tax1}{$tax2};
272 0         0 return $dist;
273             }
274              
275             =begin comment
276              
277             Name : _write
278             Usage : $block->_write();
279             Function: Writes out NEXUS Distances Block
280             Returns : none
281             Args : file handle
282              
283             =end comment
284              
285             =cut
286              
287             sub _write {
288 0     0   0 my ( $self, $fh, $verbose ) = @_;
289 0   0     0 $fh ||= \*STDOUT;
290              
291 0         0 Bio::NEXUS::Block::_write( $self, $fh );
292 0         0 $self->_write_dimensions( $fh, $verbose );
293 0         0 $self->_write_format( $fh, $verbose );
294 0         0 $self->_write_matrix( $fh, $verbose );
295 0         0 print $fh "END;\n";
296             }
297              
298             =begin comment
299              
300             Name : _write_matrix
301             Usage : $block->_write_matrix();
302             Function: writes out the matrix for a NEXUS Distances Block
303             Returns : none
304             Args : file handle
305              
306             =end comment
307              
308             =cut
309              
310             sub _write_matrix {
311 0     0   0 my ( $self, $fh, $verbose ) = @_;
312 0   0     0 $fh ||= \*STDOUT;
313              
314 0         0 my %format = %{ $self->get_format() };
  0         0  
315              
316 0 0       0 my $triangle = defined $format{'triangle'} ? $format{'triangle'} : 'lower';
317 0 0       0 my $diagonal = defined $format{'diagonal'} ? $format{'diagonal'} : 1;
318 0 0       0 my $labels = defined $format{'labels'} ? $format{'labels'} : 1;
319 0 0       0 my $missing = defined $format{'missing'} ? $format{'missing'} : undef;
320 0 0       0 my $interleave = defined $format{'interleave'} ? $format{'interleave'} : 0;
321              
322 0 0 0     0 if ( $triangle =~ /^both$/i && !$diagonal ) {
323 0         0 Bio::NEXUS::Util::Exceptions::BadFormat->throw(
324             'error' => "The Distances Block contains a matrix that has\n"
325             . "both upper and lower halves, but does not have\n"
326             . "diagonal values. This is prohibited by the NEXUS standard"
327             );
328             }
329              
330 0         0 print $fh "\tMATRIX\n";
331 0         0 my $matrix = $self->get_matrix();
332              
333 0         0 my @taxlabels = @{ $self->get_taxlabels };
  0         0  
334              
335 0         0 for ( my $r = 0; $r < @taxlabels; $r++ ) {
336 0         0 my $row_taxon = $taxlabels[$r];
337 0         0 my $print_taxon = _nexus_formatted($row_taxon);
338 0         0 print $fh "\t$print_taxon";
339              
340 0         0 my ( $first_col, $last_col );
341              
342             # Determine which part of the matrix to iterate through,
343             # based on whether its 'upper', 'lower', or 'both'
344 0 0       0 if ( $triangle =~ /^both$/i ) {
    0          
    0          
345 0         0 ( $first_col, $last_col ) = ( 0, scalar @taxlabels );
346             }
347             elsif ( $triangle =~ /^lower$/i ) {
348 0         0 ( $first_col, $last_col ) = ( 0, $r );
349 0 0       0 $last_col++ if $diagonal;
350             }
351             elsif ( $triangle =~ /^upper$/i ) {
352 0         0 ( $first_col, $last_col ) = ( $r, scalar @taxlabels );
353 0 0       0 $first_col++ unless $diagonal;
354             }
355              
356 0         0 for ( my $c = $first_col; $c < $last_col; $c++ ) {
357 0         0 my $col_taxon = $taxlabels[$c];
358 0         0 print $fh "\t" . $matrix->{$row_taxon}{$col_taxon};
359             }
360 0         0 print $fh "\n";
361             }
362 0         0 print $fh "\t;\n";
363             }
364              
365             sub AUTOLOAD {
366 1 50   1   5 return if $AUTOLOAD =~ /DESTROY$/;
367 1         2 my $package_name = __PACKAGE__ . '::';
368              
369             # The following methods are deprecated and are temporarily supported
370             # via a warning and a redirection
371 1         3 my %synonym_for = (
372              
373             # "${package_name}parse" => "${package_name}_parse_tree", # example
374             );
375              
376 1 50       4 if ( defined $synonym_for{$AUTOLOAD} ) {
377 0         0 $logger->warn("$AUTOLOAD() is deprecated; use $synonym_for{$AUTOLOAD}() instead");
378 0         0 goto &{ $synonym_for{$AUTOLOAD} };
  0         0  
379             }
380             else {
381 1         7 Bio::NEXUS::Util::Exceptions::UnknownMethod->throw(
382             'error' => "ERROR: Unknown method $AUTOLOAD called"
383             );
384             }
385             }
386              
387             1;