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.03'; |
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
69
|
use 5.006; |
|
4
|
|
|
|
|
14
|
|
9
|
4
|
|
|
4
|
|
20
|
use strict; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
119
|
|
10
|
4
|
|
|
4
|
|
22
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
112
|
|
11
|
|
|
|
|
|
|
|
12
|
4
|
|
|
4
|
|
2263
|
use Moose; |
|
4
|
|
|
|
|
1900539
|
|
|
4
|
|
|
|
|
25
|
|
13
|
4
|
|
|
4
|
|
32021
|
use MooseX::StrictConstructor; |
|
4
|
|
|
|
|
123708
|
|
|
4
|
|
|
|
|
17
|
|
14
|
4
|
|
|
4
|
|
39074
|
use namespace::autoclean; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
27
|
|
15
|
|
|
|
|
|
|
|
16
|
4
|
|
|
4
|
|
1980
|
use autodie qw(:all); |
|
4
|
|
|
|
|
47101
|
|
|
4
|
|
|
|
|
34
|
|
17
|
4
|
|
|
4
|
|
83304
|
use Scalar::Util qw(reftype openhandle); |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
236
|
|
18
|
4
|
|
|
4
|
|
25
|
use List::Util qw(first pairmap max uniqnum all); |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
263
|
|
19
|
4
|
|
|
4
|
|
26
|
use Carp qw(croak); |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
153
|
|
20
|
|
|
|
|
|
|
|
21
|
4
|
|
|
4
|
|
2230
|
use Bio::RNA::Treekin::PopulationDataRecord; |
|
4
|
|
|
|
|
17
|
|
|
4
|
|
|
|
|
280
|
|
22
|
|
|
|
|
|
|
|
23
|
4
|
|
|
4
|
|
59
|
use overload '""' => \&stringify; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
31
|
|
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
|
4492
|
my ($self) = @_; |
62
|
|
|
|
|
|
|
|
63
|
2
|
|
|
|
|
5
|
my $data_count = @{ $self->_population_data }; |
|
2
|
|
|
|
|
129
|
|
64
|
2
|
|
|
|
|
12
|
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
|
28
|
my $self = shift; |
72
|
|
|
|
|
|
|
|
73
|
15
|
|
|
|
|
39
|
my $first_pop = $self->population(0); |
74
|
15
|
50
|
|
|
|
36
|
confess 'min_count: no population data present' |
75
|
|
|
|
|
|
|
unless defined $first_pop; |
76
|
|
|
|
|
|
|
|
77
|
15
|
|
|
|
|
63
|
my $min_count = $first_pop->min_count; |
78
|
|
|
|
|
|
|
|
79
|
15
|
|
|
|
|
36
|
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
|
23109
|
my ($self, $i) = @_; |
160
|
|
|
|
|
|
|
|
161
|
52
|
|
|
|
|
1953
|
my $population_record = $self->_population_data->[$i]; |
162
|
52
|
|
|
|
|
134
|
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
|
|
363
|
my ($class, $header_line) = @_; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# key and value separated by first ':' (match non-greedy!) |
215
|
150
|
|
|
|
|
679
|
my ($key, $value) = $header_line =~ m{ ^ ( .+? ) : [ ] ( .* ) $ }x; |
216
|
|
|
|
|
|
|
|
217
|
150
|
50
|
|
|
|
331
|
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
|
|
|
|
|
436
|
$key = (lc $key) =~ s/\s+/_/gr; |
222
|
|
|
|
|
|
|
|
223
|
150
|
|
|
|
|
403
|
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
|
|
18
|
my ($class, $command) = @_; |
236
|
|
|
|
|
|
|
|
237
|
7
|
|
|
|
|
94
|
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
|
|
|
|
|
18
|
my @init_population_strings; |
242
|
7
|
|
|
|
|
20
|
while (@command_parts) { |
243
|
116
|
100
|
|
|
|
251
|
if (shift @command_parts eq '--p0') { |
244
|
|
|
|
|
|
|
# Next value should be a population value. |
245
|
32
|
50
|
|
|
|
58
|
confess 'No argument following a --p0 switch' |
246
|
|
|
|
|
|
|
unless @command_parts; |
247
|
32
|
|
|
|
|
65
|
push @init_population_strings, shift @command_parts; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Store population of state i in index i-1. |
252
|
7
|
|
|
|
|
14
|
my @init_population; |
253
|
7
|
|
|
|
|
15
|
foreach my $init_population_string (@init_population_strings) { |
254
|
32
|
|
|
|
|
112
|
my ($state, $population) = split /=/, $init_population_string; |
255
|
32
|
|
|
|
|
77
|
$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
|
|
|
44
|
$_ //= 0. foreach @init_population; |
263
|
|
|
|
|
|
|
|
264
|
7
|
|
|
|
|
346
|
my $init_population_record |
265
|
|
|
|
|
|
|
= Bio::RNA::Treekin::PopulationDataRecord->new( |
266
|
|
|
|
|
|
|
time => 0, |
267
|
|
|
|
|
|
|
populations => \@init_population, |
268
|
|
|
|
|
|
|
); |
269
|
7
|
|
|
|
|
22
|
return $init_population_record; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub _parse_header_lines { |
273
|
10
|
|
|
10
|
|
26
|
my ($class, $header_lines_ref) = @_; |
274
|
|
|
|
|
|
|
|
275
|
10
|
|
|
|
|
18
|
my @header_args; |
276
|
10
|
|
|
|
|
26
|
foreach my $line (@$header_lines_ref) { |
277
|
150
|
|
|
|
|
279
|
my ($key, $value) = $class->_get_header_line_key_value($line); |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Implement special handling for certain keys. |
280
|
150
|
100
|
|
|
|
345
|
if ($key eq 'rates_file') { |
|
|
100
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# remove (#index) from file name and store the value |
282
|
7
|
|
|
|
|
37
|
my ($file_name, $file_index) |
283
|
|
|
|
|
|
|
= $value =~ m{ ^ (.+) [ ] [(] [#] (\d+) [)] $ }x; |
284
|
7
|
|
|
|
|
26
|
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
|
|
|
|
|
20
|
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
|
|
|
|
|
313
|
push @header_args, ($key => $value); |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
} |
303
|
10
|
|
|
|
|
95
|
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
|
|
27
|
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
|
|
|
|
|
23
|
my ($current_line, @header_lines, @population_data_lines); |
316
|
10
|
|
|
|
|
210
|
while (defined ($current_line = <$record_handle>)) { |
317
|
342
|
50
|
33
|
|
|
3114
|
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
|
|
|
|
962
|
if ($current_line =~ s/^# //) { # header line |
322
|
150
|
|
|
|
|
694
|
push @header_lines, $current_line; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
else { # data line |
325
|
192
|
|
|
|
|
698
|
push @population_data_lines, $current_line; |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
# Sanity checks. |
330
|
10
|
50
|
|
|
|
90
|
confess 'No header lines found in Treekin file' |
331
|
|
|
|
|
|
|
unless @header_lines; |
332
|
10
|
|
|
|
|
44
|
chomp @header_lines; |
333
|
|
|
|
|
|
|
|
334
|
10
|
50
|
|
|
|
30
|
confess 'No population data lines found in Treekin file' |
335
|
|
|
|
|
|
|
unless @population_data_lines; |
336
|
10
|
|
|
|
|
36
|
chomp @population_data_lines; |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
###################### Old implementation ################# |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# # Separate lines into header and population data. All header lines |
341
|
|
|
|
|
|
|
# # begin with a '# ' (remove it!) |
342
|
|
|
|
|
|
|
# my ($current_line, @header_lines); |
343
|
|
|
|
|
|
|
# while (defined ($current_line = <$record_handle>)) { |
344
|
|
|
|
|
|
|
# next if $current_line =~ /^@/; # drop xmgrace annotations |
345
|
|
|
|
|
|
|
# # header lines start with '# ', remove it |
346
|
|
|
|
|
|
|
# last unless $current_line =~ s/^# //; |
347
|
|
|
|
|
|
|
# push @header_lines, $current_line; |
348
|
|
|
|
|
|
|
# } |
349
|
|
|
|
|
|
|
# chomp @header_lines; |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# confess 'Unexpected end of record while parsing header' |
352
|
|
|
|
|
|
|
# unless defined $current_line; |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
# my @population_data_lines = ($current_line); |
355
|
|
|
|
|
|
|
# while (defined ($current_line = <$record_handle>)) { |
356
|
|
|
|
|
|
|
# push @population_data_lines, $current_line; |
357
|
|
|
|
|
|
|
# } |
358
|
|
|
|
|
|
|
# chomp @population_data_lines; |
359
|
|
|
|
|
|
|
|
360
|
10
|
|
|
|
|
49
|
return \@header_lines, \@population_data_lines; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
sub _parse_population_data_lines { |
364
|
10
|
|
|
10
|
|
22
|
my ($class, $population_data_lines_ref) = @_; |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
my @population_data |
367
|
10
|
|
|
|
|
28
|
= map { Bio::RNA::Treekin::PopulationDataRecord->new($_) } |
|
161
|
|
|
|
|
5533
|
|
368
|
|
|
|
|
|
|
@$population_data_lines_ref |
369
|
|
|
|
|
|
|
; |
370
|
|
|
|
|
|
|
|
371
|
9
|
|
|
|
|
33
|
return (population_data => \@population_data); |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
around BUILDARGS => sub { |
375
|
|
|
|
|
|
|
my $orig = shift; |
376
|
|
|
|
|
|
|
my $class = shift; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# Call original constructor if passed more than one arg. |
379
|
|
|
|
|
|
|
return $class->$orig(@_) unless @_ == 1; |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# Retrive file handle or pass on hash ref to constructor. |
382
|
|
|
|
|
|
|
my $record_handle; |
383
|
|
|
|
|
|
|
if (reftype $_[0]) { |
384
|
|
|
|
|
|
|
if (reftype $_[0] eq reftype {}) { # arg hash passed, |
385
|
|
|
|
|
|
|
return $class->$orig(@_); # pass on as is |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
elsif (reftype $_[0] eq reftype \*STDIN) { # file handle passed |
388
|
|
|
|
|
|
|
$record_handle = shift; |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
else { |
391
|
|
|
|
|
|
|
croak 'Invalid ref type passed to constructor'; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
else { # file name passed |
395
|
|
|
|
|
|
|
my $record_file = shift; |
396
|
|
|
|
|
|
|
open $record_handle, '<', $record_file; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# Read in file. |
400
|
|
|
|
|
|
|
my ($header_lines_ref, $population_data_lines_ref) |
401
|
|
|
|
|
|
|
= $class->_read_record_lines($record_handle); |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# Parse file. |
404
|
|
|
|
|
|
|
my @header_args = $class->_parse_header_lines($header_lines_ref); |
405
|
|
|
|
|
|
|
my @data_args |
406
|
|
|
|
|
|
|
= $class->_parse_population_data_lines($population_data_lines_ref); |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
my %args = (@header_args, @data_args); |
409
|
|
|
|
|
|
|
return $class->$orig(\%args); |
410
|
|
|
|
|
|
|
}; |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub BUILD { |
413
|
9
|
|
|
9
|
0
|
17
|
my $self = shift; |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# Force construction despite laziness. |
416
|
9
|
|
|
|
|
31
|
$self->min_count; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# Adjust min count of initial population as it was not known when |
419
|
|
|
|
|
|
|
# initial values were extracted from Treekin cmd. |
420
|
9
|
100
|
|
|
|
335
|
$self->init_population->set_min_count( $self->min_count ) |
421
|
|
|
|
|
|
|
if $self->has_init_population; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub stringify { |
425
|
8
|
|
|
8
|
1
|
172
|
my $self = shift; |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
# Format header line value of rates file entry. |
428
|
|
|
|
|
|
|
my $make_rates_file_val = sub { |
429
|
6
|
|
|
6
|
|
169
|
$self->rates_file . ' (#' . $self->file_index . ')'; |
430
|
8
|
|
|
|
|
47
|
}; |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
# Header |
433
|
8
|
100
|
|
|
|
309
|
my @header_entries = ( |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
434
|
|
|
|
|
|
|
$self->has_rates_file ? ('Rates file' => $make_rates_file_val->()) : (), |
435
|
|
|
|
|
|
|
$self->has_info ? ('Info' => $self->info) : (), |
436
|
|
|
|
|
|
|
$self->has_cmd ? ('Cmd' => $self->cmd) : (), |
437
|
|
|
|
|
|
|
'Date' => $self->date, |
438
|
|
|
|
|
|
|
'Sequence' => $self->sequence, |
439
|
|
|
|
|
|
|
'Method' => $self->method, |
440
|
|
|
|
|
|
|
'Start time' => $self->start_time, |
441
|
|
|
|
|
|
|
'Stop time' => $self->stop_time, |
442
|
|
|
|
|
|
|
'Temperature' => $self->temperature, |
443
|
|
|
|
|
|
|
'Basename' => $self->basename, |
444
|
|
|
|
|
|
|
'Time increment' => $self->time_increment, |
445
|
|
|
|
|
|
|
'Degeneracy' => $self->degeneracy, |
446
|
|
|
|
|
|
|
'Absorbing state' => $self->absorbing_state, |
447
|
|
|
|
|
|
|
'States limit' => $self->states_limit, |
448
|
|
|
|
|
|
|
); |
449
|
|
|
|
|
|
|
|
450
|
8
|
|
|
102
|
|
60
|
my $header_str = join "\n", pairmap { "# $a: $b" } @header_entries; |
|
102
|
|
|
|
|
215
|
|
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# Population data |
453
|
|
|
|
|
|
|
my $population_str |
454
|
8
|
|
|
|
|
37
|
= join "\n", map { "$_" } @{ $self->_population_data }; |
|
132
|
|
|
|
|
383
|
|
|
8
|
|
|
|
|
253
|
|
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# Footer (new Treekin versions only). |
457
|
8
|
100
|
|
|
|
324
|
my $footer_str = $self->has_of_iterations |
458
|
|
|
|
|
|
|
? '# of iterations: ' . $self->of_iterations |
459
|
|
|
|
|
|
|
: q{}; |
460
|
|
|
|
|
|
|
|
461
|
8
|
|
|
|
|
75
|
my $self_as_str = $header_str . "\n" . $population_str; |
462
|
8
|
100
|
|
|
|
29
|
$self_as_str .= "\n" . $footer_str if $footer_str; |
463
|
|
|
|
|
|
|
|
464
|
8
|
|
|
|
|
114
|
return $self_as_str; |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
1; # End of Bio::RNA::Treekin::Record |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
__END__ |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=pod |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=encoding UTF-8 |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=head1 NAME |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
Bio::RNA::Treekin::Record - Parse, query, and manipulate I<Treekin> output. |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=head1 SYNOPSIS |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
use Bio::RNA::Treekin; |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=head1 DESCRIPTION |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
Parses a regular output file of I<Treekin>. Allows to query population data |
490
|
|
|
|
|
|
|
as well as additional info from the header. New minima can be generated. The |
491
|
|
|
|
|
|
|
stringification returns, again, a valid I<Treekin> file which can be, e. g., |
492
|
|
|
|
|
|
|
visualized using I<Grace>. |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
These attributes of the class allow to query various data from the header of |
497
|
|
|
|
|
|
|
the input file. |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
=head2 date |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
The time and date of the I<Treekin> run. |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=head2 sequence |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
The RNA sequence for which the simulation was computed. |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=head2 method |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
The method used to build the transition matrix as documented for the |
510
|
|
|
|
|
|
|
C<--method> switch of I<Treekin>. |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=head2 start_time |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Initial time of the simulation. |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=head2 stop_time |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
Time at which the simulation stops. |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=head2 temperature |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
Temperature of the simulation in degrees Celsius. |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=head2 basename |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
Name of the input file. May be C<< <stdin> >> if data was read from standard |
527
|
|
|
|
|
|
|
input. |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
=head2 time_increment |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Factor by which the time is multiplied in each simulation step (roughly, the |
532
|
|
|
|
|
|
|
truth is more complicated). |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
=head2 degeneracy |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
Whether to consider degeneracy in transition rates. |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
=head2 absorbing_state |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
The states specified as absorbing do not have any outgoing transitions and |
541
|
|
|
|
|
|
|
thus serve as "population sinks" during the simulation. |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
=head2 states_limit |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
Maximum number of states (???). Value is always (?) 2^31 = 2147483647. |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
=head2 info |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
A free text field containing additional comments. |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
Only available in I<some> of the records of a I<BarMap> multi-record file. Use |
552
|
|
|
|
|
|
|
predicate C<has_info> to check whether this attribute is available. |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
=head2 init_population |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
The initial population specified by the user. This information is extracted |
557
|
|
|
|
|
|
|
from the C<cmd> attribute. |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
Only available in I<BarMap>'s multi-record files. Use predicate |
560
|
|
|
|
|
|
|
C<has_init_population> to check whether this attribute is available. |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
=head2 rates_file |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
The file that the rate matrix was read from. |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
Only available in I<BarMap>'s multi-record files. Use predicate |
567
|
|
|
|
|
|
|
C<has_rates_file> to check whether this attribute is available. |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=head2 file_index |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
Zero-based index given to the input files in the order they were read. |
572
|
|
|
|
|
|
|
Extracted from the C<rates_file> attribute. |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
Use predicate C<has_file_index> to check whether this attribute is available. |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
=head2 cmd |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
The command used to invoke I<Treekin>. Only available in I<BarMap>'s |
579
|
|
|
|
|
|
|
multi-record files. |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
Use predicate C<has_cmd> to check whether this attribute is available. |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
=head1 METHODS |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
These methods allow the construction, querying and manipulation of the record |
587
|
|
|
|
|
|
|
objects and its population data. |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
=head2 Bio::RNA::Treekin::Record->new($treekin_file) |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=head2 Bio::RNA::Treekin::Record->new($treekin_handle) |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
Construct a new record from a (single) I<Treekin> file. |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=head2 $record->population_data_count |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
Return the number of population data records, i. e. the number of simulated |
598
|
|
|
|
|
|
|
time steps, including the start time. |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
=head2 $record->min_count |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
Return the number of minima. |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=head2 $record->mins |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
Return the list of all contained minima, i. e. C<< 1...$record->min_count >> |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=head2 $record->keep_mins(@kept_minima) |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
Remove all minima but the ones from C<@kept_minima>. The list is sorted and |
611
|
|
|
|
|
|
|
de-duplicated first. |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=head2 $record->splice_mins(@kept_minima) |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
Like C<keep_mins()>, but do not sort / de-duplicate, but use C<@kept_minima> |
616
|
|
|
|
|
|
|
as is. This can be used to remove, duplicate or reorder minima. |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
=head2 $record->max_pop_of_min($minimum) |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
Get the maximum population value of all time points for a specific C<$minimum>. |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=head2 $record->pops_of_min($minimum) |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
Get a list of the populations at all time points (in chronological order) for |
625
|
|
|
|
|
|
|
a single C<$minimum>. |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
=head2 $record->final_population |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
Get the last population data record, an object of class |
630
|
|
|
|
|
|
|
L<Bio::RNA::Treekin::PopulationDataRecord>. It contains the population data |
631
|
|
|
|
|
|
|
for all minima at the C<stop_time>. |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
=head2 $record->population($i) |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
Get the C<$i>-th population data record, an object of class |
636
|
|
|
|
|
|
|
L<Bio::RNA::Treekin::PopulationDataRecord>. C<$i> is a zero-based index in |
637
|
|
|
|
|
|
|
chronological order. |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
=head2 $record->populations |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
Returns the list of all population data records. Useful for iterating. |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
=head2 $record->add_min |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
Add a single new minimum with all-zero entries. Data can then be appended to |
646
|
|
|
|
|
|
|
this new min using C<append_pop_data()>. |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
Returns the index of the new minimum. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=head2 $record->append_pop_data($pop_data_ref, $append_to_min_ref) |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
Given a list of population data records C<$pop_data_ref>, append them to the |
653
|
|
|
|
|
|
|
population data of this record. |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
The columns of the added data can be |
656
|
|
|
|
|
|
|
re-arranged on the fly by providing a mapping C<$append_to_min_ref> (a hash |
657
|
|
|
|
|
|
|
ref) giving for each minimum in C<$pop_data_ref> (key) a |
658
|
|
|
|
|
|
|
minimum in the current population data (value) to which the new minimum should |
659
|
|
|
|
|
|
|
be swapped. If no data is provided for some minimum of this record, its |
660
|
|
|
|
|
|
|
population is set to zero in the newly added entries. |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
=head2 $record->stringify |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
=head2 "$record" |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
Returns the record as a I<Treekin> file. |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
=head1 AUTHOR |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
Felix Kuehnl, C<< <felix@bioinf.uni-leipzig.de> >> |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
=head1 BUGS |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
Please report any bugs or feature requests by raising an issue at |
676
|
|
|
|
|
|
|
L<https://github.com/xileF1337/Bio-RNA-Treekin/issues>. |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
You can also do so by mailing to C<bug-bio-rna-treekin at rt.cpan.org>, |
679
|
|
|
|
|
|
|
or through the web interface at |
680
|
|
|
|
|
|
|
L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Bio-RNA-Treekin>. I will be |
681
|
|
|
|
|
|
|
notified, and then you'll automatically be notified of progress on your bug as |
682
|
|
|
|
|
|
|
I make changes. |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=head1 SUPPORT |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
perldoc Bio::RNA::Treekin |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
You can also look for information at: |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=over 4 |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
=item * Github: the official repository |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
L<https://github.com/xileF1337/Bio-RNA-Treekin> |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Bio-RNA-Treekin> |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
L<http://annocpan.org/dist/Bio-RNA-Treekin> |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
=item * CPAN Ratings |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
L<https://cpanratings.perl.org/d/Bio-RNA-Treekin> |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
=item * Search CPAN |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
L<https://metacpan.org/release/Bio-RNA-Treekin> |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=back |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
Copyright 2019-2021 Felix Kuehnl. |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify |
724
|
|
|
|
|
|
|
it under the terms of the GNU General Public License as published by |
725
|
|
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or |
726
|
|
|
|
|
|
|
(at your option) any later version. |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, |
729
|
|
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
730
|
|
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
731
|
|
|
|
|
|
|
GNU General Public License for more details. |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License |
734
|
|
|
|
|
|
|
along with this program. If not, see L<http://www.gnu.org/licenses/>. |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
=cut |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
# End of Bio/RNA/Treekin/Record.pm |