File Coverage

lib/Bio/Phylo/Forest/DBTree/Result/Node.pm
Criterion Covered Total %
statement 47 80 58.7
branch 2 8 25.0
condition 0 5 0.0
subroutine 16 27 59.2
pod 17 17 100.0
total 82 137 59.8


line stmt bran cond sub pod time code
1             package Bio::Phylo::Forest::DBTree::Result::Node;
2 2     2   6269 use strict;
  2         3  
  2         48  
3 2     2   6 use warnings;
  2         2  
  2         38  
4 2     2   7 use Bio::Phylo::Forest::DBTree;
  2         1  
  2         40  
5 2     2   33 use Bio::Phylo::Forest::Node;
  2         3  
  2         22  
6 2     2   31 use Bio::Phylo::Util::Logger;
  2         2  
  2         74  
7 2     2   6 use base 'DBIx::Class::Core';
  2         2  
  2         862  
8 2     2   123106 use base 'Bio::Phylo::Forest::Node';
  2         3  
  2         1134  
9              
10             my $log = Bio::Phylo::Util::Logger->new;
11              
12             __PACKAGE__->table("node");
13              
14             =head1 NAME
15              
16             Bio::Phylo::Forest::DBTree::Result::Node - Phylogenetic database record as a node object
17              
18             =head1 SYNOPSIS
19              
20             # same API is Bio::Phylo::Forest::Node
21            
22             =head1 DESCRIPTION
23              
24             This package implements an object-relational interface to records in a phylogenetic
25             database. This way, the record can be used as if it's a tree node with the same
26             programming interface as is used by L, but without making the demands that
27             loading an entire tree in memory would make.
28              
29             =head1 DATABASE ACCESSORS
30              
31             The following methods directly access fields of the database-backed node record. In
32             principle, these methods can also be used as setters, although for the indexes and keys
33             you really need to know what you're doing or the topology of the tree could become
34             irreparably corrupted.
35              
36             =head2 id()
37              
38             Returns primary key of the node object, an integer.
39              
40             =head2 parent()
41              
42             Returns the foreign key of the node object's parent, an integer.
43              
44             =head2 left()
45              
46             Returns the pre-order index of the node object, an integer.
47              
48             =head2 right()
49              
50             Returns the post-order index of the node object, an integer.
51              
52             =head2 name()
53              
54             Returns the node name, a string.
55              
56             =head2 length()
57              
58             Returns the node's branch length, a floating point number.
59              
60             =head2 height()
61              
62             Returns the node's height from the root, a floating point number.
63              
64             =cut
65              
66             __PACKAGE__->add_columns(
67             "id",
68             { data_type => "int", is_nullable => 0 },
69             "parent",
70             { data_type => "int", is_nullable => 0 },
71             "left",
72             { data_type => "int", is_nullable => 1 },
73             "right",
74             { data_type => "int", is_nullable => 1 },
75             "name",
76             { data_type => "string", is_nullable => 0 },
77             "length",
78             { data_type => "float", is_nullable => 0 },
79             "height",
80             { data_type => "float", is_nullable => 1 },
81             );
82             __PACKAGE__->set_primary_key("id");
83              
84             my $schema;
85             sub _schema {
86 7 100   7   32 if ( not $schema ) {
87 1         6 $schema = Bio::Phylo::Forest::DBTree->connect->resultset('Node');
88             }
89 7         321 return $schema;
90             }
91              
92             =head1 NODE METHODS
93              
94             These methods override methods of the same name in L, to make more efficient
95             use of the database.
96              
97             =head2 get_parent()
98              
99             Returns the node's parent, if any.
100              
101             =cut
102              
103             sub get_parent {
104 0     0 1 0 my $self = shift;
105 0         0 return $self->_schema->find($self->parent);
106             }
107              
108             =head2 get_children_rs()
109              
110             Returns the node's children, if any, as a L result set.
111              
112             =cut
113              
114             sub get_children_rs {
115 2     2 1 4 my $self = shift;
116 2         37 my $id = $self->id;
117 2         21 return $self->_schema->search({
118             '-and' => [
119             'parent' => { '==' => $id },
120             'id' => { '!=' => $id },
121             ]
122             });
123             }
124              
125             =head2 get_children()
126              
127             Returns the node's children, if any, as an array reference.
128              
129             =cut
130              
131 2     2 1 111 sub get_children { [ shift->get_children_rs->all ] }
132              
133             =head2 get_descendants_rs()
134              
135             Returns the node's descendants, if any, as a L result set.
136              
137             =cut
138              
139             sub get_descendants_rs {
140 0     0 1 0 my $self = shift;
141 0         0 return $self->_schema->search(
142             {
143             '-and' => [
144             'left' => { '>' => $self->left },
145             'right' => { '<' => $self->right },
146             ]
147             }
148             )
149             }
150              
151             =head2 get_descendants()
152              
153             Returns the node's descendants, if any, as an array reference.
154              
155             =cut
156              
157 0     0 1 0 sub get_descendants { [ shift->get_descendants_rs->all ] }
158              
159             =head2 get_terminals_rs()
160              
161             Returns the node's descendant tips, if any, as a L result set.
162              
163             =cut
164              
165             sub get_terminals_rs {
166 0     0 1 0 my $self = shift;
167 0         0 my $scalar = 'right';
168 0         0 return $self->_schema->search(
169             {
170             '-and' => [
171             'left' => { '>' => $self->left },
172             'right' => { '<' => $self->right },
173             'left' => { '==' => \$scalar },
174             ]
175             }
176             )
177             }
178              
179             =head2 get_terminals()
180              
181             Returns the node's descendant tips, if any, as an array reference.
182              
183             =cut
184              
185 0     0 1 0 sub get_terminals { [ shift->get_terminals_rs->all ] }
186              
187             =head2 get_internals_rs()
188              
189             Returns the node's descendant internal nodes, if any, as a L result set.
190              
191             =cut
192              
193             sub get_internals_rs {
194 0     0 1 0 my $self = shift;
195 0         0 my $scalar = 'right';
196 0         0 return $self->_schema->search(
197             {
198             '-and' => [
199             'left' => { '>' => $self->left },
200             'right' => { '<' => $self->right },
201             'left' => { '!=' => \$scalar },
202             ]
203             }
204             )
205             }
206              
207             =head2 get_internals()
208              
209             Returns the node's descendant internal nodes, if any, as an array reference.
210              
211             =cut
212              
213 0     0 1 0 sub get_internals { [ shift->get_internals_rs->all ] }
214              
215             =head2 get_ancestors_rs()
216              
217             Returns the node's ancestors, if any, as a L result set.
218              
219             =cut
220              
221             sub get_ancestors_rs {
222 2     2 1 3 my $self = shift;
223 2         6 return $self->_schema->search(
224             {
225             '-and' => [
226             'left' => { '<' => $self->left },
227             'right' => { '>' => $self->right },
228             ]
229             }
230             )
231             }
232              
233             =head2 get_ancestors()
234              
235             Returns the node's ancestors, if any, as an array ref.
236              
237             =cut
238              
239 2     2 1 3825 sub get_ancestors { [ shift->get_ancestors_rs->all ] }
240              
241             =head2 get_mrca()
242              
243             Given another node in the same tree, returns the most recent common ancestor of the two.
244              
245             =cut
246              
247             sub get_mrca {
248 3     3 1 3724 my ( $self, $other ) = @_;
249 3         58 my @lefts = sort { $a <=> $b } $self->left, $other->left;
  3         80  
250 3         47 my @rights = sort { $a <=> $b } $self->right, $other->right;
  3         91  
251 3         9 return $self->_schema->search(
252             {
253             '-and' => [
254             'left' => { '<' => $lefts[0] },
255             'right' => { '>' => $rights[1] },
256             ]
257             },
258             {
259             'order_by' => 'right',
260             'rows' => 1,
261             }
262             )->single;
263             }
264              
265             {
266 2     2   10 no warnings 'recursion';
  2         4  
  2         568  
267             sub _index {
268 0     0   0 my ( $self, $counter, $height ) = @_;
269 0   0     0 $height += ( $self->get_branch_length || 0 );
270            
271             # initialize or update counter
272 0 0       0 if ( ref($counter) eq 'SCALAR' ) {
273 0         0 $$counter = $$counter + 1;
274             }
275             else {
276 0         0 my $i = 1;
277 0         0 $counter = \$i;
278             }
279            
280             # report progress
281 0 0       0 if ( not $$counter % 1000 ) {
282 0         0 $log->info("updated index ".$$counter);
283             }
284            
285             # update and recurse
286 0         0 $self->update({ 'left' => $$counter, 'height' => $height });
287 0         0 my @c = @{ $self->get_children };
  0         0  
288 0         0 for my $child ( @c ) {
289 0         0 $child->_index($counter, $height);
290             }
291 0 0       0 if ( @c ) {
292 0         0 $$counter = $$counter + 1;
293             }
294 0         0 $self->update({ 'right' => $$counter });
295             }
296             }
297              
298             =head2 get_id()
299              
300             Same as C, see above.
301              
302             =cut
303              
304 17     17 1 6967 sub get_id { shift->id }
305              
306             =head2 get_name()
307              
308             Same as C, see above.
309              
310             =cut
311              
312 0     0 1 0 sub get_name { shift->name }
313              
314             =head2 get_branch_length()
315              
316             Same as C, see above.
317              
318             =cut
319              
320 0     0 1 0 sub get_branch_length { shift->length }
321              
322             =head2 is_descendant_of()
323              
324             Given another node, determines of the invocant is the descendant of the argument.
325              
326             =cut
327              
328             sub is_descendant_of {
329 0     0 1 0 my ( $self, $other ) = @_;
330 0   0     0 return ( $self->left > $other->left ) && ( $self->right < $other->right );
331             }
332              
333             =head2 calc_patristic_distance()
334              
335             Given another node, calculates the patristic distance between the two.
336              
337             =cut
338              
339             sub calc_patristic_distance {
340 1     1 1 343 my ( $self, $other ) = @_;
341 1         4 my $mrca = $self->get_mrca($other);
342 1         2149 my $mh = $mrca->height;
343 1         60 my $sh = $self->height;
344 1         20 my $oh = $other->height;
345 1         8 return ( $sh - $mh ) + ( $oh - $mh );
346             }
347              
348             1;