line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Bio/RNA/Treekin/Record.pm |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Stores a data from a single row of the Treekin file, i.e. the populations of |
4
|
|
|
|
|
|
|
# all minima at a given time point. |
5
|
|
|
|
|
|
|
package Bio::RNA::Treekin::Record; |
6
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
53
|
use v5.14; # required for non-destructive subst m///r |
|
4
|
|
|
|
|
15
|
|
9
|
4
|
|
|
4
|
|
20
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
96
|
|
10
|
4
|
|
|
4
|
|
23
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
114
|
|
11
|
|
|
|
|
|
|
|
12
|
4
|
|
|
4
|
|
2407
|
use Moose; |
|
4
|
|
|
|
|
1943792
|
|
|
4
|
|
|
|
|
30
|
|
13
|
4
|
|
|
4
|
|
36028
|
use MooseX::StrictConstructor; |
|
4
|
|
|
|
|
129236
|
|
|
4
|
|
|
|
|
17
|
|
14
|
4
|
|
|
4
|
|
42001
|
use namespace::autoclean; |
|
4
|
|
|
|
|
13
|
|
|
4
|
|
|
|
|
29
|
|
15
|
|
|
|
|
|
|
|
16
|
4
|
|
|
4
|
|
2129
|
use autodie qw(:all); |
|
4
|
|
|
|
|
47786
|
|
|
4
|
|
|
|
|
24
|
|
17
|
4
|
|
|
4
|
|
83003
|
use Scalar::Util qw(reftype openhandle); |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
334
|
|
18
|
4
|
|
|
4
|
|
28
|
use List::Util qw(first pairmap max uniqnum all); |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
305
|
|
19
|
4
|
|
|
4
|
|
26
|
use Carp qw(croak); |
|
4
|
|
|
|
|
18
|
|
|
4
|
|
|
|
|
183
|
|
20
|
|
|
|
|
|
|
|
21
|
4
|
|
|
4
|
|
2533
|
use Bio::RNA::Treekin::PopulationDataRecord; |
|
4
|
|
|
|
|
21
|
|
|
4
|
|
|
|
|
293
|
|
22
|
|
|
|
|
|
|
|
23
|
4
|
|
|
4
|
|
38
|
use overload '""' => \&stringify; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
46
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
has '_population_data' => ( |
27
|
|
|
|
|
|
|
is => 'ro', |
28
|
|
|
|
|
|
|
required => 1, |
29
|
|
|
|
|
|
|
init_arg => 'population_data', |
30
|
|
|
|
|
|
|
); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
has 'date' => (is => 'ro', required => 1); |
33
|
|
|
|
|
|
|
has 'sequence' => (is => 'ro', required => 1); |
34
|
|
|
|
|
|
|
has 'method' => (is => 'ro', required => 1); |
35
|
|
|
|
|
|
|
has 'start_time' => (is => 'ro', required => 1); |
36
|
|
|
|
|
|
|
has 'stop_time' => (is => 'ro', required => 1); |
37
|
|
|
|
|
|
|
has 'temperature' => (is => 'ro', required => 1); |
38
|
|
|
|
|
|
|
has 'basename' => (is => 'ro', required => 1); |
39
|
|
|
|
|
|
|
has 'time_increment' => (is => 'ro', required => 1); |
40
|
|
|
|
|
|
|
has 'degeneracy' => (is => 'ro', required => 1); |
41
|
|
|
|
|
|
|
has 'absorbing_state' => (is => 'ro', required => 1); |
42
|
|
|
|
|
|
|
has 'states_limit' => (is => 'ro', required => 1); |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# Add optional attributes including predicate. |
45
|
|
|
|
|
|
|
has $_ => ( |
46
|
|
|
|
|
|
|
is => 'ro', |
47
|
|
|
|
|
|
|
required => 0, |
48
|
|
|
|
|
|
|
predicate => "has_$_", |
49
|
|
|
|
|
|
|
) |
50
|
|
|
|
|
|
|
foreach qw( |
51
|
|
|
|
|
|
|
info |
52
|
|
|
|
|
|
|
init_population |
53
|
|
|
|
|
|
|
rates_file |
54
|
|
|
|
|
|
|
file_index |
55
|
|
|
|
|
|
|
cmd |
56
|
|
|
|
|
|
|
of_iterations |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# Get number of population data rows stored. |
60
|
|
|
|
|
|
|
sub population_data_count { |
61
|
2
|
|
|
2
|
1
|
4412
|
my ($self) = @_; |
62
|
|
|
|
|
|
|
|
63
|
2
|
|
|
|
|
4
|
my $data_count = @{ $self->_population_data }; |
|
2
|
|
|
|
|
128
|
|
64
|
2
|
|
|
|
|
11
|
return $data_count; |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# Number of states / minima in this simulation. |
68
|
|
|
|
|
|
|
# Get number of mins in the first population record; it should be the |
69
|
|
|
|
|
|
|
# same for all records. |
70
|
|
|
|
|
|
|
sub min_count { |
71
|
15
|
|
|
15
|
1
|
27
|
my $self = shift; |
72
|
|
|
|
|
|
|
|
73
|
15
|
|
|
|
|
49
|
my $first_pop = $self->population(0); |
74
|
15
|
50
|
|
|
|
42
|
confess 'min_count: no population data present' |
75
|
|
|
|
|
|
|
unless defined $first_pop; |
76
|
|
|
|
|
|
|
|
77
|
15
|
|
|
|
|
48
|
my $min_count = $first_pop->min_count; |
78
|
|
|
|
|
|
|
|
79
|
15
|
|
|
|
|
53
|
return $min_count; |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# Return a list of all minima, i. e. 1..n, where n is the total number of |
83
|
|
|
|
|
|
|
# minima. |
84
|
|
|
|
|
|
|
sub mins { |
85
|
0
|
|
|
0
|
1
|
0
|
my ($self) = @_; |
86
|
0
|
|
|
|
|
0
|
my @mins = 1..$self->min_count; |
87
|
|
|
|
|
|
|
|
88
|
0
|
|
|
|
|
0
|
return @mins; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# Keep only the population data for the selected minima, remove all other. |
92
|
|
|
|
|
|
|
# Will NOT rescale populations, so they may no longer sum up to 1. |
93
|
|
|
|
|
|
|
# Arguments: |
94
|
|
|
|
|
|
|
# mins: List of mins to keep. Will be sorted and uniq'ed (cf. splice()). |
95
|
|
|
|
|
|
|
# Returns the return value of splice(). |
96
|
|
|
|
|
|
|
sub keep_mins { |
97
|
0
|
|
|
0
|
1
|
0
|
my ($self, @kept_mins) = @_; |
98
|
0
|
|
|
|
|
0
|
@kept_mins = uniqnum sort {$a <=> $b} @kept_mins; # sort / uniq'ify |
|
0
|
|
|
|
|
0
|
|
99
|
0
|
|
|
|
|
0
|
return $self->splice_mins(@kept_mins); |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Keep only the population data for the selected minima, remove all other. |
103
|
|
|
|
|
|
|
# May duplicate and re-order. |
104
|
|
|
|
|
|
|
# mins: List of mins to keep. Will be used as is. |
105
|
|
|
|
|
|
|
# Returns itself. |
106
|
|
|
|
|
|
|
sub splice_mins { |
107
|
0
|
|
|
0
|
1
|
0
|
my ($self, @kept_mins) = @_; |
108
|
|
|
|
|
|
|
|
109
|
0
|
|
|
|
|
0
|
my $min_count = $self->min_count; |
110
|
|
|
|
|
|
|
confess 'Cannot splice, minimum out of bounds' |
111
|
0
|
0
|
|
0
|
|
0
|
unless all {$_ >= 1 and $_ <= $min_count} @kept_mins; |
|
0
|
0
|
|
|
|
0
|
|
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Directly update raw population data here instead of doing tons of |
114
|
|
|
|
|
|
|
# calls passing the same min array. |
115
|
0
|
|
|
|
|
0
|
my @kept_indices = map {$_ - 1} @kept_mins; |
|
0
|
|
|
|
|
0
|
|
116
|
0
|
|
|
|
|
0
|
for my $pop_data (@{$self->_population_data}) { # each point in time |
|
0
|
|
|
|
|
0
|
|
117
|
0
|
|
|
|
|
0
|
my $raw_pop_data = $pop_data->_populations; |
118
|
0
|
|
|
|
|
0
|
@{$raw_pop_data} = @{$raw_pop_data}[@kept_indices]; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
0
|
|
|
|
|
0
|
return $self; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# Get the maximal population for the given minimum over all time points. |
125
|
|
|
|
|
|
|
sub max_pop_of_min { |
126
|
0
|
|
|
0
|
1
|
0
|
my ($self, $min) = @_; |
127
|
0
|
|
|
|
|
0
|
my $max_pop = '-Inf'; |
128
|
0
|
|
|
|
|
0
|
for my $pop_data (@{$self->_population_data}) { # each point in time |
|
0
|
|
|
|
|
0
|
|
129
|
0
|
|
|
|
|
0
|
$max_pop = max $max_pop, $pop_data->of_min($min); # update max |
130
|
|
|
|
|
|
|
} |
131
|
0
|
|
|
|
|
0
|
return $max_pop; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# For a given minimum, return all population values in chronological |
135
|
|
|
|
|
|
|
# order. |
136
|
|
|
|
|
|
|
# Arguments: |
137
|
|
|
|
|
|
|
# min: Minimum for which to collect the population data. |
138
|
|
|
|
|
|
|
# Returns a list of population values in chronological order. |
139
|
|
|
|
|
|
|
sub pops_of_min { |
140
|
0
|
|
|
0
|
1
|
0
|
my ($self, $min) = @_; |
141
|
|
|
|
|
|
|
|
142
|
0
|
|
|
|
|
0
|
my @pops_of_min = map { $_->of_min($min) } $self->populations; |
|
0
|
|
|
|
|
0
|
|
143
|
|
|
|
|
|
|
|
144
|
0
|
|
|
|
|
0
|
return @pops_of_min; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Final population data record, i.e. the result of the simulation. |
148
|
|
|
|
|
|
|
sub final_population { |
149
|
0
|
|
|
0
|
1
|
0
|
my ($self) = @_; |
150
|
|
|
|
|
|
|
|
151
|
0
|
|
|
|
|
0
|
my $final_population_data |
152
|
|
|
|
|
|
|
= $self->population($self->population_data_count - 1); |
153
|
|
|
|
|
|
|
|
154
|
0
|
|
|
|
|
0
|
return $final_population_data; |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# Get the i-th population data record (0-based indexing). |
158
|
|
|
|
|
|
|
sub population { |
159
|
52
|
|
|
52
|
1
|
22164
|
my ($self, $i) = @_; |
160
|
|
|
|
|
|
|
|
161
|
52
|
|
|
|
|
1974
|
my $population_record = $self->_population_data->[$i]; |
162
|
52
|
|
|
|
|
130
|
return $population_record; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Return all population data records. |
166
|
|
|
|
|
|
|
sub populations { |
167
|
0
|
|
|
0
|
1
|
0
|
return @{ $_[0]->_population_data }; |
|
0
|
|
|
|
|
0
|
|
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Add a new minimum with all-zero entries. Data can then be appended to |
171
|
|
|
|
|
|
|
# this new min. |
172
|
|
|
|
|
|
|
# Returns the index of the new minimum. |
173
|
|
|
|
|
|
|
sub add_min { |
174
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
175
|
0
|
|
|
|
|
0
|
my $new_min_count = $self->min_count + 1; |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# Increase the min count of all population data records by one. |
178
|
|
|
|
|
|
|
$_->set_min_count($new_min_count) |
179
|
0
|
|
|
|
|
0
|
foreach @{ $self->_population_data }, $self->init_population; |
|
0
|
|
|
|
|
0
|
|
180
|
|
|
|
|
|
|
|
181
|
0
|
|
|
|
|
0
|
return $new_min_count; # count == highest index |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# Given a list of population data records, append them to the population data |
185
|
|
|
|
|
|
|
# of this record. The columns of the added data can be re-arranged on the fly by |
186
|
|
|
|
|
|
|
# providing a mapping (hash ref) giving for each minimum in the population |
187
|
|
|
|
|
|
|
# data to be added (key) a minimum in the current population data |
188
|
|
|
|
|
|
|
# (value) to which the new minimum should be swapped. If no data is provided |
189
|
|
|
|
|
|
|
# for some minimum of this record, its population is set to zero in the |
190
|
|
|
|
|
|
|
# newly added entries. |
191
|
|
|
|
|
|
|
# Arguments: |
192
|
|
|
|
|
|
|
# pop_data_ref: ref to the array of population data to be added |
193
|
|
|
|
|
|
|
# append_to_min_ref: |
194
|
|
|
|
|
|
|
# hash ref describing which mininum in pop_data_ref (key) |
195
|
|
|
|
|
|
|
# should be mapped to which minimum in this record (value) |
196
|
|
|
|
|
|
|
# The passed population data objects are modified. |
197
|
|
|
|
|
|
|
sub append_pop_data { |
198
|
0
|
|
|
0
|
1
|
0
|
my ($self, $pop_data_ref, $append_to_min_ref) = @_; |
199
|
|
|
|
|
|
|
|
200
|
0
|
0
|
|
|
|
0
|
if (defined $append_to_min_ref) { |
201
|
0
|
|
|
|
|
0
|
my $min_count = $self->min_count; |
202
|
0
|
|
|
|
|
0
|
$_->transform($append_to_min_ref, $min_count) foreach @$pop_data_ref; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
|
|
0
|
push @{ $self->_population_data }, @$pop_data_ref; |
|
0
|
|
|
|
|
0
|
|
206
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
0
|
return; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Decode a single header line into a key and a value, which are returned. |
211
|
|
|
|
|
|
|
sub _get_header_line_key_value { |
212
|
150
|
|
|
150
|
|
374
|
my ($class, $header_line) = @_; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# key and value separated by first ':' (match non-greedy!) |
215
|
150
|
|
|
|
|
670
|
my ($key, $value) = $header_line =~ m{ ^ ( .+? ) : [ ] ( .* ) $ }x; |
216
|
|
|
|
|
|
|
|
217
|
150
|
50
|
|
|
|
334
|
confess "Invalid key in header line:\n$header_line" |
218
|
|
|
|
|
|
|
unless defined $key; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# Convert key to lower case and replace spaces by underscores. |
221
|
150
|
|
|
|
|
503
|
$key = (lc $key) =~ s/\s+/_/gr; |
222
|
|
|
|
|
|
|
|
223
|
150
|
|
|
|
|
437
|
return ($key, $value); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# Decode the initial population from the Treekin command line. The |
227
|
|
|
|
|
|
|
# population is given as multiple --p0 a=x switches, where a is the state |
228
|
|
|
|
|
|
|
# index and x is the fraction of population initially present in this |
229
|
|
|
|
|
|
|
# state. |
230
|
|
|
|
|
|
|
# Arguments: |
231
|
|
|
|
|
|
|
# command: the command line string used to call treekin |
232
|
|
|
|
|
|
|
# Returns a hash ref containing the initial population of each state a at |
233
|
|
|
|
|
|
|
# position a (1-based). |
234
|
|
|
|
|
|
|
sub _parse_init_population_from_cmd { |
235
|
7
|
|
|
7
|
|
20
|
my ($class, $command) = @_; |
236
|
|
|
|
|
|
|
|
237
|
7
|
|
|
|
|
97
|
my @command_parts = split /\s+/, $command; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# Extract the initial population strings given as (multiple) arguments |
240
|
|
|
|
|
|
|
# --p0 to Treekin from the Treekin command. |
241
|
7
|
|
|
|
|
19
|
my @init_population_strings; |
242
|
7
|
|
|
|
|
21
|
while (@command_parts) { |
243
|
116
|
100
|
|
|
|
259
|
if (shift @command_parts eq '--p0') { |
244
|
|
|
|
|
|
|
# Next value should be a population value. |
245
|
32
|
50
|
|
|
|
63
|
confess 'No argument following a --p0 switch' |
246
|
|
|
|
|
|
|
unless @command_parts; |
247
|
32
|
|
|
|
|
68
|
push @init_population_strings, shift @command_parts; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Store population of state i in index i-1. |
252
|
7
|
|
|
|
|
15
|
my @init_population; |
253
|
7
|
|
|
|
|
19
|
foreach my $init_population_string (@init_population_strings) { |
254
|
32
|
|
|
|
|
119
|
my ($state, $population) = split /=/, $init_population_string; |
255
|
32
|
|
|
|
|
80
|
$init_population[$state-1] = $population; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# If no population was specified on the cmd line, init 100% in state 1 |
259
|
7
|
100
|
|
|
|
21
|
$init_population[0] = 1 unless @init_population_strings; |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# Set undefined states to zero. |
262
|
7
|
|
50
|
|
|
105
|
$_ //= 0. foreach @init_population; |
263
|
|
|
|
|
|
|
|
264
|
7
|
|
|
|
|
394
|
my $init_population_record |
265
|
|
|
|
|
|
|
= Bio::RNA::Treekin::PopulationDataRecord->new( |
266
|
|
|
|
|
|
|
time => 0, |
267
|
|
|
|
|
|
|
populations => \@init_population, |
268
|
|
|
|
|
|
|
); |
269
|
7
|
|
|
|
|
23
|
return $init_population_record; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub _parse_header_lines { |
273
|
10
|
|
|
10
|
|
28
|
my ($class, $header_lines_ref) = @_; |
274
|
|
|
|
|
|
|
|
275
|
10
|
|
|
|
|
19
|
my @header_args; |
276
|
10
|
|
|
|
|
37
|
foreach my $line (@$header_lines_ref) { |
277
|
150
|
|
|
|
|
344
|
my ($key, $value) = $class->_get_header_line_key_value($line); |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Implement special handling for certain keys. |
280
|
150
|
100
|
|
|
|
374
|
if ($key eq 'rates_file') { |
|
|
100
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# remove (#index) from file name and store the value |
282
|
7
|
|
|
|
|
47
|
my ($file_name, $file_index) |
283
|
|
|
|
|
|
|
= $value =~ m{ ^ (.+) [ ] [(] [#] (\d+) [)] $ }x; |
284
|
7
|
|
|
|
|
28
|
push @header_args, ( |
285
|
|
|
|
|
|
|
rates_file => $file_name, |
286
|
|
|
|
|
|
|
file_index => $file_index, |
287
|
|
|
|
|
|
|
); |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
elsif ($key eq 'cmd') { |
290
|
|
|
|
|
|
|
# Extract initial population from Treekin command. |
291
|
7
|
|
|
|
|
25
|
my $init_population_ref |
292
|
|
|
|
|
|
|
= $class->_parse_init_population_from_cmd($value); |
293
|
7
|
|
|
|
|
24
|
push @header_args, ( |
294
|
|
|
|
|
|
|
cmd => $value, |
295
|
|
|
|
|
|
|
init_population => $init_population_ref, |
296
|
|
|
|
|
|
|
); |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
else { |
299
|
|
|
|
|
|
|
# For the rest, just push key and value as constructor args. |
300
|
136
|
|
|
|
|
356
|
push @header_args, ($key => $value); |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
} |
303
|
10
|
|
|
|
|
101
|
return @header_args; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
# Read all lines from the given handle and separate it into header lines |
308
|
|
|
|
|
|
|
# and data lines. |
309
|
|
|
|
|
|
|
sub _read_record_lines { |
310
|
10
|
|
|
10
|
|
29
|
my ($class, $record_handle) = @_; |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# Separate lines into header and population data. All header lines |
313
|
|
|
|
|
|
|
# begin with a '# ' (remove it!) |
314
|
|
|
|
|
|
|
# Note: Newer versions of treekin also add header info *below* data lines. |
315
|
10
|
|
|
|
|
21
|
my ($current_line, @header_lines, @population_data_lines); |
316
|
10
|
|
|
|
|
254
|
while (defined ($current_line = <$record_handle>)) { |
317
|
342
|
50
|
33
|
|
|
3376
|
next if $current_line =~ /^@/ # drop xmgrace annotations |
318
|
|
|
|
|
|
|
or $current_line =~ m{ ^ \s* $ }x; # or empty lines |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# Header lines start with '# ', remove it. |
321
|
342
|
100
|
|
|
|
1049
|
if ($current_line =~ s/^# //) { # header line |
322
|
150
|
|
|
|
|
795
|
push @header_lines, $current_line; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
else { # data line |
325
|
192
|
|
|
|
|
736
|
push @population_data_lines, $current_line; |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
# Sanity checks. |
330
|
10
|
50
|
|
|
|
96
|
confess 'No header lines found in Treekin file' |
331
|
|
|
|
|
|
|
unless @header_lines; |
332
|
10
|
|
|
|
|
57
|
chomp @header_lines; |
333
|
|
|
|
|
|
|
|
334
|
10
|
50
|
|
|
|
34
|
confess 'No population data lines found in Treekin file' |
335
|
|
|
|
|
|
|
unless @population_data_lines; |
336
|
10
|
|
|
|
|
41
|
chomp @population_data_lines; |
337
|
|
|
|
|
|
|
|
338
|
10
|
|
|
|
|
64
|
return \@header_lines, \@population_data_lines; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub _parse_population_data_lines { |
342
|
10
|
|
|
10
|
|
25
|
my ($class, $population_data_lines_ref) = @_; |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
my @population_data |
345
|
10
|
|
|
|
|
26
|
= map { Bio::RNA::Treekin::PopulationDataRecord->new($_) } |
|
161
|
|
|
|
|
5610
|
|
346
|
|
|
|
|
|
|
@$population_data_lines_ref |
347
|
|
|
|
|
|
|
; |
348
|
|
|
|
|
|
|
|
349
|
9
|
|
|
|
|
71
|
return (population_data => \@population_data); |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
around BUILDARGS => sub { |
353
|
|
|
|
|
|
|
my $orig = shift; |
354
|
|
|
|
|
|
|
my $class = shift; |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
# Call original constructor if passed more than one arg. |
357
|
|
|
|
|
|
|
return $class->$orig(@_) unless @_ == 1; |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# Retrive file handle or pass on hash ref to constructor. |
360
|
|
|
|
|
|
|
my $record_handle; |
361
|
|
|
|
|
|
|
if (reftype $_[0]) { |
362
|
|
|
|
|
|
|
if (reftype $_[0] eq reftype {}) { # arg hash passed, |
363
|
|
|
|
|
|
|
return $class->$orig(@_); # pass on as is |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
elsif (reftype $_[0] eq reftype \*STDIN) { # file handle passed |
366
|
|
|
|
|
|
|
$record_handle = shift; |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
else { |
369
|
|
|
|
|
|
|
croak 'Invalid ref type passed to constructor'; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
else { # file name passed |
373
|
|
|
|
|
|
|
my $record_file = shift; |
374
|
|
|
|
|
|
|
open $record_handle, '<', $record_file; |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
# Read in file. |
378
|
|
|
|
|
|
|
my ($header_lines_ref, $population_data_lines_ref) |
379
|
|
|
|
|
|
|
= $class->_read_record_lines($record_handle); |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# Parse file. |
382
|
|
|
|
|
|
|
my @header_args = $class->_parse_header_lines($header_lines_ref); |
383
|
|
|
|
|
|
|
my @data_args |
384
|
|
|
|
|
|
|
= $class->_parse_population_data_lines($population_data_lines_ref); |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
my %args = (@header_args, @data_args); |
387
|
|
|
|
|
|
|
return $class->$orig(\%args); |
388
|
|
|
|
|
|
|
}; |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
sub BUILD { |
391
|
9
|
|
|
9
|
0
|
19
|
my $self = shift; |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# Force construction despite laziness. |
394
|
9
|
|
|
|
|
35
|
$self->min_count; |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# Adjust min count of initial population as it was not known when |
397
|
|
|
|
|
|
|
# initial values were extracted from Treekin cmd. |
398
|
9
|
100
|
|
|
|
349
|
$self->init_population->set_min_count( $self->min_count ) |
399
|
|
|
|
|
|
|
if $self->has_init_population; |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
sub stringify { |
403
|
8
|
|
|
8
|
1
|
189
|
my $self = shift; |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
# Format header line value of rates file entry. |
406
|
|
|
|
|
|
|
my $make_rates_file_val = sub { |
407
|
6
|
|
|
6
|
|
173
|
$self->rates_file . ' (#' . $self->file_index . ')'; |
408
|
8
|
|
|
|
|
40
|
}; |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
# Header |
411
|
8
|
100
|
|
|
|
310
|
my @header_entries = ( |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
412
|
|
|
|
|
|
|
$self->has_rates_file ? ('Rates file' => $make_rates_file_val->()) : (), |
413
|
|
|
|
|
|
|
$self->has_info ? ('Info' => $self->info) : (), |
414
|
|
|
|
|
|
|
$self->has_cmd ? ('Cmd' => $self->cmd) : (), |
415
|
|
|
|
|
|
|
'Date' => $self->date, |
416
|
|
|
|
|
|
|
'Sequence' => $self->sequence, |
417
|
|
|
|
|
|
|
'Method' => $self->method, |
418
|
|
|
|
|
|
|
'Start time' => $self->start_time, |
419
|
|
|
|
|
|
|
'Stop time' => $self->stop_time, |
420
|
|
|
|
|
|
|
'Temperature' => $self->temperature, |
421
|
|
|
|
|
|
|
'Basename' => $self->basename, |
422
|
|
|
|
|
|
|
'Time increment' => $self->time_increment, |
423
|
|
|
|
|
|
|
'Degeneracy' => $self->degeneracy, |
424
|
|
|
|
|
|
|
'Absorbing state' => $self->absorbing_state, |
425
|
|
|
|
|
|
|
'States limit' => $self->states_limit, |
426
|
|
|
|
|
|
|
); |
427
|
|
|
|
|
|
|
|
428
|
8
|
|
|
102
|
|
80
|
my $header_str = join "\n", pairmap { "# $a: $b" } @header_entries; |
|
102
|
|
|
|
|
230
|
|
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
# Population data |
431
|
|
|
|
|
|
|
my $population_str |
432
|
8
|
|
|
|
|
39
|
= join "\n", map { "$_" } @{ $self->_population_data }; |
|
132
|
|
|
|
|
401
|
|
|
8
|
|
|
|
|
258
|
|
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# Footer (new Treekin versions only). |
435
|
8
|
100
|
|
|
|
320
|
my $footer_str = $self->has_of_iterations |
436
|
|
|
|
|
|
|
? '# of iterations: ' . $self->of_iterations |
437
|
|
|
|
|
|
|
: q{}; |
438
|
|
|
|
|
|
|
|
439
|
8
|
|
|
|
|
90
|
my $self_as_str = $header_str . "\n" . $population_str; |
440
|
8
|
100
|
|
|
|
31
|
$self_as_str .= "\n" . $footer_str if $footer_str; |
441
|
|
|
|
|
|
|
|
442
|
8
|
|
|
|
|
117
|
return $self_as_str; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
1; # End of Bio::RNA::Treekin::Record |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
__END__ |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=pod |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=encoding UTF-8 |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
=head1 NAME |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
Bio::RNA::Treekin::Record - Parse, query, and manipulate I<Treekin> output. |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head1 SYNOPSIS |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
use Bio::RNA::Treekin; |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
=head1 DESCRIPTION |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
Parses a regular output file of I<Treekin>. Allows to query population data |
468
|
|
|
|
|
|
|
as well as additional info from the header. New minima can be generated. The |
469
|
|
|
|
|
|
|
stringification returns, again, a valid I<Treekin> file which can be, e. g., |
470
|
|
|
|
|
|
|
visualized using I<Grace>. |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
These attributes of the class allow to query various data from the header of |
475
|
|
|
|
|
|
|
the input file. |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=head2 date |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
The time and date of the I<Treekin> run. |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=head2 sequence |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
The RNA sequence for which the simulation was computed. |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head2 method |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
The method used to build the transition matrix as documented for the |
488
|
|
|
|
|
|
|
C<--method> switch of I<Treekin>. |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
=head2 start_time |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
Initial time of the simulation. |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=head2 stop_time |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
Time at which the simulation stops. |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=head2 temperature |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
Temperature of the simulation in degrees Celsius. |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
=head2 basename |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
Name of the input file. May be C<< <stdin> >> if data was read from standard |
505
|
|
|
|
|
|
|
input. |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=head2 time_increment |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
Factor by which the time is multiplied in each simulation step (roughly, the |
510
|
|
|
|
|
|
|
truth is more complicated). |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=head2 degeneracy |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Whether to consider degeneracy in transition rates. |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=head2 absorbing_state |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
The states specified as absorbing do not have any outgoing transitions and |
519
|
|
|
|
|
|
|
thus serve as "population sinks" during the simulation. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=head2 states_limit |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
Maximum number of states (???). Value is always (?) 2^31 = 2147483647. |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
=head2 info |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
A free text field containing additional comments. |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
Only available in I<some> of the records of a I<BarMap> multi-record file. Use |
530
|
|
|
|
|
|
|
predicate C<has_info> to check whether this attribute is available. |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
=head2 init_population |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
The initial population specified by the user. This information is extracted |
535
|
|
|
|
|
|
|
from the C<cmd> attribute. |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
Only available in I<BarMap>'s multi-record files. Use predicate |
538
|
|
|
|
|
|
|
C<has_init_population> to check whether this attribute is available. |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=head2 rates_file |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
The file that the rate matrix was read from. |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
Only available in I<BarMap>'s multi-record files. Use predicate |
545
|
|
|
|
|
|
|
C<has_rates_file> to check whether this attribute is available. |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
=head2 file_index |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
Zero-based index given to the input files in the order they were read. |
550
|
|
|
|
|
|
|
Extracted from the C<rates_file> attribute. |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
Use predicate C<has_file_index> to check whether this attribute is available. |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=head2 cmd |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
The command used to invoke I<Treekin>. Only available in I<BarMap>'s |
557
|
|
|
|
|
|
|
multi-record files. |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
Use predicate C<has_cmd> to check whether this attribute is available. |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
=head1 METHODS |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
These methods allow the construction, querying and manipulation of the record |
565
|
|
|
|
|
|
|
objects and its population data. |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
=head2 Bio::RNA::Treekin::Record->new($treekin_file) |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=head2 Bio::RNA::Treekin::Record->new($treekin_handle) |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
Construct a new record from a (single) I<Treekin> file. |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=head2 $record->population_data_count |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
Return the number of population data records, i. e. the number of simulated |
576
|
|
|
|
|
|
|
time steps, including the start time. |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
=head2 $record->min_count |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
Return the number of minima. |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=head2 $record->mins |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
Return the list of all contained minima, i. e. C<< 1...$record->min_count >> |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=head2 $record->keep_mins(@kept_minima) |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
Remove all minima but the ones from C<@kept_minima>. The list is sorted and |
589
|
|
|
|
|
|
|
de-duplicated first. |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=head2 $record->splice_mins(@kept_minima) |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
Like C<keep_mins()>, but do not sort / de-duplicate, but use C<@kept_minima> |
594
|
|
|
|
|
|
|
as is. This can be used to remove, duplicate or reorder minima. |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=head2 $record->max_pop_of_min($minimum) |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
Get the maximum population value of all time points for a specific C<$minimum>. |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
=head2 $record->pops_of_min($minimum) |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
Get a list of the populations at all time points (in chronological order) for |
603
|
|
|
|
|
|
|
a single C<$minimum>. |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
=head2 $record->final_population |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
Get the last population data record, an object of class |
608
|
|
|
|
|
|
|
L<Bio::RNA::Treekin::PopulationDataRecord>. It contains the population data |
609
|
|
|
|
|
|
|
for all minima at the C<stop_time>. |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
=head2 $record->population($i) |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
Get the C<$i>-th population data record, an object of class |
614
|
|
|
|
|
|
|
L<Bio::RNA::Treekin::PopulationDataRecord>. C<$i> is a zero-based index in |
615
|
|
|
|
|
|
|
chronological order. |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=head2 $record->populations |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
Returns the list of all population data records. Useful for iterating. |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
=head2 $record->add_min |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
Add a single new minimum with all-zero entries. Data can then be appended to |
624
|
|
|
|
|
|
|
this new min using C<append_pop_data()>. |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
Returns the index of the new minimum. |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=head2 $record->append_pop_data($pop_data_ref, $append_to_min_ref) |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
Given a list of population data records C<$pop_data_ref>, append them to the |
631
|
|
|
|
|
|
|
population data of this record. |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
The columns of the added data can be |
634
|
|
|
|
|
|
|
re-arranged on the fly by providing a mapping C<$append_to_min_ref> (a hash |
635
|
|
|
|
|
|
|
ref) giving for each minimum in C<$pop_data_ref> (key) a |
636
|
|
|
|
|
|
|
minimum in the current population data (value) to which the new minimum should |
637
|
|
|
|
|
|
|
be swapped. If no data is provided for some minimum of this record, its |
638
|
|
|
|
|
|
|
population is set to zero in the newly added entries. |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=head2 $record->stringify |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=head2 "$record" |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
Returns the record as a I<Treekin> file. |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=head1 AUTHOR |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
Felix Kuehnl, C<< <felix@bioinf.uni-leipzig.de> >> |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=head1 BUGS |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
Please report any bugs or feature requests by raising an issue at |
654
|
|
|
|
|
|
|
L<https://github.com/xileF1337/Bio-RNA-Treekin/issues>. |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
You can also do so by mailing to C<bug-bio-rna-treekin at rt.cpan.org>, |
657
|
|
|
|
|
|
|
or through the web interface at |
658
|
|
|
|
|
|
|
L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Bio-RNA-Treekin>. I will be |
659
|
|
|
|
|
|
|
notified, and then you'll automatically be notified of progress on your bug as |
660
|
|
|
|
|
|
|
I make changes. |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
=head1 SUPPORT |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
perldoc Bio::RNA::Treekin |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
You can also look for information at: |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
=over 4 |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
=item * Github: the official repository |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
L<https://github.com/xileF1337/Bio-RNA-Treekin> |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Bio-RNA-Treekin> |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
L<http://annocpan.org/dist/Bio-RNA-Treekin> |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
=item * CPAN Ratings |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
L<https://cpanratings.perl.org/d/Bio-RNA-Treekin> |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
=item * Search CPAN |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
L<https://metacpan.org/release/Bio-RNA-Treekin> |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=back |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
Copyright 2019-2021 Felix Kuehnl. |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify |
702
|
|
|
|
|
|
|
it under the terms of the GNU General Public License as published by |
703
|
|
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or |
704
|
|
|
|
|
|
|
(at your option) any later version. |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, |
707
|
|
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
708
|
|
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
709
|
|
|
|
|
|
|
GNU General Public License for more details. |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License |
712
|
|
|
|
|
|
|
along with this program. If not, see L<http://www.gnu.org/licenses/>. |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=cut |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
# End of Bio/RNA/Treekin/Record.pm |