File Coverage

blib/lib/Bio/Phylo/Matrices/MatrixRole.pm
Criterion Covered Total %
statement 375 1244 30.1
branch 79 398 19.8
condition 13 120 10.8
subroutine 40 109 36.7
pod 33 80 41.2
total 540 1951 27.6


line stmt bran cond sub pod time code
1             package Bio::Phylo::Matrices::MatrixRole;
2 13     13   83 use strict;
  13         24  
  13         337  
3 13     13   770 use Data::Dumper;
  13         10030  
  13         637  
4 13     13   72 use base qw'Bio::Phylo::Matrices::TypeSafeData Bio::Phylo::Taxa::TaxaLinker';
  13         22  
  13         3753  
5 13     13   3610 use Bio::Phylo::Models::Substitution::Binary;
  13         31  
  13         373  
6 13     13   4146 use Bio::Phylo::Models::Substitution::Dna;
  13         34  
  13         1187  
7 13     13   2782 use Bio::Phylo::Util::OptionalInterface 'Bio::Align::AlignI';
  13         27  
  13         100  
8 13     13   78 use Bio::Phylo::Util::CONSTANT qw':objecttypes /looks_like/';
  13         23  
  13         2997  
9 13     13   82 use Bio::Phylo::Util::Exceptions qw'throw';
  13         25  
  13         490  
10 13     13   71 use Bio::Phylo::NeXML::Writable;
  13         25  
  13         112  
11 13     13   3025 use Bio::Phylo::Matrices::Datum;
  13         26  
  13         73  
12 13     13   74 use Bio::Phylo::IO qw(parse unparse);
  13         26  
  13         572  
13 13     13   72 use Bio::Phylo::Factory;
  13         27  
  13         50  
14             my $LOADED_WRAPPERS = 0;
15             {
16             my $CONSTANT_TYPE = _MATRIX_;
17             my $CONSTANT_CONTAINER = _MATRICES_;
18             my $logger = __PACKAGE__->get_logger;
19             my $factory = Bio::Phylo::Factory->new;
20              
21             =head1 NAME
22              
23             Bio::Phylo::Matrices::MatrixRole - Extra behaviours for a character state matrix
24              
25             =head1 SYNOPSIS
26              
27             use Bio::Phylo::Factory;
28             my $fac = Bio::Phylo::Factory->new;
29              
30             # instantiate taxa object
31             my $taxa = $fac->create_taxa;
32             for ( 'Homo sapiens', 'Pan paniscus', 'Pan troglodytes' ) {
33             $taxa->insert( $fac->create_taxon( '-name' => $_ ) );
34             }
35              
36             # instantiate matrix object, 'standard' data type. All categorical
37             # data types follow semantics like this, though with different
38             # symbols in lookup table and matrix
39             my $standard_matrix = $fac->create_matrix(
40             '-type' => 'STANDARD',
41             '-taxa' => $taxa,
42             '-lookup' => {
43             '-' => [],
44             '0' => [ '0' ],
45             '1' => [ '1' ],
46             '?' => [ '0', '1' ],
47             },
48             '-labels' => [ 'Opposable big toes', 'Opposable thumbs', 'Not a pygmy' ],
49             '-matrix' => [
50             [ 'Homo sapiens' => '0', '1', '1' ],
51             [ 'Pan paniscus' => '1', '1', '0' ],
52             [ 'Pan troglodytes' => '1', '1', '1' ],
53             ],
54             );
55              
56             # note: complicated constructor for mixed data!
57             my $mixed_matrix = Bio::Phylo::Matrices::Matrix->new(
58              
59             # if you want to create 'mixed', value for '-type' is array ref...
60             '-type' => [
61              
62             # ...with first field 'mixed'...
63             'mixed',
64              
65             # ...second field is an array ref...
66             [
67              
68             # ...with _ordered_ key/value pairs...
69             'dna' => 10, # value is length of type range
70             'standard' => 10, # value is length of type range
71              
72             # ... or, more complicated, value is a hash ref...
73             'rna' => {
74             '-length' => 10, # value is length of type range
75              
76             # ...value for '-args' is an array ref with args
77             # as can be passed to 'unmixed' datatype constructors,
78             # for example, here we modify the lookup table for
79             # rna to allow both 'U' (default) and 'T'
80             '-args' => [
81             '-lookup' => {
82             'A' => [ 'A' ],
83             'C' => [ 'C' ],
84             'G' => [ 'G' ],
85             'U' => [ 'U' ],
86             'T' => [ 'T' ],
87             'M' => [ 'A', 'C' ],
88             'R' => [ 'A', 'G' ],
89             'S' => [ 'C', 'G' ],
90             'W' => [ 'A', 'U', 'T' ],
91             'Y' => [ 'C', 'U', 'T' ],
92             'K' => [ 'G', 'U', 'T' ],
93             'V' => [ 'A', 'C', 'G' ],
94             'H' => [ 'A', 'C', 'U', 'T' ],
95             'D' => [ 'A', 'G', 'U', 'T' ],
96             'B' => [ 'C', 'G', 'U', 'T' ],
97             'X' => [ 'G', 'A', 'U', 'T', 'C' ],
98             'N' => [ 'G', 'A', 'U', 'T', 'C' ],
99             },
100             ],
101             },
102             ],
103             ],
104             );
105              
106             # prints 'mixed(Dna:1-10, Standard:11-20, Rna:21-30)'
107             print $mixed_matrix->get_type;
108              
109             =head1 DESCRIPTION
110              
111             This module defines a container object that holds
112             L<Bio::Phylo::Matrices::Datum> objects. The matrix
113             object inherits from L<Bio::Phylo::Listable>, so the
114             methods defined there apply here.
115              
116             =head1 METHODS
117              
118             =head2 CONSTRUCTOR
119              
120             =over
121              
122             =item new()
123              
124             Matrix constructor.
125              
126             Type : Constructor
127             Title : new
128             Usage : my $matrix = Bio::Phylo::Matrices::Matrix->new;
129             Function: Instantiates a Bio::Phylo::Matrices::Matrix
130             object.
131             Returns : A Bio::Phylo::Matrices::Matrix object.
132             Args : -type => optional, but if used must be FIRST argument,
133             defines datatype, one of dna|rna|protein|
134             continuous|standard|restriction|[ mixed => [] ]
135              
136             -taxa => optional, link to taxa object
137             -lookup => character state lookup hash ref
138             -labels => array ref of character labels
139             -matrix => two-dimensional array, first element of every
140             row is label, subsequent are characters
141              
142             =cut
143              
144             sub new : Constructor {
145              
146             # could be child class
147 38     38 1 1342 my $class = shift;
148              
149             # notify user
150 38         226 $logger->info("constructor called for '$class'");
151 38 100       121 if ( not $LOADED_WRAPPERS ) {
152 11 0 0 0 0 23 eval do { local $/; <DATA> };
  11 0 0 0 0 49  
  11 0 0 0 0 33695  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0 0 0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0   0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
153 11 50       57 die $@ if $@;
154 11         28 $LOADED_WRAPPERS++;
155             }
156              
157             # go up inheritance tree, eventually get an ID
158 38         341 my $self = $class->SUPER::new(
159             '-characters' => $factory->create_characters,
160             '-listener' => \&_update_characters,
161             @_
162             );
163 38         149 return $self;
164 13     13   79 }
  13         29  
  13         73  
165              
166             =item new_from_bioperl()
167              
168             Matrix constructor from Bio::Align::AlignI argument.
169              
170             Type : Constructor
171             Title : new_from_bioperl
172             Usage : my $matrix =
173             Bio::Phylo::Matrices::Matrix->new_from_bioperl(
174             $aln
175             );
176             Function: Instantiates a
177             Bio::Phylo::Matrices::Matrix object.
178             Returns : A Bio::Phylo::Matrices::Matrix object.
179             Args : An alignment that implements Bio::Align::AlignI
180              
181             =cut
182              
183             sub new_from_bioperl {
184 0     0 1 0 my ( $class, $aln, @args ) = @_;
185 0 0       0 if ( looks_like_instance( $aln, 'Bio::Align::AlignI' ) ) {
186 0         0 $aln->unmatch;
187 0         0 $aln->map_chars( '\.', '-' );
188 0         0 my @seqs = $aln->each_seq;
189 0         0 my ( $type, $missing, $gap, $matchchar );
190 0 0       0 if ( $seqs[0] ) {
191 0   0     0 $type =
192             $seqs[0]->alphabet
193             || $seqs[0]->_guess_alphabet
194             || 'dna';
195             }
196             else {
197 0         0 $type = 'dna';
198             }
199 0   0     0 my $self = $factory->create_matrix(
      0        
      0        
200             '-type' => $type,
201             '-special_symbols' => {
202             '-missing' => $aln->missing_char || '?',
203             '-matchchar' => $aln->match_char || '.',
204             '-gap' => $aln->gap_char || '-',
205             },
206             @args
207             );
208              
209             # XXX create raw getter/setter pairs for annotation,
210             # accession, consensus_meta source
211 0         0 for my $field ( qw(description accession id annotation consensus_meta score source) ) {
212 0         0 $self->$field( $aln->$field );
213             }
214 0         0 my $to = $self->get_type_object;
215 0         0 for my $seq (@seqs) {
216 0         0 my $datum = Bio::Phylo::Matrices::Datum->new_from_bioperl( $seq,
217             '-type_object' => $to );
218 0         0 $self->insert($datum);
219             }
220 0         0 return $self;
221             }
222             else {
223 0         0 throw 'ObjectMismatch' => 'Not a bioperl alignment!';
224             }
225             }
226              
227             =back
228              
229             =head2 MUTATORS
230              
231             =over
232              
233             =item set_special_symbols
234              
235             Sets three special symbols in one call
236              
237             Type : Mutator
238             Title : set_special_symbols
239             Usage : $matrix->set_special_symbols(
240             -missing => '?',
241             -gap => '-',
242             -matchchar => '.'
243             );
244             Function: Assigns state labels.
245             Returns : $self
246             Args : Three args (with distinct $x, $y and $z):
247             -missing => $x,
248             -gap => $y,
249             -matchchar => $z
250             Notes : This method is here to ensure
251             you don't accidentally use the
252             same symbol for missing AND gap
253              
254             =cut
255              
256             sub set_special_symbols {
257 0     0 1 0 my $self = shift;
258 0         0 my %args;
259 0 0 0     0 if ( ( @_ == 1 && ref( $_[0] ) eq 'HASH' && ( %args = %{ $_[0] } ) )
  0   0     0  
      0        
260             || ( %args = looks_like_hash @_ ) )
261             {
262 0 0 0     0 if ( !defined $args{'-missing'}
      0        
263             || !defined $args{'-gap'}
264             || !defined $args{'-matchchar'} )
265             {
266 0         0 throw 'BadArgs' =>
267             'Need -missing => $x, -gap => $y, -matchchar => $z arguments, not '
268             . "@_";
269             }
270 0         0 my %values = map { $_ => 1 } values %args;
  0         0  
271 0         0 my @values = keys %values;
272 0 0       0 if ( scalar @values < 3 ) {
273 0         0 throw 'BadArgs' => 'Symbols must be distinct, not '
274             . join( ', ', values %args );
275             }
276 0   0     0 my %old_special_symbols = (
      0        
      0        
277             ( $self->get_missing || '?' ) => 'set_missing',
278             ( $self->get_gap || '-' ) => 'set_gap',
279             ( $self->get_matchchar || '.' ) => 'set_matchchar',
280             );
281             my %new_special_symbols = (
282             $args{'-missing'} => 'set_missing',
283             $args{'-gap'} => 'set_gap',
284 0         0 $args{'-matchchar'} => 'set_matchchar',
285             );
286 0         0 my %dummies;
287 0         0 while (%new_special_symbols) {
288 0         0 for my $sym ( keys %new_special_symbols ) {
289 0 0       0 if ( not $old_special_symbols{$sym} ) {
    0          
290 0         0 my $method = $new_special_symbols{$sym};
291 0         0 $self->$method($sym);
292 0         0 delete $new_special_symbols{$sym};
293             }
294             elsif ( $old_special_symbols{$sym} eq
295             $new_special_symbols{$sym} )
296             {
297 0         0 delete $new_special_symbols{$sym};
298             }
299             else {
300 0         0 DUMMY: for my $dummy (qw(! @ $ % ^ & *)) {
301 0 0 0     0 if ( !$new_special_symbols{$dummy}
      0        
302             && !$old_special_symbols{$dummy}
303             && !$dummies{$dummy} )
304             {
305 0         0 my $method = $old_special_symbols{$sym};
306 0         0 $self->$method($dummy);
307 0         0 $dummies{$dummy} = 1;
308 0         0 delete $old_special_symbols{$sym};
309 0         0 $old_special_symbols{$dummy} = $method;
310 0         0 last DUMMY;
311             }
312             }
313             }
314             }
315             }
316             }
317 0         0 return $self;
318             }
319              
320             =item set_charlabels()
321              
322             Sets argument character labels.
323              
324             Type : Mutator
325             Title : set_charlabels
326             Usage : $matrix->set_charlabels( [ 'char1', 'char2', 'char3' ] );
327             Function: Assigns character labels.
328             Returns : $self
329             Args : ARRAY, or nothing (to reset);
330              
331             =cut
332              
333             sub set_charlabels {
334 4     4 1 10 my ( $self, $charlabels ) = @_;
335              
336             # it's an array ref, but what about its contents?
337 4 50 0     16 if ( looks_like_instance( $charlabels, 'ARRAY' ) ) {
    0          
338 4         9 for my $label ( @{$charlabels} ) {
  4         12  
339 15 50       30 if ( ref $label ) {
340 0         0 throw 'BadArgs' =>
341             "charlabels must be an array ref of scalars";
342             }
343             }
344             }
345              
346             # it's defined but not an array ref
347             elsif ( defined $charlabels
348             && !looks_like_instance( $charlabels, 'ARRAY' ) )
349             {
350 0         0 throw 'BadArgs' => "charlabels must be an array ref of scalars";
351             }
352              
353             # there might be more labels than currently existing characters.
354             # here we add however more are needed
355 4         15 my $characters = $self->get_characters;
356 4         7 my $missing = scalar(@{$charlabels}) - scalar(@{$characters->get_entities});
  4         9  
  4         14  
357 4         13 for my $i ( 1 .. $missing ) {
358 4         22 $characters->insert($factory->create_character);
359             }
360              
361             # it's either a valid array ref, or nothing, i.e. a reset
362 4         8 my @characters = @{ $characters->get_entities };
  4         10  
363 4         8 for my $i ( 0 .. $#{$charlabels} ) {
  4         11  
364 15         45 $characters[$i]->set_name( $charlabels->[$i] );
365             }
366 4         10 return $self;
367             }
368              
369             =item set_raw()
370              
371             Set contents using two-dimensional array argument.
372              
373             Type : Mutator
374             Title : set_raw
375             Usage : $matrix->set_raw( [ [ 'taxon1' => 'acgt' ], [ 'taxon2' => 'acgt' ] ] );
376             Function: Syntax sugar to define $matrix data contents.
377             Returns : $self
378             Args : A two-dimensional array; first dimension contains matrix rows,
379             second dimension contains taxon name / character string pair.
380              
381             =cut
382              
383             sub set_raw {
384 12     12 1 31 my ( $self, $raw ) = @_;
385 12 50       32 if ( defined $raw ) {
386 12 50       34 if ( looks_like_instance( $raw, 'ARRAY' ) ) {
387 12         20 my @rows;
388 12         22 for my $row ( @{$raw} ) {
  12         36  
389 46 50       91 if ( defined $row ) {
390 46 50       99 if ( looks_like_instance( $row, 'ARRAY' ) ) {
391             my $matrixrow = $factory->create_datum(
392             '-type_object' => $self->get_type_object,
393             '-name' => $row->[0],
394 46         124 '-char' => join( ' ', @$row[ 1 .. $#{$row} ] ),
  46         357  
395             );
396 46         138 push @rows, $matrixrow;
397             }
398             else {
399 0         0 throw 'BadArgs' =>
400             "Raw matrix row must be an array reference";
401             }
402             }
403             }
404 12         67 $self->clear;
405 12         61 $self->insert($_) for @rows;
406             }
407             else {
408 0         0 throw 'BadArgs' => "Raw matrix must be an array reference";
409             }
410             }
411 12         29 return $self;
412             }
413              
414             =back
415              
416             =head2 ACCESSORS
417              
418             =over
419              
420             =item get_special_symbols()
421              
422             Retrieves hash ref for missing, gap and matchchar symbols
423              
424             Type : Accessor
425             Title : get_special_symbols
426             Usage : my %syms = %{ $matrix->get_special_symbols };
427             Function: Retrieves special symbols
428             Returns : HASH ref, e.g. { -missing => '?', -gap => '-', -matchchar => '.' }
429             Args : None.
430              
431             =cut
432              
433             sub get_special_symbols {
434 0     0 1 0 my $self = shift;
435             return {
436 0         0 '-missing' => $self->get_missing,
437             '-matchchar' => $self->get_matchchar,
438             '-gap' => $self->get_gap
439             };
440             }
441              
442             =item get_charlabels()
443              
444             Retrieves character labels.
445              
446             Type : Accessor
447             Title : get_charlabels
448             Usage : my @charlabels = @{ $matrix->get_charlabels };
449             Function: Retrieves character labels.
450             Returns : ARRAY
451             Args : None.
452              
453             =cut
454              
455             sub get_charlabels {
456 4     4 1 39 my $self = shift;
457             my @labels =
458 4         5 map { $_->get_name } @{ $self->get_characters->get_entities };
  9         27  
  4         14  
459 4         15 return \@labels;
460             }
461              
462             =item get_nchar()
463              
464             Calculates number of characters.
465              
466             Type : Accessor
467             Title : get_nchar
468             Usage : my $nchar = $matrix->get_nchar;
469             Function: Calculates number of characters (columns) in matrix (if the matrix
470             is non-rectangular, returns the length of the longest row).
471             Returns : INT
472             Args : none
473              
474             =cut
475              
476             sub get_nchar {
477 214     214 1 334 my $self = shift;
478 214         294 my $nchar = 0;
479 214         272 for my $row ( @{ $self->get_entities } ) {
  214         424  
480 639   50     1221 my $offset = ( $row->get_position || 1 ) - 1;
481 639         845 my $rowlength = scalar @{ $row->get_entities };
  639         1039  
482 639         843 $rowlength += $offset;
483 639 100       1206 $nchar = $rowlength if $rowlength > $nchar;
484             }
485 214         486 return $nchar;
486             }
487              
488             =item get_ntax()
489              
490             Calculates number of taxa (rows) in matrix.
491              
492             Type : Accessor
493             Title : get_ntax
494             Usage : my $ntax = $matrix->get_ntax;
495             Function: Calculates number of taxa (rows) in matrix
496             Returns : INT
497             Args : none
498              
499             =cut
500              
501 16     16 1 631 sub get_ntax { scalar @{ shift->get_entities } }
  16         39  
502              
503             =item get_raw()
504              
505             Retrieves a 'raw' (two-dimensional array) representation of the matrix's contents.
506              
507             Type : Accessor
508             Title : get_raw
509             Usage : my $rawmatrix = $matrix->get_raw;
510             Function: Retrieves a 'raw' (two-dimensional array) representation
511             of the matrix's contents.
512             Returns : A two-dimensional array; first dimension contains matrix rows,
513             second dimension contains taxon name and characters.
514             Args : NONE
515              
516             =cut
517              
518             sub get_raw {
519 5     5 1 9 my $self = shift;
520 5         9 my @raw;
521 5         6 for my $row ( @{ $self->get_entities } ) {
  5         16  
522 22         32 my @row;
523 22         43 push @row, $row->get_name;
524 22         48 my @char = $row->get_char;
525 22         106 push @row, @char;
526 22         63 push @raw, \@row;
527             }
528 5         13 return \@raw;
529             }
530              
531             =item get_ungapped_columns()
532              
533             Type : Accessor
534             Title : get_ungapped_columns
535             Usage : my @ungapped = @{ $matrix->get_ungapped_columns };
536             Function: Retrieves the zero-based column indices of columns without gaps
537             Returns : An array reference with zero or more indices (i.e. integers)
538             Args : NONE
539              
540             =cut
541              
542             sub get_ungapped_columns {
543 1     1 1 31 my $self = shift;
544 1         6 my $raw = $self->get_raw;
545 1         3 my $nchar = $self->get_nchar;
546 1         2 my @ungapped;
547 1         7 my $gap = $self->get_gap;
548 1         10 for my $i ( 1 .. $nchar ) {
549 29         32 my %seen;
550 29         35 for my $row ( @$raw ) {
551 116         129 my $c = $row->[$i];
552 116         147 $seen{$c}++;
553             }
554 29 100       55 push @ungapped, $i - 1 unless $seen{$gap};
555             }
556 1         10 return \@ungapped;
557             }
558              
559             =item get_invariant_columns()
560              
561             Type : Accessor
562             Title : get_invariant_columns
563             Usage : my @invariant = @{ $matrix->get_invariant_columns };
564             Function: Retrieves the zero-based column indices of invariant columns
565             Returns : An array reference with zero or more indices (i.e. integers)
566             Args : Optional:
567             -gap => if true, counts the gap symbol (probably '-') as a variant
568             -missing => if true, counts the missing symbol (probably '?') as a variant
569              
570             =cut
571              
572             sub get_invariant_columns {
573 2     2 1 1444 my ( $self, %args ) = @_;
574 2         6 my $raw = $self->get_raw;
575 2         6 my $nchar = $self->get_nchar;
576 2         6 my $gap = $self->get_gap;
577 2         10 my $miss = $self->get_missing;
578 2         5 my @invariant;
579 2         5 for my $i ( 1 .. $nchar ) {
580 58         64 my %seen;
581 58         70 for my $row ( @$raw ) {
582 232         274 my $c = uc $row->[$i];
583 232         294 $seen{$c}++;
584             }
585 58         89 my @states = keys %seen;
586 58 100       91 @states = grep { $_ ne $gap } @states unless $args{'-gap'};
  45         81  
587 58 50       99 @states = grep { $_ ne $miss } @states unless $args{'-missing'};
  80         133  
588 58 100 100     186 push @invariant, $i - 1 if @states and @states == 1;
589             }
590 2         17 return \@invariant;
591             }
592              
593              
594             =back
595              
596             =head2 CALCULATIONS
597              
598             =over
599              
600             =item calc_indel_sizes()
601              
602             Calculates size distribution of insertions or deletions
603              
604             Type : Calculation
605             Title : calc_indel_sizes
606             Usage : my %sizes = %{ $matrix->calc_indel_sizes };
607             Function: Calculates the size distribution of indels.
608             Returns : HASH
609             Args : Optional:
610             -trim => if true, disregards indels at start and end
611             -insertions => if true, counts insertions, if false, counts deletions
612              
613             =cut
614              
615             sub calc_indel_sizes {
616 0     0 1 0 my ($self,%args) = @_;
617 0         0 my $ntax = $self->get_ntax;
618 0         0 my $nchar = $self->get_nchar;
619 0         0 my $raw = $self->get_raw;
620 0         0 my $gap = $self->get_gap;
621 0         0 my @indels;
622              
623             # iterate over rows
624 0         0 for my $row ( @$raw ) {
625 0         0 my $previous;
626             my @row_indels;
627              
628             # iterate over columns
629 0         0 for my $i ( 1 .. $nchar ) {
630              
631             # focal cell is a gap
632 0 0       0 if ( $row->[$i] eq $gap ) {
633 0 0       0 if ( $previous ) {
634              
635             # we're extending an indel
636 0 0       0 if ( $previous eq $gap ) {
637 0         0 $row_indels[-1]->{'end'} = $i;
638             }
639              
640             # we're starting an indel
641             else {
642 0         0 push @row_indels, { 'start' => $i };
643             }
644             }
645              
646             # sequence starts with an indel
647             else {
648 0         0 push @row_indels, { 'start' => $i };
649             }
650             }
651             else {
652             # gap of length 1 is closed: start==end
653 0 0 0     0 if ( $previous and $previous eq $gap ){
654 0         0 $row_indels[-1]->{'end'} = $i;
655             }
656             }
657 0         0 $previous = $row->[$i];
658             }
659              
660             # flatten the lists
661 0         0 push @indels, @row_indels;
662             }
663              
664             # remove tops and tails
665 0 0       0 if ( $args{'-trim'} ) {
666 0         0 @indels = grep { $_->{'start'} > 1 } @indels;
  0         0  
667 0         0 @indels = grep { $_->{'end'} < $nchar } @indels;
  0         0  
668             }
669              
670             # count sizes
671 0         0 my %sizes;
672 0         0 for my $i ( 1 .. $nchar ) {
673 0         0 my @starting_here = grep { $_->{'start'} == $i } @indels;
  0         0  
674 0 0       0 if ( @starting_here ) {
675 0         0 for my $j ( $i + 1 .. $nchar ) {
676 0         0 my @ending_here = grep { $_->{'end'} == $j } @starting_here;
  0         0  
677 0 0       0 if ( @ending_here ) {
678 0         0 my $size = $j - $i;
679              
680             # more taxa have an indel here than don't. crudely, we
681             # say this is probably an insertion
682 0 0       0 if ( scalar(@ending_here) > ( $ntax / 2 ) ) {
683 0 0       0 $sizes{$size}++ if $args{'-insertions'};
684             }
685             # it's a deletion
686             else {
687 0 0       0 $sizes{$size}++ if not $args{'-insertions'};
688             }
689             }
690             }
691             }
692             }
693 0         0 return \%sizes;
694             }
695              
696              
697             =item calc_prop_invar()
698              
699             Calculates proportion of invariant sites.
700              
701             Type : Calculation
702             Title : calc_prop_invar
703             Usage : my $pinvar = $matrix->calc_prop_invar;
704             Function: Calculates proportion of invariant sites.
705             Returns : Scalar: a number
706             Args : Optional:
707             # if true, counts missing (usually the '?' symbol) as a state
708             # in the final tallies. Otherwise, missing states are ignored
709             -missing => 1
710             # if true, counts gaps (usually the '-' symbol) as a state
711             # in the final tallies. Otherwise, gap states are ignored
712             -gap => 1
713              
714             =cut
715              
716             sub calc_prop_invar {
717 1     1 1 1743 my $self = shift;
718 1         7 my $raw = $self->get_raw;
719 1         4 my $ntax = $self->get_ntax;
720 1   50     3 my $nchar = $self->get_nchar || 1;
721 1         4 my %args = looks_like_hash @_;
722 1         2 my @symbols_to_ignore;
723 1         3 for my $sym (qw(missing gap)) {
724 2 50       7 if ( not exists $args{"-${sym}"} ) {
725 2         4 my $method = "get_${sym}";
726 2         9 push @symbols_to_ignore, $self->$method;
727             }
728             }
729 1         2 my $invar_count;
730 1         3 for my $i ( 1 .. $nchar ) {
731 4         8 my %seen;
732 4         7 for my $j ( 0 .. ( $ntax - 1 ) ) {
733 12         19 $seen{ $raw->[$j]->[$i] }++;
734             }
735 4         7 delete @seen{@symbols_to_ignore};
736 4         7 my @symbols_in_column = keys %seen;
737 4 100       10 $invar_count++ if scalar(@symbols_in_column) <= 1;
738             }
739 1         7 return $invar_count / $nchar;
740             }
741              
742             =item calc_state_counts()
743              
744             Calculates occurrences of states.
745              
746             Type : Calculation
747             Title : calc_state_counts
748             Usage : my %counts = %{ $matrix->calc_state_counts };
749             Function: Calculates occurrences of states.
750             Returns : Hashref: keys are states, values are counts
751             Args : Optional - one or more states to focus on
752              
753             =cut
754              
755             sub calc_state_counts {
756 4     4 1 1813 my $self = shift;
757 4         9 my %totals;
758 4         7 for my $row ( @{ $self->get_entities } ) {
  4         12  
759 18         45 my $counts = $row->calc_state_counts(@_);
760 18         23 for my $state ( keys %{$counts} ) {
  18         41  
761 60         103 $totals{$state} += $counts->{$state};
762             }
763             }
764 4         13 return \%totals;
765             }
766              
767             =item calc_state_frequencies()
768              
769             Calculates the frequencies of the states observed in the matrix.
770              
771             Type : Calculation
772             Title : calc_state_frequencies
773             Usage : my %freq = %{ $object->calc_state_frequencies() };
774             Function: Calculates state frequencies
775             Returns : A hash, keys are state symbols, values are frequencies
776             Args : Optional:
777             # if true, counts missing (usually the '?' symbol) as a state
778             # in the final tallies. Otherwise, missing states are ignored
779             -missing => 1
780             # if true, counts gaps (usually the '-' symbol) as a state
781             # in the final tallies. Otherwise, gap states are ignored
782             -gap => 1
783             Comments: Throws exception if matrix holds continuous values
784              
785             =cut
786              
787             sub calc_state_frequencies {
788 2     2 1 5 my $self = shift;
789 2         4 my %result;
790 2         4 for my $row ( @{ $self->get_entities } ) {
  2         7  
791 5         19 my $freqs = $row->calc_state_frequencies(@_);
792 5         9 for my $state ( keys %{$freqs} ) {
  5         10  
793 20         36 $result{$state} += $freqs->{$state};
794             }
795             }
796 2         5 my $total = 0;
797 2         9 $total += $_ for values %result;
798 2 50       13 if ( $total > 0 ) {
799 2         6 for my $state ( keys %result ) {
800 8         11 $result{$state} /= $total;
801             }
802             }
803 2         7 return \%result;
804             }
805              
806             =item calc_distinct_site_patterns()
807              
808             Identifies the distinct distributions of states for all characters and
809             counts their occurrences. Returns an array-of-arrays, where the first cell
810             of each inner array holds the occurrence count, the second cell holds the
811             pattern, i.e. an array of states. For example, for a matrix like this:
812              
813             taxon1 GTGTGTGTGTGTGTGTGTGTGTG
814             taxon2 AGAGAGAGAGAGAGAGAGAGAGA
815             taxon3 TCTCTCTCTCTCTCTCTCTCTCT
816             taxon4 TCTCTCTCTCTCTCTCTCTCTCT
817             taxon5 AAAAAAAAAAAAAAAAAAAAAAA
818             taxon6 CGCGCGCGCGCGCGCGCGCGCGC
819             taxon7 AAAAAAAAAAAAAAAAAAAAAAA
820              
821             The following data structure will be returned:
822              
823             [
824             [ 12, [ 'G', 'A', 'T', 'T', 'A', 'C', 'A' ] ],
825             [ 11, [ 'T', 'G', 'C', 'C', 'A', 'G', 'A' ] ]
826             ]
827              
828             The patterns are sorted from most to least frequently occurring, the states
829             for each pattern are in the order of the rows in the matrix. (In other words,
830             the original matrix can more or less be reconstructed by inverting the patterns,
831             and multiplying them by their occurrence, although the order of the columns
832             will be lost.)
833              
834             Type : Calculation
835             Title : calc_distinct_site_patterns
836             Usage : my $patterns = $object->calc_distinct_site_patterns;
837             Function: Calculates distinct site patterns.
838             Returns : A multidimensional array, see above.
839             Args : NONE
840             Comments:
841              
842             =cut
843              
844             sub calc_distinct_site_patterns {
845 1     1 1 40 my ( $self, $indices ) = @_;
846 1         5 my $raw = $self->get_raw;
847 1         4 my $nchar = $self->get_nchar;
848 1         5 my $ntax = $self->get_ntax;
849 1         3 my ( %pattern, %index );
850 1         4 for my $i ( 1 .. $nchar ) {
851 23         27 my @column;
852 23         36 for my $j ( 0 .. ( $ntax - 1 ) ) {
853 161         230 push @column, $raw->[$j]->[$i];
854             }
855 23         33 my $col_pattern = join ' ', @column;
856 23         33 $pattern{$col_pattern}++;
857 23 100       63 $index{$col_pattern} = [] if not $index{$col_pattern};
858 23         26 push @{ $index{$col_pattern} }, $i - 1;
  23         46  
859             }
860 1         2 my @pattern_array;
861 1         5 for my $key ( keys %pattern ) {
862 2         13 my @column = split / /, $key;
863 2 50       6 if ( $indices ) {
864 0         0 push @pattern_array, [ $pattern{$key}, \@column, $index{$key} ];
865             }
866             else {
867 2         6 push @pattern_array, [ $pattern{$key}, \@column ];
868             }
869             }
870 1         7 return [ sort { $b->[0] <=> $a->[0] } @pattern_array ];
  1         19  
871             }
872              
873             =item calc_gc_content()
874              
875             Calculates the G+C content as a fraction on the total
876              
877             Type : Calculation
878             Title : calc_gc_content
879             Usage : my $fraction = $obj->calc_gc_content;
880             Function: Calculates G+C content
881             Returns : A number between 0 and 1 (inclusive)
882             Args : Optional:
883             # if true, counts missing (usually the '?' symbol) as a state
884             # in the final tallies. Otherwise, missing states are ignored
885             -missing => 1
886             # if true, counts gaps (usually the '-' symbol) as a state
887             # in the final tallies. Otherwise, gap states are ignored
888             -gap => 1
889             Comments: Throws 'BadArgs' exception if matrix holds anything other than DNA
890             or RNA. The calculation also takes the IUPAC symbol S (which is C|G)
891             into account, but no other symbols (such as V, for A|C|G);
892              
893             =cut
894              
895             sub calc_gc_content {
896 1     1 1 13 my $self = shift;
897 1         4 my $type = $self->get_type;
898 1 50       8 if ( $type !~ /^(?:d|r)na/i ) {
899 0         0 throw 'BadArgs' => "Matrix doesn't contain nucleotides";
900             }
901 1         5 my $freq = $self->calc_state_frequencies;
902 1         2 my $total = 0;
903 1         2 for (qw(c C g G s S)) {
904 6 100       14 $total += $freq->{$_} if exists $freq->{$_};
905             }
906 1         22 return $total;
907             }
908              
909             =item calc_median_sequence()
910              
911             Calculates the median character sequence of the matrix
912              
913             Type : Calculation
914             Title : calc_median_sequence
915             Usage : my $seq = $obj->calc_median_sequence;
916             Function: Calculates median sequence
917             Returns : Array in list context, string in scalar context
918             Args : Optional:
919             -ambig => if true, uses ambiguity codes to summarize equally frequent
920             states for a given character. Otherwise picks a random one.
921             -missing => if true, keeps the missing symbol (probably '?') if this
922             is the most frequent for a given character. Otherwise strips it.
923             -gaps => if true, keeps the gap symbol (probably '-') if this is the most
924             frequent for a given character. Otherwise strips it.
925             Comments: The intent of this method is to provide a crude approximation of the most
926             commonly occurring sequences in an alignment, for example as a starting
927             sequence for a sequence simulator. This gives you something to work with if
928             ancestral sequence calculation is too computationally intensive and/or not
929             really necessary.
930              
931             =cut
932              
933             sub calc_median_sequence {
934 0     0 1 0 my ( $self, %args ) = @_;
935 0         0 my $to = $self->get_type_object;
936 0         0 my $type = $self->get_type;
937 0 0       0 if ( $type =~ /continuous/i ) {
938 0         0 throw 'BadArgs' => 'No median sequence calculation for continuous data (yet)';
939             }
940             else {
941 0         0 my $raw = $self->get_raw;
942 0         0 my $ntax = $self->get_ntax;
943 0         0 my $nchar = $self->get_nchar;
944 0         0 my $gap = $self->get_gap;
945 0         0 my $miss = $self->get_missing;
946 0         0 my @seq;
947 0         0 for my $i ( 1 .. $nchar ) {
948 0         0 my %seen;
949 0         0 for my $row ( @$raw ) {
950 0         0 my $c = uc $row->[$i];
951 0         0 $seen{$c}++;
952             }
953 0         0 my @sorted = sort { $seen{$b} <=> $seen{$a} } keys %seen;
  0         0  
954 0         0 my $max = $seen{$sorted[0]};
955 0         0 my @top;
956 0         0 my $j = 0;
957 0   0     0 while ( $sorted[$j] and $seen{$sorted[$j]} == $max ) {
958 0         0 push @top, $sorted[$j];
959 0         0 $j++;
960             }
961 0 0       0 if ( @top == 1 ) {
962 0         0 push @seq, @top;
963             }
964             else {
965 0 0       0 if ( $args{'-ambig'} ) {
966 0         0 push @seq, $to->get_symbol_for_states(@top);
967             }
968             else {
969 0         0 push @seq, shift @top;
970             }
971             }
972             }
973 0 0       0 if ( not $args{'-gaps'} ) {
974 0         0 @seq = grep { $_ ne $gap } @seq;
  0         0  
975             }
976 0 0       0 if ( not $args{'-missing'} ) {
977 0         0 @seq = grep { $_ ne $miss } @seq;
  0         0  
978             }
979 0 0       0 return wantarray ? @seq : join '', @seq;
980             }
981             }
982              
983             =back
984              
985             =head2 METHODS
986              
987             =over
988              
989             =item keep_chars()
990              
991             Creates a cloned matrix that only keeps the characters at
992             the supplied (zero-based) indices.
993              
994             Type : Utility method
995             Title : keep_chars
996             Usage : my $clone = $object->keep_chars([6,3,4,1]);
997             Function: Creates spliced clone.
998             Returns : A spliced clone of the invocant.
999             Args : Required, an array ref of integers
1000             Comments: The columns are retained in the order in
1001             which they were supplied.
1002              
1003             =cut
1004              
1005             sub keep_chars {
1006 4     4 1 19 my ( $self, $indices_array_ref ) = @_;
1007 4         8 my @indices = @{$indices_array_ref};
  4         11  
1008 4         24 my $clone = $self->clone;
1009 4         9 for my $seq ( @{ $clone->get_entities } ) {
  4         13  
1010 16         47 $seq->keep_entities( \@indices );
1011 16         19 my @anno = @{ $seq->get_annotations };
  16         33  
1012 16 50       35 if (@anno) {
1013 0         0 my @re_anno = @anno[@indices];
1014 0         0 $seq->set_annotations(@re_anno);
1015             }
1016             }
1017 4         12 $clone->get_characters->keep_entities(\@indices);
1018 4         29 return $clone;
1019             }
1020              
1021             =item prune_chars()
1022              
1023             Creates a cloned matrix that omits the characters at
1024             the supplied (zero-based) indices.
1025              
1026             Type : Utility method
1027             Title : prune_chars
1028             Usage : my $clone = $object->prune_chars([6,3,4,1]);
1029             Function: Creates spliced clone.
1030             Returns : A spliced clone of the invocant.
1031             Args : Required, an array ref of integers
1032             Comments: The columns are retained in the order in
1033             which they were supplied.
1034              
1035             =cut
1036              
1037             sub prune_chars {
1038 3     3 1 19 my ( $self, $indices ) = @_;
1039 3         8 my $nchar = $self->get_nchar;
1040 3         5 my %indices = map { $_ => 1 } @{$indices};
  7         18  
  3         6  
1041 3         6 my @keep;
1042 3         9 for my $i ( 0 .. ( $nchar - 1 ) ) {
1043 13 100       30 push @keep, $i if not exists $indices{$i};
1044             }
1045 3         13 return $self->keep_chars( \@keep );
1046             }
1047              
1048             =item prune_invariant()
1049              
1050             Creates a cloned matrix that omits the characters for which all taxa
1051             have the same state (or missing);
1052              
1053             Type : Utility method
1054             Title : prune_invariant
1055             Usage : my $clone = $object->prune_invariant;
1056             Function: Creates spliced clone.
1057             Returns : A spliced clone of the invocant.
1058             Args : None
1059             Comments: The columns are retained in the order in
1060             which they were supplied.
1061              
1062             =cut
1063              
1064             sub prune_invariant {
1065 1     1 1 11 my $self = shift;
1066 1         3 my $nchar = $self->get_nchar;
1067 1         5 my $missing = $self->get_missing;
1068 1         3 my @indices;
1069 1         4 for my $i ( 0 .. ( $nchar - 1 ) ) {
1070 5         10 my %seen;
1071 5         6 ROW: for my $row ( @{ $self->get_entities } ) {
  5         8  
1072 25         47 my $state = $row->get_by_index($i);
1073 25 100       51 next ROW if $state eq $missing;
1074 14         25 $seen{ $state } = 1;
1075             }
1076 5         13 my @states = keys %seen;
1077 5 100       13 push @indices, $i if scalar(@states) <= 1;
1078             }
1079 1         4 return $self->prune_chars(\@indices);
1080             }
1081              
1082             =item prune_uninformative()
1083              
1084             Creates a cloned matrix that omits all uninformative characters. Uninformative
1085             are considered characters where all non-missing values are either invariant
1086             or autapomorphies.
1087              
1088             Type : Utility method
1089             Title : prune_uninformative
1090             Usage : my $clone = $object->prune_uninformative;
1091             Function: Creates spliced clone.
1092             Returns : A spliced clone of the invocant.
1093             Args : None
1094             Comments: The columns are retained in the order in
1095             which they were supplied.
1096              
1097             =cut
1098              
1099             sub prune_uninformative {
1100 1     1 1 17 my $self = shift;
1101 1         4 my $nchar = $self->get_nchar;
1102 1         5 my $ntax = $self->get_ntax;
1103 1         5 my $missing = $self->get_missing;
1104 1         2 my @indices;
1105 1         4 for my $i ( 0 .. ( $nchar - 1 ) ) {
1106 5         7 my %seen;
1107 5         7 for my $row ( @{ $self->get_entities } ) {
  5         10  
1108 25         55 my $state = $row->get_by_index($i);
1109 25         66 $seen{ $state }++;
1110             }
1111 5         12 my @states = keys %seen;
1112 5 100       9 push @indices, $i if scalar(@states) <= 1;
1113 5 100       14 if ( scalar(@states) > 1 ) {
1114 4         5 my $seen_informative;
1115 4   100     12 my $non_missing = $ntax - ( $seen{$missing} || 0 );
1116 4         6 my @informative_maybe;
1117 4         7 for my $state ( @states ) {
1118 10 100       24 if ( $seen{$state} == 1 ) {
1119 4         5 $non_missing--;
1120             }
1121             else {
1122 6         11 push @informative_maybe, $state;
1123             }
1124             }
1125 4         9 for my $state ( @informative_maybe ) {
1126 6 100       10 $seen_informative++ if $seen{$state} < $non_missing;
1127             }
1128 4 100       11 push @indices, $i unless $seen_informative;
1129             }
1130             }
1131 1         7 return $self->prune_chars(\@indices);
1132             }
1133              
1134             =item prune_missing_and_gaps()
1135              
1136             Creates a cloned matrix that omits all characters for which the invocant only
1137             has missing and/or gap states.
1138              
1139             Type : Utility method
1140             Title : prune_missing_and_gaps
1141             Usage : my $clone = $object->prune_missing_and_gaps;
1142             Function: Creates spliced clone.
1143             Returns : A spliced clone of the invocant.
1144             Args : None
1145             Comments: The columns are retained in the order in
1146             which they were supplied.
1147              
1148             =cut
1149              
1150             sub prune_missing_and_gaps {
1151 0     0 1 0 my $self = shift;
1152 0         0 my $nchar = $self->get_nchar;
1153 0         0 my $to = $self->get_type_object;
1154 0         0 my $m = $to->get_missing;
1155 0         0 my $g = $to->get_gap;
1156 0         0 my @indices;
1157 0         0 for my $i ( 0 .. ( $nchar - 1 ) ) {
1158 0         0 my %col;
1159             $self->visit(sub{
1160 0     0   0 my $row = shift;
1161 0         0 my $state = $row->get_by_index($i);
1162 0 0 0     0 if ( $state ne $m and $state ne $g ) {
1163 0         0 $col{$state} = 1;
1164             }
1165 0 0       0 push @indices, $i if not keys %col;
1166 0         0 });
1167             }
1168 0         0 return $self->prune_chars(\@indices);
1169             }
1170              
1171             =item bootstrap()
1172              
1173             Creates bootstrapped clone.
1174              
1175             Type : Utility method
1176             Title : bootstrap
1177             Usage : my $bootstrap = $object->bootstrap;
1178             Function: Creates bootstrapped clone.
1179             Returns : A bootstrapped clone of the invocant.
1180             Args : Optional, a subroutine reference that returns a random
1181             integer between 0 (inclusive) and the argument provided
1182             to it (exclusive). The default implementation is to use
1183             sub { int( rand( shift ) ) }, a user might override this
1184             by providing an implementation with a better random number
1185             generator.
1186             Comments: The bootstrapping algorithm uses perl's random number
1187             generator to create a new series of indices (without
1188             replacement) of the same length as the original matrix.
1189             These indices are first sorted, then applied to the
1190             cloned sequences. Annotations (if present) stay connected
1191             to the resampled cells.
1192              
1193             =cut
1194              
1195             sub bootstrap {
1196 0     0   0 my $self = shift;
1197 0   0 0   0 my $gen = shift || sub { int( rand(shift) ) };
  0         0  
1198 0         0 my $nchar = $self->get_nchar;
1199 0         0 my @indices;
1200 0         0 push @indices, $gen->($nchar) for ( 1 .. $nchar );
1201 0         0 @indices = sort { $a <=> $b } @indices;
  0         0  
1202 0         0 return $self->keep_chars( \@indices );
1203             }
1204              
1205             =item jackknife()
1206              
1207             Creates jackknifed clone.
1208              
1209             Type : Utility method
1210             Title : jackknife
1211             Usage : my $bootstrap = $object->jackknife(0.5);
1212             Function: Creates jackknifed clone.
1213             Returns : A jackknifed clone of the invocant.
1214             Args : * Required, a number between 0 and 1, representing the
1215             fraction of characters to jackknife.
1216             * Optional, a subroutine reference that returns a random
1217             integer between 0 (inclusive) and the argument provided
1218             to it (exclusive). The default implementation is to use
1219             sub { int( rand( shift ) ) }, a user might override this
1220             by providing an implementation with a better random number
1221             generator.
1222             Comments: The jackknife algorithm uses perl's random number
1223             generator to create a new series of indices of cells to keep.
1224             These indices are first sorted, then applied to the
1225             cloned sequences. Annotations (if present) stay connected
1226             to the resampled cells.
1227              
1228             =cut
1229              
1230             sub jackknife {
1231 0     0 1 0 my ( $self, $prop ) = @_;
1232 0 0 0     0 if ( not looks_like_number $prop or $prop >= 1 or $prop < 0 ) {
      0        
1233 0         0 throw 'BadNumber' =>
1234             "Jackknifing proportion must be a number between 0 and 1";
1235             }
1236 0   0 0   0 my $gen = $_[2] || sub { int( rand(shift) ) };
  0         0  
1237 0         0 my $nchar = $self->get_nchar;
1238 0         0 my ( %indices, @indices );
1239 0         0 while ( scalar keys %indices < ( $nchar - int( $nchar * $prop ) ) ) {
1240 0         0 $indices{ $gen->($nchar) } = 1;
1241             }
1242 0         0 @indices = sort { $a <=> $b } keys %indices;
  0         0  
1243 0         0 return $self->keep_chars( \@indices );
1244             }
1245              
1246             =item replicate()
1247              
1248             Creates simulated replicate.
1249              
1250             Type : Utility method
1251             Title : replicate
1252             Usage : my $replicate = $matrix->replicate($tree);
1253             Function: Creates simulated replicate.
1254             Returns : A simulated replicate of the invocant.
1255             Args : Tree to simulate the characters on.
1256             Optional:
1257             -seed => a random integer seed
1258             -model => an object of class Bio::Phylo::Models::Substitution::Dna or
1259             Bio::Phylo::Models::Substitution::Binary
1260             -random_rootseq => start DNA sequence simulation from random ancestral sequence
1261             instead of the median sequence in the alignment.
1262              
1263             Comments: Requires Statistics::R, with 'ape', 'phylosim', 'phangorn' and 'phytools'.
1264             If model is not given as argument, it will be estimated.
1265              
1266             =cut
1267              
1268             sub replicate {
1269 0     0 1 0 my ($self,%args) = @_;
1270              
1271 0         0 my $tree = $args{'-tree'};
1272 0 0       0 if ( not looks_like_object $tree, _TREE_ ) {
1273 0         0 throw 'BadArgs' => "Need tree as argument";
1274             }
1275              
1276 0         0 my $type = $self->get_type;
1277 0 0       0 if ( $type =~ /dna/i ) {
    0          
1278             return $self->_replicate_dna(
1279             '-tree' => $tree,
1280             '-model' => $args{'-model'},
1281             '-seed' => $args{'-seed'},
1282 0         0 '-random_rootseq' => $args{'-random_rootseq'}
1283             );
1284             }
1285             elsif ( $type =~ /standard/i ) {
1286             return $self->_replicate_binary(
1287             '-tree' => $tree,
1288             '-model' => $args{'-model'},
1289 0         0 '-seed' => $args{'-seed'}
1290             );
1291             }
1292             else {
1293 0         0 throw 'BadArgs' => "Can't replicate $type matrices (yet?)";
1294             }
1295             }
1296            
1297             sub _replicate_dna {
1298 0     0   0 my ($self,%args) = @_;
1299            
1300 0         0 my $seed = $args{'-seed'};
1301 0         0 my $tree = $args{'-tree'};
1302 0         0 my $model = $args{'-model'};
1303 0         0 my $random_rootseq = $args{'-random_rootseq'};
1304            
1305 0 0       0 if ( scalar @{ $self->get_entities } < 3 ) {
  0         0  
1306 0         0 $logger->warn("Cannot replicate a matrix with less than three elements.");
1307 0         0 return;
1308             }
1309              
1310             # we will need 'ape', 'phylosim' (and 'phangorn' for model testing)
1311 0 0       0 if ( looks_like_class 'Statistics::R' ) {
1312              
1313             # instantiate R
1314 0         0 my $R = Statistics::R->new;
1315 0 0       0 $R->run(qq[set.seed($seed)]) if $seed;
1316 0         0 $R->run(q[options(device=NULL)]);
1317 0         0 $R->run(q[require('ape')]);
1318 0         0 $R->run(q[phylosim <- require('phylosim')]);
1319 0         0 $R->run(q[PSIM_FAST<-TRUE]);
1320             # check if phylosim (and therefore ape) is installed
1321 0 0       0 if ( $R->get(q[phylosim]) eq 'FALSE' ) {
1322 0         0 $logger->warn('R package phylosim must be installed to replicate alignment.');
1323 0         0 return;
1324             }
1325              
1326             # pass in the tree, scale it so that its length sums to 1.
1327             # in combination with a gamma function and/or invariant sites
1328             # this should give us sequences that are reasonably realistic:
1329             # not overly divergent.
1330 0         0 my $newick = $tree->to_newick;
1331 0         0 $R->run(qq[phylo <- read.tree(text="$newick")]);
1332 0         0 $R->run(q[tree <- PhyloSim(phylo)]);
1333 0         0 $R->run(q[scaleTree(tree,1/tree$treeLength)]);
1334 0         0 $R->run(q[t <- tree$phylo]);
1335              
1336             # run the model test if model not given as argument
1337 0 0       0 if ( ! $model ) {
1338 0         0 $logger->info("no model given as argument, determining model with phangorn's modelTest");
1339 0         0 $model = 'Bio::Phylo::Models::Substitution::Dna'->modeltest(
1340             '-matrix' => $self,
1341             '-tree' => $tree
1342             );
1343             }
1344             # prepare data for processes
1345 0         0 my @ungapped = @{ $self->get_ungapped_columns };
  0         0  
1346 0         0 my @invariant = @{ $self->get_invariant_columns };
  0         0  
1347 0         0 my %deletions = %{ $self->calc_indel_sizes( '-trim' => 0 ) };
  0         0  
1348 0         0 my %insertions = %{ $self->calc_indel_sizes( '-trim' => 0, '-insertions' => 1 ) };
  0         0  
1349              
1350             # set ancestral (root) sequence
1351 0         0 my $ancestral;
1352 0         0 my @alphabet = ('A', 'T', 'C', 'G');
1353 0 0       0 if ( $random_rootseq ) {
1354             # start from random sequence if requested
1355 0         0 $logger->debug('simulating from random ancestral sequence');
1356 0         0 $ancestral .= $alphabet[ rand @alphabet ] for 1..$self->length;
1357             }
1358             else {
1359 0         0 $logger->debug('simulating from median ancestral sequence');
1360             # start from median sequence
1361 0         0 $ancestral = $self->calc_median_sequence;
1362             # phylosim does not accept ambiguity characters, replace with random nucleotides
1363 0         0 $ancestral =~ s/[^ATCG]/$alphabet[rand @alphabet]/g;
1364             }
1365 0         0 $logger->debug("ancestral sequence for simulation : $ancestral");
1366 0         0 $R->run(qq[root.seq=NucleotideSequence(string='$ancestral')]);
1367              
1368 0         0 my $m = ref($model);
1369 0 0       0 if ( $m=~/([^\:\:]+$)/ ) {
1370 0         0 $m = $1;
1371             }
1372             # mapping between model names (can differ between Bio::Phylo and phylosim)
1373 0         0 my %models = ('JC69'=>'JC69', 'GTR'=>'GTR', 'F81'=>'F81', 'HKY85'=>'HKY', 'K80'=>'K80');
1374 0   0     0 my $type = $models{$m} || 'GTR';
1375              
1376             # collect model specific parameters in string passed to R
1377 0         0 my $model_params = '';
1378 0 0       0 if ( $type =~ /(?:F81|GTR|K80|HKY)/ ) {
1379 0         0 $logger->debug("setting base frequencies for substitution model");
1380 0         0 $model_params .= 'base.freqs=c(' . join(',', @{$model->get_pi}) . ')';
  0         0  
1381             }
1382 0 0       0 if ( $type =~ /GTR/ ) {
1383 0         0 $logger->debug("setting rate params for GTR model");
1384             # (watch out for different column order in phangorn's and phylosim's Q matrix!)
1385 0         0 my $a = $model->get_rate('C', 'T');
1386 0         0 my $b = $model->get_rate('A', 'T');
1387 0         0 my $c = $model->get_rate('G', 'T');
1388 0         0 my $d = $model->get_rate('A', 'C');
1389 0         0 my $e = $model->get_rate('G', 'C');
1390 0         0 my $f = $model->get_rate('G', 'A');
1391 0         0 $model_params .= ", rate.params=list(a=$a, b=$b, c=$c, d=$d, e=$e, f=$f)";
1392             }
1393 0 0       0 if ( $type =~ /(?:K80|HKY)/) {
1394 0         0 $logger->debug("setting kappa parameter for substitution model");
1395 0         0 my $kappa = $model->get_kappa;
1396             # get transition and transversion rates from kappa,
1397             # scale with number of nucleotides to obtain similar Q matrices
1398 0         0 my $alpha = $kappa * 4;
1399 0         0 my $beta = 4;
1400 0         0 $model_params .= ", rate.params=list(Alpha=$alpha, Beta=$beta)";
1401             }
1402             # create model for phylosim
1403 0         0 $R->run(qq[model <- $type($model_params)]);
1404             # set parameters for indels
1405 0 0       0 if ( keys %deletions ) {
1406             # deletions
1407 0         0 my @del_sizes = keys %deletions;
1408 0         0 my $del_total = 0;
1409 0         0 $del_total += $_ for values (%deletions);
1410 0         0 my @del_probs = map {$_/$del_total} values %deletions;
  0         0  
1411 0         0 my $size_str = 'c(' . join(',', @del_sizes) . ')';
1412 0         0 my $prob_str = 'c(' . join(',', @del_probs) . ')';
1413 0         0 my $rate = scalar(keys %deletions) / $self->get_nchar;
1414 0         0 $logger->debug("Setting deletion rate to $rate");
1415 0         0 $R->run(qq[attachProcess(root.seq, DiscreteDeletor(rate=$rate, sizes=$size_str, probs=$prob_str))]);
1416             }
1417 0 0       0 if ( keys %insertions ) {
1418             # insertions
1419 0         0 my @ins_sizes = keys %insertions;
1420 0         0 my $ins_total = 0;
1421 0         0 $ins_total += $_ for values (%insertions);
1422 0         0 my @ins_probs = map {$_/$ins_total} values %insertions;
  0         0  
1423 0         0 my $size_str = 'c(' . join(',', @ins_sizes) . ')';
1424 0         0 my $prob_str = 'c(' . join(',', @ins_probs) . ')';
1425 0         0 my $maxsize = (sort { $b <=> $a } keys(%insertions))[0];
  0         0  
1426 0         0 my $rate = scalar(keys %insertions) / $self->get_nchar;
1427 0         0 $logger->debug("Setting insertion rate to $rate");
1428 0         0 $R->run(qq[i <- DiscreteInsertor(rate=$rate, sizes=$size_str, probs=$prob_str)]);
1429 0         0 $R->run(qq[template <- NucleotideSequence(length=$maxsize,processes=list(list(model)))]);
1430 0         0 $R->run(q[i$templateSeq <- template]);
1431 0         0 $R->run(qq[attachProcess(root.seq, i)]);
1432             }
1433              
1434             # specify model that evolves root sequence
1435 0         0 $R->run(q[attachProcess(root.seq, model)]);
1436              
1437             # set invariant sites
1438 0 0       0 if ( scalar @invariant ) {
1439 0   0     0 my $pinvar = $model->get_pinvar || scalar(@invariant)/$self->get_nchar;
1440 0 0       0 if ( $pinvar == 1){
1441 0         0 my $epsilon = 0.01;
1442 0         0 $pinvar -= $epsilon;
1443             }
1444             # set a high value for gamma, then we approximate the empirical number of invariant sites
1445 0         0 $R->run(qq[plusInvGamma(root.seq,model,pinv=$pinvar,shape=1e10)]);
1446             }
1447              
1448             # run the simulation
1449 0         0 $R->run(q[sim <- PhyloSim(root.seq=root.seq, phylo=t)]);
1450 0         0 $R->run(q[Simulate(sim)]);
1451              
1452             # get alignment as Fasta string
1453 0         0 my $aln = $R->get(q[paste('>', t$tip.label, '\n', apply(sim$alignment, 1, paste, collapse='')[t$tip.label], '\n', collapse='', sep='')]);
1454 0         0 $aln =~ s/\\n/\n/g;
1455              
1456             # create matrix
1457 0         0 my $project = parse(
1458             '-string' => $aln,
1459             '-format' => 'fasta',
1460             '-type' => 'dna',
1461             '-as_project' => 1,
1462             );
1463              
1464 0         0 my ($matrix) = @{ $project->get_items(_MATRIX_) };
  0         0  
1465              
1466 0         0 return $matrix;
1467             }
1468             }
1469              
1470             sub _replicate_binary {
1471 0     0   0 my ($self,%args) = @_;
1472              
1473 0         0 my $seed = $args{'-seed'};
1474 0         0 my $tree = $args{'-tree'};
1475              
1476             # we will need both 'ape' and 'phytools'
1477 0 0       0 if ( looks_like_class 'Statistics::R' ) {
1478 0         0 my $pattern = $self->calc_distinct_site_patterns('with_indices_of_patterns');
1479 0         0 my $characters = $self->get_characters;
1480 0         0 $logger->info("matrix has ".scalar(@$pattern)." distinct patterns");
1481              
1482             # instantiate R
1483 0         0 my $newick = $tree->to_newick;
1484 0         0 my $R = Statistics::R->new;
1485 0 0       0 $R->run(qq[set.seed($seed)]) if $seed;
1486 0         0 $R->run(q[library("ape")]);
1487 0         0 $R->run(q[library("phytools")]);
1488 0         0 $R->run(qq[phylo <- read.tree(text="$newick")]);
1489              
1490             # start the matrix
1491 0         0 my @matrix;
1492 0         0 push @matrix, [] for 1 .. $self->get_ntax;
1493              
1494             # iterate over distinct site patterns
1495 0         0 for my $p ( @$pattern ) {
1496 0         0 my $i = $p->[2]->[0];
1497 0         0 my %pat = map { $_ => 1 } @{ $p->[1] };
  0         0  
  0         0  
1498 0         0 my $states;
1499              
1500             # if this column is completely invariant, the model
1501             # estimator is unhappy, so we just copy it over
1502 0 0       0 if ( keys(%pat) == 1 ) {
1503 0         0 $logger->info("column is invariant, copying verbatim");
1504 0         0 $states = $p->[1];
1505             }
1506             else {
1507 0         0 my $model = $args{'-model'};
1508 0 0       0 if ( ! $model ) {
1509 0         0 $logger->info("going to test model for column(s) $i..*");
1510 0         0 $model = Bio::Phylo::Models::Substitution::Binary->modeltest(
1511             '-tree' => $tree,
1512             '-matrix' => $self,
1513             '-char' => $characters->get_by_index($i),
1514             );
1515             }
1516             # pass model to R
1517 0         0 my ( $fw, $rev ) = ( $model->get_forward, $model->get_reverse );
1518              
1519             # add epsilon if one of the rates is zero, otherwise sim.history chokes
1520 0 0       0 $fw = 1e-6 if $fw == 0;
1521 0 0       0 $rev = 1e-6 if $rev == 0;
1522 0         0 $logger->info("0 -> 1 ($fw), 1 -> 0 ($rev)");
1523              
1524 0         0 my $rates = [ 0, $fw, $rev, 0 ];
1525 0         0 $R->set( 'rates' => $rates );
1526 0         0 $R->run(qq[Q<-matrix(rates,2,2,byrow=TRUE)]);
1527 0         0 $R->run(qq[rownames(Q)<-colnames(Q)<-c("0","1")]);
1528 0         0 $R->run(qq[diag(Q)<--rowSums(Q)]);
1529              
1530             # simulate character on tree, get states
1531 0         0 $R->run(qq[tt<-sim.history(phylo,Q,message=FALSE)]);
1532 0         0 $R->run(qq[states<-as.double(getStates(tt,"tips"))]);
1533 0         0 $states = $R->get(q[states]);
1534             }
1535              
1536             # add states to matrix
1537 0         0 my @indices = @{ $p->[2] };
  0         0  
1538 0         0 for my $row ( 0 .. $#{ $states } ) {
  0         0  
1539 0         0 my $value = $states->[$row];
1540 0         0 for my $col ( @indices ) {
1541 0         0 $matrix[$row]->[$col] = $value;
1542             }
1543             }
1544             }
1545              
1546             # create matrix
1547 0         0 my $i = 0;
1548             $tree->visit_depth_first(
1549             '-pre' => sub {
1550 0     0   0 my $node = shift;
1551 0 0       0 if ( $node->is_terminal ) {
1552 0         0 unshift @{ $matrix[$i++] }, $node->get_name
  0         0  
1553             }
1554             }
1555 0         0 );
1556 0         0 $logger->info(Dumper(\@matrix));
1557 0         0 return $factory->create_matrix(
1558             '-type' => $self->get_type,
1559             '-raw' => \@matrix,
1560             );
1561             }
1562             }
1563              
1564             =item insert()
1565              
1566             Insert argument in invocant.
1567              
1568             Type : Listable method
1569             Title : insert
1570             Usage : $matrix->insert($datum);
1571             Function: Inserts $datum in $matrix.
1572             Returns : Modified object
1573             Args : A datum object
1574             Comments: This method re-implements the method by the same
1575             name in Bio::Phylo::Listable
1576              
1577             =cut
1578              
1579             sub insert {
1580 120     120 1 248 my ( $self, $obj ) = @_;
1581 120         158 my $obj_container;
1582 120         178 eval { $obj_container = $obj->_container };
  120         303  
1583 120 100 66     368 if ( $@ || $obj_container != $self->_type ) {
1584 1         4 throw 'ObjectMismatch' => 'object not a datum object!';
1585             }
1586 119         566 $logger->info("inserting '$obj' in '$self'");
1587 119 50       297 if ( !$self->get_type_object->is_same( $obj->get_type_object ) ) {
1588 0         0 throw 'ObjectMismatch' => 'object is of wrong data type';
1589             }
1590 119         404 my $taxon1 = $obj->get_taxon;
1591 119   66     336 my $mname = $self->get_name || $self->get_internal_name;
1592 119         187 for my $ents ( @{ $self->get_entities } ) {
  119         279  
1593 224 50       406 if ( $obj->get_id == $ents->get_id ) {
1594 0         0 throw 'ObjectMismatch' => 'row already inserted';
1595             }
1596 224 100       444 if ($taxon1) {
1597 1         5 my $tname = $taxon1->get_name;
1598 1         4 my $taxon2 = $ents->get_taxon;
1599 1 50 33     11 if ( $taxon2 && $taxon1->get_id == $taxon2->get_id ) {
1600 0         0 my $tmpl = 'Note: a row linking to %s already exists in matrix %s';
1601 0         0 $logger->info(sprintf $tmpl,$tname,$mname);
1602             }
1603             }
1604             }
1605 119         389 $self->SUPER::insert($obj);
1606 119         434 return $self;
1607             }
1608              
1609             =begin comment
1610              
1611             Validates the object's contents.
1612              
1613             Type : Method
1614             Title : validate
1615             Usage : $obj->validate
1616             Function: Validates the object's contents
1617             Returns : True or throws Bio::Phylo::Util::Exceptions::InvalidData
1618             Args : None
1619             Comments: This method implements the interface method by the same
1620             name in Bio::Phylo::Matrices::TypeSafeData
1621              
1622             =end comment
1623              
1624             =cut
1625              
1626             sub _validate {
1627 0     0   0 shift->visit( sub { shift->validate } );
  73     73   472  
1628             }
1629              
1630             =item compress_lookup()
1631              
1632             Removes unused states from lookup table
1633              
1634             Type : Method
1635             Title : validate
1636             Usage : $obj->compress_lookup
1637             Function: Removes unused states from lookup table
1638             Returns : $self
1639             Args : None
1640              
1641             =cut
1642              
1643             sub compress_lookup {
1644 0     0 1 0 my $self = shift;
1645 0         0 my $to = $self->get_type_object;
1646 0         0 my $lookup = $to->get_lookup;
1647 0         0 my %seen;
1648 0         0 for my $row ( @{ $self->get_entities } ) {
  0         0  
1649 0         0 my @char = $row->get_char;
1650 0         0 $seen{$_}++ for (@char);
1651             }
1652 0         0 for my $state ( keys %{$lookup} ) {
  0         0  
1653 0 0       0 if ( not exists $seen{$state} ) {
1654 0         0 delete $lookup->{$state};
1655             }
1656             }
1657 0         0 $to->set_lookup($lookup);
1658 0         0 return $self;
1659             }
1660              
1661             =item check_taxa()
1662              
1663             Validates taxa associations.
1664              
1665             Type : Method
1666             Title : check_taxa
1667             Usage : $obj->check_taxa
1668             Function: Validates relation between matrix and taxa block
1669             Returns : Modified object
1670             Args : None
1671             Comments: This method implements the interface method by the same
1672             name in Bio::Phylo::Taxa::TaxaLinker
1673              
1674             =cut
1675              
1676             sub check_taxa {
1677 48     48 1 76 my $self = shift;
1678              
1679             # is linked to taxa
1680 48 100       117 if ( my $taxa = $self->get_taxa ) {
1681             my %taxa =
1682 44         69 map { $_->get_internal_name => $_ } @{ $taxa->get_entities };
  186         395  
  44         114  
1683 44         86 ROW_CHECK: for my $row ( @{ $self->get_entities } ) {
  44         96  
1684 116 100       245 if ( my $taxon = $row->get_taxon ) {
1685 105 50       217 next ROW_CHECK if exists $taxa{ $taxon->get_name };
1686             }
1687 11         29 my $name = $row->get_name;
1688 11 50       29 if ( exists $taxa{$name} ) {
1689 11         82 $row->set_taxon( $taxa{$name} );
1690             }
1691             else {
1692 0         0 my $taxon = $factory->create_taxon( -name => $name );
1693 0         0 $taxa{$name} = $taxon;
1694 0         0 $taxa->insert($taxon);
1695 0         0 $row->set_taxon($taxon);
1696             }
1697             }
1698             }
1699              
1700             # not linked
1701             else {
1702 4         11 for my $row ( @{ $self->get_entities } ) {
  4         10  
1703 16         30 $row->set_taxon();
1704             }
1705             }
1706 48         94 return $self;
1707             }
1708              
1709             =item make_taxa()
1710              
1711             Creates a taxa block from the objects contents if none exists yet.
1712              
1713             Type : Method
1714             Title : make_taxa
1715             Usage : my $taxa = $obj->make_taxa
1716             Function: Creates a taxa block from the objects contents if none exists yet.
1717             Returns : $taxa
1718             Args : NONE
1719              
1720             =cut
1721              
1722             sub make_taxa {
1723 1     1 1 11 my $self = shift;
1724 1 50       11 if ( my $taxa = $self->get_taxa ) {
1725 0         0 return $taxa;
1726             }
1727             else {
1728 1         3 my %taxa;
1729 1         9 my $taxa = $factory->create_taxa;
1730 1         3 for my $row ( @{ $self->get_entities } ) {
  1         4  
1731 2         7 my $name = $row->get_internal_name;
1732 2 50       7 if ( not $taxa{$name} ) {
1733 2         12 $taxa{$name} = $factory->create_taxon( '-name' => $name );
1734             }
1735             }
1736 1 50       5 if ( keys %taxa ) {
1737 1         8 $taxa->insert( map { $taxa{$_} } sort { $a cmp $b } keys %taxa );
  2         16  
  1         7  
1738             }
1739 1         17 $self->set_taxa($taxa);
1740 1         7 return $taxa;
1741             }
1742             }
1743              
1744             =back
1745              
1746             =head2 SERIALIZERS
1747              
1748             =over
1749              
1750             =item to_xml()
1751              
1752             Serializes matrix to nexml format.
1753              
1754             Type : Format convertor
1755             Title : to_xml
1756             Usage : my $data_block = $matrix->to_xml;
1757             Function: Converts matrix object into a nexml element structure.
1758             Returns : Nexml block (SCALAR).
1759             Args : Optional:
1760             -compact => 1 (for compact representation of matrix)
1761              
1762             =cut
1763              
1764             sub to_xml {
1765 0     0 1 0 my $self = shift;
1766 0         0 $logger->debug("writing $self to xml");
1767 0         0 my ( %args, $ids_for_states );
1768 0 0       0 if (@_) {
1769 0         0 %args = @_;
1770             }
1771              
1772             # creating opening tag
1773 0         0 my $type = $self->get_type;
1774 0 0       0 my $verbosity = $args{'-compact'} ? 'Seqs' : 'Cells';
1775 0         0 my $xsi_type = 'nex:' . ucfirst($type) . $verbosity;
1776 0         0 $self->set_attributes( 'xsi:type' => $xsi_type );
1777 0         0 my $xml = $self->get_xml_tag;
1778 0         0 $logger->debug("created opening tag $xml");
1779              
1780             # normalizing symbol table
1781 0         0 my $normalized = $self->_normalize_symbols;
1782 0         0 $logger->debug("normalized symbols");
1783              
1784             # the format block
1785 0         0 $xml .= '<format>';
1786 0         0 $logger->debug($xml);
1787 0         0 my $to = $self->get_type_object;
1788 0         0 $ids_for_states = $to->get_ids_for_states(1);
1789              
1790             # write state definitions
1791             # this calls Datatype::to_xml method
1792             #
1793 0         0 $xml .= $to->to_xml( $normalized, $self->get_polymorphism );
1794 0         0 $logger->debug($xml);
1795 0         0 $xml .= $self->get_characters->to_xml;
1796 0         0 $xml .= "\n</format>";
1797              
1798             # the matrix block
1799 0         0 $xml .= "\n<matrix>";
1800             my @char_ids =
1801 0         0 map { $_->get_xml_id } @{ $self->get_characters->get_entities };
  0         0  
  0         0  
1802              
1803             # write rows
1804 0         0 my $special = $self->get_type_object->get_ids_for_special_symbols(1);
1805 0         0 for my $row ( @{ $self->get_entities } ) {
  0         0  
1806 0         0 $xml .= "\n"
1807             . $row->to_xml(
1808             '-states' => $ids_for_states,
1809             '-chars' => \@char_ids,
1810             '-symbols' => $normalized,
1811             '-special' => $special,
1812             %args,
1813             );
1814             }
1815 0         0 $xml .= "\n</matrix>";
1816 0         0 $xml .= "\n" . sprintf( '</%s>', $self->get_tag );
1817 0         0 return $xml;
1818             }
1819              
1820             # what this does:
1821             # numerical states are their own keys; also want numerical
1822             # representations for non-numerical states..."normalization"
1823             # provides this mapping....
1824             sub _normalize_symbols {
1825 0     0   0 my $self = shift;
1826 0         0 $logger->debug("normalizing symbols");
1827 0 0       0 if ( $self->get_type =~ /^standard$/i ) {
1828 0         0 my $to = $self->get_type_object;
1829 0         0 my $lookup = $self->get_lookup;
1830 0         0 my @states = keys %{$lookup};
  0         0  
1831 0 0       0 if ( my @letters = sort { $a cmp $b } grep { /[a-z]/i } @states ) {
  0         0  
  0         0  
1832 0         0 my @numbers = sort { $a <=> $b } grep { /^\d+$/ } @states;
  0         0  
  0         0  
1833 0         0 my $i = $numbers[-1];
1834 0         0 my %map = map { $_ => ++$i } @letters;
  0         0  
1835 0         0 return \%map;
1836             }
1837             else {
1838 0         0 return {};
1839             }
1840             }
1841             else {
1842 0         0 return {};
1843             }
1844             }
1845              
1846             =item to_nexus()
1847              
1848             Serializes matrix to nexus format.
1849              
1850             Type : Format convertor
1851             Title : to_nexus
1852             Usage : my $data_block = $matrix->to_nexus;
1853             Function: Converts matrix object into a nexus data block.
1854             Returns : Nexus data block (SCALAR).
1855             Args : The following options are available:
1856              
1857             # if set, writes TITLE & LINK tokens
1858             '-links' => 1
1859              
1860             # if set, writes block as a "data" block (deprecated, but used by mrbayes),
1861             # otherwise writes "characters" block (default)
1862             -data_block => 1
1863              
1864             # if set, writes "RESPECTCASE" token
1865             -respectcase => 1
1866              
1867             # if set, writes "GAPMODE=(NEWSTATE or MISSING)" token
1868             -gapmode => 1
1869              
1870             # if set, writes "MSTAXA=(POLYMORPH or UNCERTAIN)" token
1871             -polymorphism => 1
1872              
1873             # if set, writes character labels
1874             -charlabels => 1
1875              
1876             # if set, writes state labels
1877             -statelabels => 1
1878              
1879             # if set, writes mesquite-style charstatelabels
1880             -charstatelabels => 1
1881              
1882             # by default, names for sequences are derived from $datum->get_name, if
1883             # 'internal' is specified, uses $datum->get_internal_name, if 'taxon'
1884             # uses $datum->get_taxon->get_name, if 'taxon_internal' uses
1885             # $datum->get_taxon->get_internal_name, if $key, uses $datum->get_generic($key)
1886             -seqnames => one of (internal|taxon|taxon_internal|$key)
1887              
1888             =cut
1889              
1890             sub to_nexus {
1891 1     1 1 2 my $self = shift;
1892 1         6 $logger->info("writing to nexus: $self");
1893 1         5 my %args = @_;
1894 1         12 my $nchar = $self->get_nchar;
1895             my $string = sprintf "BEGIN %s;\n",
1896 1 50       9 $args{'-data_block'} ? 'DATA' : 'CHARACTERS';
1897 1         13 $string .=
1898             "[! Characters block written by "
1899             . ref($self) . " "
1900             . $self->VERSION . " on "
1901             . localtime() . " ]\n";
1902              
1903             # write links
1904 1 50       6 if ( $args{'-links'} ) {
1905 0         0 $string .= sprintf "\tTITLE %s;\n", $self->get_internal_name;
1906 0 0       0 $string .= sprintf "\tLINK TAXA=%s;\n",
1907             $self->get_taxa->get_internal_name
1908             if $self->get_taxa;
1909             }
1910              
1911             # dimensions token line - data block defines NTAX, characters block doesn't
1912 1 50       4 if ( $args{'-data_block'} ) {
1913 0         0 $string .= "\tDIMENSIONS NTAX=" . $self->get_ntax() . ' ';
1914 0         0 $string .= 'NCHAR=' . $nchar . ";\n";
1915             }
1916             else {
1917 1         4 $string .= "\tDIMENSIONS NCHAR=" . $nchar . ";\n";
1918             }
1919              
1920             # format token line
1921 1         15 $string .= "\tFORMAT DATATYPE=" . $self->get_type();
1922             $string .= ( $self->get_respectcase ? " RESPECTCASE" : "" )
1923 1 0       7 if $args{'-respectcase'}; # mrbayes no like
    50          
1924 1 50       7 $string .= " MATCHCHAR=" . $self->get_matchchar if $self->get_matchchar;
1925 1         11 $string .= " MISSING=" . $self->get_missing();
1926 1 50       7 $string .= " GAP=" . $self->get_gap() if $self->get_gap();
1927 1         3 $string .= ";\n";
1928              
1929             # options token line (mrbayes no like)
1930 1 50 33     6 if ( $args{'-gapmode'} or $args{'-polymorphism'} ) {
1931 0         0 $string .= "\tOPTIONS ";
1932             $string .=
1933             "GAPMODE=" . ( $self->get_gapmode ? "NEWSTATE " : "MISSING " )
1934 0 0       0 if $args{'-gapmode'};
    0          
1935             $string .=
1936             "MSTAXA="
1937             . ( $self->get_polymorphism ? "POLYMORPH " : "UNCERTAIN " )
1938 0 0       0 if $args{'-polymorphism'};
    0          
1939 0         0 $string .= ";\n";
1940             }
1941              
1942             # charlabels token line
1943 1 50       8 if ( $args{'-charlabels'} ) {
1944 0         0 my $charlabels;
1945 0 0       0 if ( my @labels = @{ $self->get_charlabels } ) {
  0         0  
1946 0         0 my $i = 1;
1947 0         0 for my $label (@labels) {
1948 0 0       0 $charlabels .=
1949             $label =~ /\s/
1950             ? "\n\t\t [$i] '$label'"
1951             : "\n\t\t [$i] $label";
1952 0         0 $i++;
1953             }
1954 0         0 $string .= "\tCHARLABELS$charlabels\n\t;\n";
1955             }
1956             }
1957              
1958             # statelabels token line
1959 1 50       3 if ( $args{'-statelabels'} ) {
1960 0         0 my $statelabels;
1961 0 0       0 if ( my @labels = @{ $self->get_statelabels } ) {
  0         0  
1962 0         0 my $i = 1;
1963 0         0 for my $labelset (@labels) {
1964 0         0 $statelabels .= "\n\t\t $i";
1965 0         0 for my $label ( @{$labelset} ) {
  0         0  
1966 0 0       0 $statelabels .=
1967             $label =~ /\s/
1968             ? "\n\t\t\t'$label'"
1969             : "\n\t\t\t$label";
1970 0         0 $i++;
1971             }
1972 0         0 $statelabels .= ',';
1973             }
1974 0         0 $string .= "\tSTATELABELS$statelabels\n\t;\n";
1975             }
1976             }
1977              
1978             # charstatelabels token line
1979 1 50       3 if ( $args{'-charstatelabels'} ) {
1980 0         0 my @charlabels = @{ $self->get_charlabels };
  0         0  
1981 0         0 my @statelabels = @{ $self->get_statelabels };
  0         0  
1982 0 0 0     0 if ( @charlabels and @statelabels ) {
1983 0         0 my $charstatelabels;
1984 0         0 my $nlabels = $self->get_nchar - 1;
1985 0         0 for my $i ( 0 .. $nlabels ) {
1986 0         0 $charstatelabels .= "\n\t\t" . ( $i + 1 );
1987 0 0       0 if ( my $label = $charlabels[$i] ) {
1988 0 0       0 $charstatelabels .=
1989             $label =~ /\s/ ? " '$label' /" : " $label /";
1990             }
1991             else {
1992 0         0 $charstatelabels .= " ' ' /";
1993             }
1994 0 0       0 if ( my $labelset = $statelabels[$i] ) {
1995 0         0 for my $label ( @{$labelset} ) {
  0         0  
1996 0 0       0 $charstatelabels .=
1997             $label =~ /\s/ ? " '$label'" : " $label";
1998             }
1999             }
2000             else {
2001 0         0 $charstatelabels .= " ' '";
2002             }
2003 0 0       0 $charstatelabels .= ',' if $i < $nlabels;
2004             }
2005 0         0 $string .= "\tCHARSTATELABELS$charstatelabels\n\t;\n";
2006             }
2007             }
2008              
2009             # ...and write matrix!
2010 1         3 $string .= "\tMATRIX\n";
2011 1         2 my $length = 0;
2012 1         2 foreach my $datum ( @{ $self->get_entities } ) {
  1         3  
2013 3 100       14 $length = length( $datum->get_nexus_name )
2014             if length( $datum->get_nexus_name ) > $length;
2015             }
2016 1         3 $length += 4;
2017 1         2 my $sp = ' ';
2018 1         1 foreach my $datum ( @{ $self->get_entities } ) {
  1         3  
2019 3         4 $string .= "\t\t";
2020              
2021             # construct name
2022 3         5 my $name;
2023 3 50 0     6 if ( not $args{'-seqnames'} ) {
    0          
    0          
2024 3         6 $name = $datum->get_nexus_name;
2025             }
2026             elsif ( $args{'-seqnames'} =~ /^internal$/i ) {
2027 0         0 $name = $datum->get_nexus_name;
2028             }
2029             elsif ( $args{'-seqnames'} =~ /^taxon/i and $datum->get_taxon ) {
2030 0 0       0 if ( $args{'-seqnames'} =~ /^taxon_internal$/i ) {
    0          
2031 0         0 $name = $datum->get_taxon->get_nexus_name;
2032             }
2033             elsif ( $args{'-seqnames'} =~ /^taxon$/i ) {
2034 0         0 $name = $datum->get_taxon->get_nexus_name;
2035             }
2036             }
2037             else {
2038 0         0 $name = $datum->get_generic( $args{'-seqnames'} );
2039             }
2040 3 50       5 $name = $datum->get_nexus_name if not $name;
2041 3         8 $string .= $name . ( $sp x ( $length - length($name) ) );
2042 3         6 my $char = $datum->get_char;
2043 3         8 $string .= $char . "\n";
2044             }
2045 1         2 $string .= "\t;\nEND;\n";
2046 1         6 return $string;
2047             }
2048              
2049             =item to_dom()
2050              
2051             Analog to to_xml.
2052              
2053             Type : Serializer
2054             Title : to_dom
2055             Usage : $matrix->to_dom
2056             Function: Generates a DOM subtree from the invocant
2057             and its contained objects
2058             Returns : an Element object
2059             Args : Optional:
2060             -compact => 1 : renders characters as sequences,
2061             not individual cells
2062              
2063             =cut
2064              
2065             sub to_dom {
2066 0     0 1 0 my $self = shift;
2067 0         0 my $dom = $_[0];
2068 0         0 my @args = @_;
2069              
2070             # handle dom factory object...
2071 0 0 0     0 if ( looks_like_instance( $dom, 'SCALAR' )
2072             && $dom->_type == _DOMCREATOR_ )
2073             {
2074 0         0 splice( @args, 0, 1 );
2075             }
2076             else {
2077 0         0 $dom = $Bio::Phylo::NeXML::DOM::DOM;
2078 0 0       0 unless ($dom) {
2079 0         0 throw 'BadArgs' => 'DOM factory object not provided';
2080             }
2081             }
2082             #### make sure argument handling works here...
2083 0         0 my ( %args, $ids_for_states );
2084 0 0       0 %args = @args if @args;
2085 0         0 my $type = $self->get_type;
2086 0 0       0 my $verbosity = $args{'-compact'} ? 'Seqs' : 'Cells';
2087 0         0 my $xsi_type = 'nex:' . ucfirst($type) . $verbosity;
2088 0         0 $self->set_attributes( 'xsi:type' => $xsi_type );
2089 0         0 my $elt = $self->get_dom_elt($dom);
2090 0         0 my $normalized = $self->_normalize_symbols;
2091              
2092             # the format block
2093 0         0 my $format_elt = $dom->create_element( '-tag' => 'format' );
2094 0         0 my $to = $self->get_type_object;
2095 0         0 $ids_for_states = $to->get_ids_for_states(1);
2096              
2097             # write state definitions
2098 0         0 $format_elt->set_child(
2099             $to->to_dom( $dom, $normalized, $self->get_polymorphism ) );
2100              
2101             # write column definitions
2102             $format_elt->set_child($_)
2103 0 0       0 for $self->_package_char_labels( $dom,
2104 0         0 %{$ids_for_states} ? $to->get_xml_id : undef );
2105 0         0 $elt->set_child($format_elt);
2106              
2107             # the matrix block
2108 0         0 my $mx_elt = $dom->create_element( '-tag' => 'matrix' );
2109 0         0 my @char_ids;
2110 0         0 for ( 0 .. $self->get_nchar ) {
2111 0         0 push @char_ids, 'c' . ( $_ + 1 );
2112             }
2113              
2114             # write rows
2115 0         0 my $special = $self->get_type_object->get_ids_for_special_symbols(1);
2116 0         0 for my $row ( @{ $self->get_entities } ) {
  0         0  
2117              
2118             # $row->to_dom is calling ...::Datum::to_dom...
2119 0         0 $mx_elt->set_child(
2120             $row->to_dom(
2121             $dom,
2122             '-states' => $ids_for_states,
2123             '-chars' => \@char_ids,
2124             '-symbols' => $normalized,
2125             '-special' => $special,
2126             %args,
2127             )
2128             );
2129             }
2130 0         0 $elt->set_child($mx_elt);
2131 0         0 return $elt;
2132             }
2133              
2134             # returns an array of elements
2135             sub _package_char_labels {
2136 0     0   0 my ( $self, $dom, $states_id ) = @_;
2137 0         0 my @elts;
2138 0         0 my $labels = $self->get_charlabels;
2139 0         0 for my $i ( 1 .. $self->get_nchar ) {
2140 0         0 my $char_id = 'c' . $i;
2141 0         0 my $label = $labels->[ $i - 1 ];
2142 0         0 my $elt = $dom->create_element( '-tag' => 'char' );
2143 0         0 $elt->set_attributes( 'id' => $char_id );
2144 0 0       0 $elt->set_attributes( 'label' => $label ) if $label;
2145 0 0       0 $elt->set_attributes( 'states' => $states_id ) if $states_id;
2146 0         0 push @elts, $elt;
2147             }
2148 0         0 return @elts;
2149             }
2150 6     6   20 sub _tag { 'characters' }
2151 634     634   1383 sub _type { $CONSTANT_TYPE }
2152 18     18   35 sub _container { $CONSTANT_CONTAINER }
2153              
2154             sub _update_characters {
2155 137     137   220 my $self = shift;
2156 137         329 my $nchar = $self->get_nchar;
2157 137         369 my $characters = $self->get_characters;
2158 137         391 my $type_object = $self->get_type_object;
2159 137         211 my @chars = @{ $characters->get_entities };
  137         300  
2160 137         280 my @defined = grep { defined $_ } @chars;
  607         998  
2161 137 100       306 if ( scalar @defined != $nchar ) {
2162 26         73 for my $i ( 0 .. ( $nchar - 1 ) ) {
2163 157 50       317 if ( not $chars[$i] ) {
2164 157         782 $characters->insert_at_index(
2165             $factory->create_character(
2166             '-type_object' => $type_object
2167             ),
2168             $i,
2169             );
2170             }
2171             }
2172             }
2173 137         200 @chars = @{ $characters->get_entities };
  137         305  
2174 137 50       480 if ( scalar(@chars) > $nchar ) {
2175 0           $characters->prune_entities( [ $nchar .. $#chars ] );
2176             }
2177             }
2178              
2179             =back
2180              
2181             =cut
2182              
2183             # podinherit_insert_token
2184              
2185             =head1 SEE ALSO
2186              
2187             There is a mailing list at L<https://groups.google.com/forum/#!forum/bio-phylo>
2188             for any user or developer questions and discussions.
2189              
2190             =over
2191              
2192             =item L<Bio::Phylo::Taxa::TaxaLinker>
2193              
2194             This object inherits from L<Bio::Phylo::Taxa::TaxaLinker>, so the
2195             methods defined therein are also applicable to L<Bio::Phylo::Matrices::Matrix>
2196             objects.
2197              
2198             =item L<Bio::Phylo::Matrices::TypeSafeData>
2199              
2200             This object inherits from L<Bio::Phylo::Matrices::TypeSafeData>, so the
2201             methods defined therein are also applicable to L<Bio::Phylo::Matrices::Matrix>
2202             objects.
2203              
2204             =item L<Bio::Phylo::Manual>
2205              
2206             Also see the manual: L<Bio::Phylo::Manual> and L<http://rutgervos.blogspot.com>.
2207              
2208             =back
2209              
2210             =head1 CITATION
2211              
2212             If you use Bio::Phylo in published research, please cite it:
2213              
2214             B<Rutger A Vos>, B<Jason Caravas>, B<Klaas Hartmann>, B<Mark A Jensen>
2215             and B<Chase Miller>, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
2216             I<BMC Bioinformatics> B<12>:63.
2217             L<http://dx.doi.org/10.1186/1471-2105-12-63>
2218              
2219             =cut
2220              
2221             }
2222             1;
2223             __DATA__
2224              
2225             my %CONSERVATION_GROUPS = (
2226             'strong' => [ qw(
2227             STA
2228             NEQK
2229             NHQK
2230             NDEQ
2231             QHRK
2232             MILV
2233             MILF
2234             HY
2235             FYW
2236             )],
2237             'weak' => [ qw(
2238             CSA
2239             ATV
2240             SAG
2241             STNK
2242             STPA
2243             SGND
2244             SNDEQK
2245             NDEQHK
2246             NEQHRK
2247             FVLIM
2248             HFY
2249             )],
2250             );
2251              
2252             sub description {
2253             my ( $self, $desc ) = @_;
2254             if ( defined $desc ) {
2255             $self->set_desc( $desc );
2256             }
2257             return $self->get_desc;
2258             }
2259              
2260             sub num_sequences {
2261             my ( $self, $num ) = @_;
2262             # setter?
2263             return scalar @{ $self->get_entities };
2264             }
2265              
2266             sub datatype {
2267             my ( $self, $type ) = @_;
2268             # setter?
2269             return uc $self->get_type;
2270             }
2271              
2272             sub score {
2273             my ( $self, $score ) = @_;
2274             if ( defined $score ) {
2275             $self->set_score( $score );
2276             }
2277             return $self->get_score;
2278             }
2279              
2280             sub add_seq {
2281             my ( $self, $seq, $order ) = @_;
2282             $self->insert( $seq );
2283             }
2284              
2285             sub remove_seq {
2286             my ( $self, $seq ) = @_;
2287             $self->delete( $seq );
2288             }
2289              
2290             sub purge {
2291             $logger->warn
2292             }
2293              
2294             sub sort_alphabetically {
2295             my $self = shift;
2296             my @sorted = map { $_->[0] }
2297             sort { $a->[1] cmp $b->[1] }
2298             map { [ $_, $_->get_name ] }
2299             @{ $self->get_entities };
2300             $self->clear;
2301             $self->insert(@sorted);
2302             return @sorted;
2303             }
2304              
2305             sub each_seq {
2306             my $self = shift;
2307             return @{ $self->get_entities };
2308             }
2309              
2310             sub each_alphabetically {
2311             my $self = shift;
2312             return map { $_->[0] }
2313             sort { $a->[1] cmp $b->[1] }
2314             map { [ $_, $_->get_name ] } @{ $self->get_entities };
2315             }
2316              
2317             sub each_seq_with_id {
2318             my ( $self, $name ) = @_;
2319             return @{
2320             $self->get_by_regular_expression(
2321             '-value' => 'get_name',
2322             '-match' => qr/^\Q$name\E$/
2323             )
2324             }
2325             }
2326              
2327             sub get_seq_by_pos {
2328             my ( $self, $pos ) = @_;
2329             return $self->get_by_index( $pos - 1 );
2330             }
2331              
2332             sub select {
2333             my ( $self, $start, $end ) = @_;
2334             my $clone = $self->clone;
2335             my @contents = @{ $clone->get_entities };
2336             my @deleteme;
2337             for my $i ( 0 .. $#contents ) {
2338             if ( $i < $start - 1 or $i > $end - 1 ) {
2339             push @deleteme, $contents[$i];
2340             }
2341             }
2342             $clone->delete( $_ ) for @deleteme;
2343             return $clone;
2344             }
2345              
2346             sub select_noncont {
2347             my ( $self, @indices ) = @_;
2348             my $clone = $self->clone;
2349             my @contents = @{ $clone->get_entities };
2350             my ( @deleteme, %keep );
2351             %keep = map { ( $_ - 1 ) => 1 } @indices;
2352             for my $i ( 0 .. $#contents ) {
2353             if ( not exists $keep{$i} ) {
2354             push @deleteme, $contents[$i];
2355             }
2356             }
2357             $clone->delete( $_ ) for @deleteme;
2358             return $clone;
2359             }
2360              
2361             sub slice {
2362             my ( $self, $start, $end, $include_gapped ) = @_;
2363             my $clone = $self->clone;
2364             my $gap = $self->get_gap;
2365             SEQ: for my $seq ( @{ $clone->get_entities } ) {
2366             my @char = $self->get_char;
2367             my @slice = splice @char, ( $start - 1 ), ( $end - $start - 1 );
2368             if ( not $include_gapped ) {
2369             if ( not grep { $_ !~ /^\Q$gap\E$/ } @slice ) {
2370             next SEQ;
2371             }
2372             }
2373             $seq->set_char(@slice);
2374             }
2375             }
2376              
2377             sub map_chars {
2378             my ( $self, $from, $to ) = @_;
2379             for my $seq ( @{ $self->get_entities } ) {
2380             my @char = $seq->get_char;
2381             for my $c ( @char ) {
2382             $c =~ s/$from/$to/;
2383             }
2384             $seq->set_char( @char );
2385             }
2386             }
2387              
2388             sub uppercase {
2389             my $self = shift;
2390             for my $seq ( @{ $self->get_entities } ) {
2391             my @char = $seq->get_char;
2392             my @uc = map { uc $_ } @char;
2393             $seq->set_char(@uc);
2394             }
2395             }
2396              
2397             # from simplealign
2398             sub match_line {
2399             my ($self,$matchlinechar, $strong, $weak) = @_;
2400             my %matchchars = ('match' => $matchlinechar || '*',
2401             'weak' => $weak || '.',
2402             'strong' => $strong || ':',
2403             'mismatch' => ' ',
2404             );
2405              
2406             my @seqchars;
2407             my $alphabet;
2408             foreach my $seq ( $self->each_seq ) {
2409             push @seqchars, [ split(//, uc ($seq->seq)) ];
2410             $alphabet = $seq->alphabet unless defined $alphabet;
2411             }
2412             my $refseq = shift @seqchars;
2413             # let's just march down the columns
2414             my $matchline;
2415             POS:
2416             foreach my $pos ( 0..$self->length ) {
2417             my $refchar = $refseq->[$pos];
2418             my $char = $matchchars{'mismatch'};
2419             unless( defined $refchar ) {
2420             last if $pos == $self->length; # short circuit on last residue
2421             # this in place to handle jason's soon-to-be-committed
2422             # intron mapping code
2423             goto bottom;
2424             }
2425             my %col = ($refchar => 1);
2426             my $dash = ($refchar eq '-' || $refchar eq '.' || $refchar eq ' ');
2427             foreach my $seq ( @seqchars ) {
2428             next if $pos >= scalar @$seq;
2429             $dash = 1 if( $seq->[$pos] eq '-' || $seq->[$pos] eq '.' ||
2430             $seq->[$pos] eq ' ' );
2431             $col{$seq->[$pos]}++ if defined $seq->[$pos];
2432             }
2433             my @colresidues = sort keys %col;
2434              
2435             # if all the values are the same
2436             if( $dash ) { $char = $matchchars{'mismatch'} }
2437             elsif( @colresidues == 1 ) { $char = $matchchars{'match'} }
2438             elsif( $alphabet eq 'protein' ) { # only try to do weak/strong
2439             # matches for protein seqs
2440             TYPE:
2441             foreach my $type ( qw(strong weak) ) {
2442             # iterate through categories
2443             my %groups;
2444             # iterate through each of the aa in the col
2445             # look to see which groups it is in
2446             foreach my $c ( @colresidues ) {
2447             foreach my $f ( grep { index($_,$c) >= 0 } @{$CONSERVATION_GROUPS{$type}} ) {
2448             push @{$groups{$f}},$c;
2449             }
2450             }
2451             GRP:
2452             foreach my $cols ( values %groups ) {
2453             @$cols = sort @$cols;
2454             # now we are just testing to see if two arrays
2455             # are identical w/o changing either one
2456             # have to be same len
2457             next if( scalar @$cols != scalar @colresidues );
2458             # walk down the length and check each slot
2459             for($_=0;$_ < (scalar @$cols);$_++ ) {
2460             next GRP if( $cols->[$_] ne $colresidues[$_] );
2461             }
2462             $char = $matchchars{$type};
2463             last TYPE;
2464             }
2465             }
2466             }
2467             bottom:
2468             $matchline .= $char;
2469             }
2470             return $matchline;
2471             }
2472              
2473             sub match {
2474             my ( $self, $match ) = @_;
2475             if ( defined $match ) {
2476             $self->set_matchchar($match);
2477             }
2478             else {
2479             $self->set_matchchar('.');
2480             }
2481             $match = $self->get_matchchar;
2482             my $lookup = $self->get_type_object->get_lookup->{$match} = [ $match ];
2483             my @seqs = @{ $self->get_entities };
2484             my @firstseq = $seqs[0]->get_char;
2485             for my $i ( 1 .. $#seqs ) {
2486             my @char = $seqs[$i]->get_char;
2487             for my $j ( 0 .. $#char ) {
2488             if ( $char[$j] eq $firstseq[$j] ) {
2489             $char[$j] = $match;
2490             }
2491             }
2492             $seqs[$i]->set_char(@char);
2493             }
2494             1;
2495             }
2496              
2497             sub unmatch {
2498             my ( $self, $match ) = @_;
2499             if ( defined $match ) {
2500             $self->set_matchchar($match);
2501             }
2502             else {
2503             $self->set_matchchar('.');
2504             }
2505             $match = $self->get_matchchar;
2506             my @seqs = @{ $self->get_entities };
2507             my @firstseq = $seqs[0]->get_char;
2508             for my $i ( 1 .. $#seqs ) {
2509             my @char = $seqs[$i]->get_char;
2510             for my $j ( 0 .. $#char ) {
2511             if ( $char[$j] eq $match ) {
2512             $char[$j] = $firstseq[$j];
2513             }
2514             }
2515             $seqs[$i]->set_char(@char);
2516             }
2517             1;
2518             }
2519              
2520             sub id {
2521             my ( $self, $name ) = @_;
2522             if ( defined $name ) {
2523             $self->set_name( $name );
2524             }
2525             return $self->get_name;
2526             }
2527              
2528             sub missing_char {
2529             my ( $self, $missing ) = @_;
2530             if ( defined $missing ) {
2531             $self->set_missing( $missing );
2532             }
2533             return $self->get_missing;
2534             }
2535              
2536             sub match_char {
2537             my ( $self, $match ) = @_;
2538             if ( defined $match ) {
2539             $self->set_matchchar( $match );
2540             }
2541             return $self->get_matchchar;
2542             }
2543              
2544             sub gap_char {
2545             my ( $self, $gap ) = @_;
2546             if ( defined $gap ) {
2547             $self->set_gap( $gap );
2548             }
2549             return $self->get_gap;
2550             }
2551              
2552             sub symbol_chars {
2553             my ( $self, $includeextra ) = @_;
2554             my %seen;
2555             for my $row ( @{ $self->get_entities } ) {
2556             my @char = $row->get_char;
2557             $seen{$_} = 1 for @char;
2558             }
2559             return keys %seen if $includeextra;
2560             my $special_values = $self->get_special_symbols;
2561             my %special_keys = map { $_ => 1 } values %{ $special_values };
2562             return grep { ! $special_keys{$_} } keys %seen;
2563             }
2564              
2565             sub consensus_string {
2566             my $self = shift;
2567             my $to = $self->get_type_object;
2568             my $ntax = $self->get_ntax;
2569             my $nchar = $self->get_nchar;
2570             my @consensus;
2571             for my $i ( 0 .. $ntax - 1 ) {
2572             my ( @column, %column );
2573             for my $j ( 0 .. $nchar - 1 ) {
2574             $column{ $self->get_by_index($i)->get_by_index($j) } = 1;
2575             }
2576             @column = keys %column;
2577             push @consensus, $to->get_symbol_for_states(@column);
2578             }
2579             return join '', @consensus;
2580             }
2581              
2582             sub consensus_iupac {
2583             $logger->warn
2584             }
2585              
2586             sub is_flush { 1 }
2587              
2588             sub length { shift->get_nchar }
2589              
2590             sub maxname_length { $logger->warn }
2591              
2592             sub no_residues { $logger->warn }
2593              
2594             sub no_sequences {
2595             my $self = shift;
2596             return scalar @{ $self->get_entities };
2597             }
2598              
2599             sub percentage_identity { $logger->warn }
2600              
2601             # from simplealign
2602             sub average_percentage_identity{
2603             my ($self,@args) = @_;
2604              
2605             my @alphabet = ('A','B','C','D','E','F','G','H','I','J','K','L','M',
2606             'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
2607              
2608             my ($len, $total, $subtotal, $divisor, $subdivisor, @seqs, @countHashes);
2609              
2610             if (! $self->is_flush()) {
2611             throw 'Generic' => "All sequences in the alignment must be the same length";
2612             }
2613              
2614             @seqs = $self->each_seq();
2615             $len = $self->length();
2616              
2617             # load the each hash with correct keys for existence checks
2618              
2619             for( my $index=0; $index < $len; $index++) {
2620             foreach my $letter (@alphabet) {
2621             $countHashes[$index] = {} if not $countHashes[$index];
2622             $countHashes[$index]->{$letter} = 0;
2623             }
2624             }
2625             foreach my $seq (@seqs) {
2626             my @seqChars = split //, $seq->seq();
2627             for( my $column=0; $column < @seqChars; $column++ ) {
2628             my $char = uc($seqChars[$column]);
2629             if (exists $countHashes[$column]->{$char}) {
2630             $countHashes[$column]->{$char}++;
2631             }
2632             }
2633             }
2634              
2635             $total = 0;
2636             $divisor = 0;
2637             for(my $column =0; $column < $len; $column++) {
2638             my %hash = %{$countHashes[$column]};
2639             $subdivisor = 0;
2640             foreach my $res (keys %hash) {
2641             $total += $hash{$res}*($hash{$res} - 1);
2642             $subdivisor += $hash{$res};
2643             }
2644             $divisor += $subdivisor * ($subdivisor - 1);
2645             }
2646             return $divisor > 0 ? ($total / $divisor )*100.0 : 0;
2647             }
2648              
2649             # from simplealign
2650             sub overall_percentage_identity{
2651             my ($self, $length_measure) = @_;
2652              
2653             my @alphabet = ('A','B','C','D','E','F','G','H','I','J','K','L','M',
2654             'N','O','P','Q','R','S','T','U','V','W','X','Y','Z');
2655              
2656             my ($len, $total, @seqs, @countHashes);
2657              
2658             my %enum = map {$_ => 1} qw (align short long);
2659              
2660             throw 'Generic' => "Unknown argument [$length_measure]"
2661             if $length_measure and not $enum{$length_measure};
2662             $length_measure ||= 'align';
2663              
2664             if (! $self->is_flush()) {
2665             throw 'Generic' => "All sequences in the alignment must be the same length";
2666             }
2667              
2668             @seqs = $self->each_seq();
2669             $len = $self->length();
2670              
2671             # load the each hash with correct keys for existence checks
2672             for( my $index=0; $index < $len; $index++) {
2673             foreach my $letter (@alphabet) {
2674             $countHashes[$index] = {} if not $countHashes[$index];
2675             $countHashes[$index]->{$letter} = 0;
2676             }
2677             }
2678             foreach my $seq (@seqs) {
2679             my @seqChars = split //, $seq->seq();
2680             for( my $column=0; $column < @seqChars; $column++ ) {
2681             my $char = uc($seqChars[$column]);
2682             if (exists $countHashes[$column]->{$char}) {
2683             $countHashes[$column]->{$char}++;
2684             }
2685             }
2686             }
2687              
2688             $total = 0;
2689             for(my $column =0; $column < $len; $column++) {
2690             my %hash = %{$countHashes[$column]};
2691             foreach ( values %hash ) {
2692             next if( $_ == 0 );
2693             $total++ if( $_ == scalar @seqs );
2694             last;
2695             }
2696             }
2697              
2698             if ($length_measure eq 'short') {
2699             ## find the shortest length
2700             $len = 0;
2701             foreach my $seq ($self->each_seq) {
2702             my $count = $seq->seq =~ tr/[A-Za-z]//;
2703             if ($len) {
2704             $len = $count if $count < $len;
2705             } else {
2706             $len = $count;
2707             }
2708             }
2709             }
2710             elsif ($length_measure eq 'long') {
2711             ## find the longest length
2712             $len = 0;
2713             foreach my $seq ($self->each_seq) {
2714             my $count = $seq->seq =~ tr/[A-Za-z]//;
2715             if ($len) {
2716             $len = $count if $count > $len;
2717             } else {
2718             $len = $count;
2719             }
2720             }
2721             }
2722              
2723             return ($total / $len ) * 100.0;
2724             }
2725              
2726             sub column_from_residue_number {
2727             my ( $self, $seqname, $resnumber ) = @_;
2728             my $col;
2729             if ( my $seq = $self->get_by_name($seqname) ) {
2730             my $gap = $seq->get_gap;
2731             my @char = $seq->get_char;
2732             for my $i ( 0 .. $#char ) {
2733             $col++ if $char[$i] ne $gap;
2734             if ( $col + 1 == $resnumber ) {
2735             return $i + 1;
2736             }
2737             }
2738             }
2739             }
2740              
2741             sub displayname {
2742             my ( $self, $name, $disname ) = @_;
2743             my $seq;
2744             $self->visit( sub{ $seq = $_[0] if $_[0]->get_nse eq $name } );
2745             $self->throw("No sequence with name [$name]") unless $seq;
2746             my $disnames = $self->get_generic( 'displaynames' ) || {};
2747             if ( $disname and $name ) {
2748             $disnames->{$name} = $disname;
2749             return $disname;
2750             }
2751             elsif( defined $disnames->{$name} ) {
2752             return $disnames->{$name};
2753             }
2754             else {
2755             return $name;
2756             }
2757             }
2758              
2759             # from SimpleAlign
2760             sub maxdisplayname_length {
2761             my $self = shift;
2762             my $maxname = (-1);
2763             my ($seq,$len);
2764             foreach $seq ( $self->each_seq() ) {
2765             $len = CORE::length $self->displayname($seq->get_nse());
2766             if( $len > $maxname ) {
2767             $maxname = $len;
2768             }
2769             }
2770             return $maxname;
2771             }
2772              
2773             # from SimpleAlign
2774             sub set_displayname_flat {
2775             my $self = shift;
2776             my ($nse,$seq);
2777              
2778             foreach $seq ( $self->each_seq() ) {
2779             $nse = $seq->get_nse();
2780             $self->displayname($nse,$seq->id());
2781             }
2782             return 1;
2783             }
2784              
2785             sub set_displayname_count { $logger->warn }
2786              
2787             sub set_displayname_normal { $logger->warn }
2788              
2789             sub accession {
2790             my ( $self, $acc ) = @_;
2791             if ( defined $acc ) {
2792             $self->set_generic( 'accession' => $acc );
2793             }
2794             return $self->get_generic( 'accession' );
2795             }
2796              
2797             sub source {
2798             my ( $self, $source ) = @_;
2799             if ( defined $source ) {
2800             $self->set_generic( 'source' => $source );
2801             }
2802             return $self->get_generic( 'source' );
2803             }
2804              
2805             sub annotation {
2806             my ( $self, $anno ) = @_;
2807             if ( defined $anno ) {
2808             $self->set_generic( 'annotation' => $anno );
2809             }
2810             return $self->get_generic( 'annotation' );
2811             }
2812              
2813             sub consensus_meta {
2814             my ( $self, $meta ) = @_;
2815             if ( defined $meta ) {
2816             $self->set_generic( 'consensus_meta' => $meta );
2817             }
2818             return $self->get_generic( 'consensus_meta' );
2819             }
2820              
2821             # XXX this might be removed, and instead inherit from SimpleAlign
2822             sub max_metaname_length {
2823             my $self = shift;
2824             my $maxname = (-1);
2825             my ($seq,$len);
2826              
2827             # check seq meta first
2828             for $seq ( $self->each_seq() ) {
2829             next if !$seq->isa('Bio::Seq::MetaI' || !$seq->meta_names);
2830             for my $mtag ($seq->meta_names) {
2831             $len = CORE::length $mtag;
2832             if( $len > $maxname ) {
2833             $maxname = $len;
2834             }
2835             }
2836             }
2837              
2838             # alignment meta
2839             for my $meta ($self->consensus_meta) {
2840             next unless $meta;
2841             for my $name ($meta->meta_names) {
2842             $len = CORE::length $name;
2843             if( $len > $maxname ) {
2844             $maxname = $len;
2845             }
2846             }
2847             }
2848              
2849             return $maxname;
2850             }
2851              
2852              
2853             sub get_SeqFeatures { return }
2854              
2855