File Coverage

blib/lib/App/Rangeops/Command/connect.pm
Criterion Covered Total %
statement 139 147 94.5
branch 27 54 50.0
condition n/a
subroutine 11 11 100.0
pod 5 5 100.0
total 182 217 83.8


line stmt bran cond sub pod time code
1             package App::Rangeops::Command::connect;
2 9     9   5796 use strict;
  9         20  
  9         238  
3 9     9   43 use warnings;
  9         20  
  9         205  
4 9     9   41 use autodie;
  9         19  
  9         47  
5              
6 9     9   44278 use App::Rangeops -command;
  9         24  
  9         92  
7 9     9   2764 use App::Rangeops::Common;
  9         20  
  9         221  
8              
9 9     9   44 use constant abstract => 'connect bilaterial links into multilateral ones';
  9         20  
  9         12976  
10              
11             sub opt_spec {
12             return (
13 2     2 1 17 [ "outfile|o=s", "Output filename. [stdout] for screen." ],
14             [ "ratio|r=f",
15             "Break links if length identities less than this ratio, default is [0.9].",
16             { default => 0.9 }
17             ],
18             [ "numeric|n", "Sort chromosome names numerically.", ],
19             [ "verbose|v", "Verbose mode.", ],
20             );
21             }
22              
23             sub usage_desc {
24 2     2 1 25886 return "rangeops connect [options] ";
25             }
26              
27             sub description {
28 1     1 1 1033 my $desc;
29 1         3 $desc .= ucfirst(abstract) . ".\n";
30 1         3 $desc .= "\t are bilaterial links files without hit strands\n";
31 1         3 return $desc;
32             }
33              
34             sub validate_args {
35 1     1 1 1175 my ( $self, $opt, $args ) = @_;
36              
37 1 50       3 if ( !@{$args} ) {
  1         6  
38 0         0 $self->usage_error("This command need one or more input files.");
39             }
40 1         3 for ( @{$args} ) {
  1         4  
41 1 50       5 next if lc $_ eq "stdin";
42 1 50       7 if ( !Path::Tiny::path($_)->is_file ) {
43 0         0 $self->usage_error("The input file [$_] doesn't exist.");
44             }
45             }
46              
47 1 50       122 if ( !exists $opt->{outfile} ) {
48             $opt->{outfile}
49 0         0 = Path::Tiny::path( $args->[0] )->absolute . ".cc.tsv";
50             }
51             }
52              
53             sub execute {
54 1     1 1 7 my ( $self, $opt, $args ) = @_;
55              
56             #----------------------------#
57             # Loading
58             #----------------------------#
59 1         11 my $graph = Graph->new( directed => 0 );
60 1         292 my $info_of = {}; # info of ranges
61 1         2 for my $file ( @{$args} ) {
  1         4  
62 1         7 for my $line ( App::RL::Common::read_lines($file) ) {
63 8         2347 $info_of = App::Rangeops::Common::build_info( [$line], $info_of );
64              
65 8         15 my @parts;
66 8         22 for my $part ( split /\t/, $line ) {
67 16 50       41 next unless exists $info_of->{$part};
68 16         30 push @parts, $part;
69             }
70              
71             # all ranges will be converted to positive strands
72 8 50       21 next unless @parts == 2;
73              
74 8         11 my %new_0 = %{ $info_of->{ $parts[0] } };
  8         31  
75 8         279 my %new_1 = %{ $info_of->{ $parts[1] } };
  8         24  
76              
77 8         348 my @strands;
78 8         16 my $hit_strand = "+";
79             {
80 8         11 push @strands, $new_0{strand};
  8         18  
81 8         15 push @strands, $new_1{strand};
82 8         24 @strands = List::MoreUtils::PP::uniq(@strands);
83              
84 8 100       111 if ( @strands != 1 ) {
85 2         4 $hit_strand = "-";
86             }
87              
88 8         17 $new_0{strand} = "+";
89 8         13 $new_1{strand} = "+";
90             }
91              
92 8         23 my $range_0 = App::RL::Common::encode_header( \%new_0, 1 );
93 8         166 $info_of->{$range_0} = \%new_0;
94 8         34 my $range_1 = App::RL::Common::encode_header( \%new_1, 1 );
95 8         144 $info_of->{$range_1} = \%new_1;
96              
97             # add range
98 8         23 for my $range ( $range_0, $range_1 ) {
99 16 100       52 if ( !$graph->has_vertex($range) ) {
100 14         114 $graph->add_vertex($range);
101 14 50       558 print STDERR "Add range $range\n" if $opt->{verbose};
102             }
103             }
104              
105             # add edge
106 8 50       26 if ( !$graph->has_edge( $range_0, $range_1 ) ) {
107 8         195 $graph->add_edge( $range_0, $range_1 );
108 8         751 $graph->set_edge_attribute( $range_0, $range_1, "strand",
109             $hit_strand );
110              
111 8 50       1154 print STDERR join "\t", @parts, "\n" if $opt->{verbose};
112             printf STDERR "Nodes %d \t Edges %d\n",
113             scalar $graph->vertices, scalar $graph->edges
114 8 50       30 if $opt->{verbose};
115             }
116             else {
117 0 0       0 print STDERR " " x 4 . "Edge exists, next\n" if $opt->{verbose};
118             }
119             }
120              
121 1 50       6 print STDERR "==>" . "Finish processing [$file]\n" if $opt->{verbose};
122             }
123              
124             #----------------------------#
125             # Create cc and sort
126             #----------------------------#
127 1         2 my @lines;
128 1         10 for my $cc ( $graph->connected_components ) {
129 6         5484 my @ranges = @{$cc};
  6         13  
130 6         9 my $copy = scalar @ranges;
131              
132 6 50       15 next if $copy == 1;
133              
134 6         15 my $line = join "\t", @ranges;
135 6         14 push @lines, $line;
136             }
137              
138             my @sorted_lines
139 1         2 = @{ App::Rangeops::Common::sort_links( \@lines, $opt->{numeric} ) };
  1         8  
140              
141             #----------------------------#
142             # Change strands of ranges based on first range in cc
143             #----------------------------#
144 1         5 my $new_graph = Graph->new( directed => 0 );
145 1         178 for my $line (@sorted_lines) {
146 6         537 my @ranges = split /\t/, $line;
147 6         12 my $copy = scalar @ranges;
148 6 50       21 print STDERR "Copy number of this cc is $copy\n" if $opt->{verbose};
149              
150             # transform range to info
151             # set strand to undef
152             # create intspan
153             my @infos = map {
154 6         13 my $info = App::RL::Common::decode_header($_);
  14         35  
155 14         1794 $info->{strand} = undef;
156 14         160 $info->{intspan} = AlignDB::IntSpan->new;
157 14         325 $info->{intspan}->add_pair( $info->{start}, $info->{end} );
158 14         989 $info;
159             } @ranges;
160              
161             # set first node to positive strand
162 6         19 $infos[0]->{strand} = "+";
163              
164 6         67 my $assigned = AlignDB::IntSpan->new(0);
165 6         423 my $unhandled = AlignDB::IntSpan->new;
166 6         62 $unhandled->add_pair( 0, $copy - 1 );
167 6         314 $unhandled->remove($assigned);
168              
169 6         911 my @edges; # store edge pairs. not all nodes connected
170 6         18 while ( $assigned->size < $copy ) {
171 6         125 for my $i ( $assigned->elements ) {
172 6         123 for my $j ( $unhandled->elements ) {
173 8 50       120 next if !$graph->has_edge( $ranges[$i], $ranges[$j] );
174             next
175 8 50       238 if !$graph->has_edge_attribute( $ranges[$i],
176             $ranges[$j], "strand" );
177 8 50       851 next if !defined $infos[$i]->{strand};
178              
179             # assign strands
180 8         70 my $hit_strand
181             = $graph->get_edge_attribute( $ranges[$i], $ranges[$j],
182             "strand" );
183 8 100       806 if ( $hit_strand eq "-" ) {
184             printf " " x 4 . "change strand of %s\n", $ranges[$j]
185 2 50       9 if $opt->{verbose};
186 2 50       7 if ( $infos[$i]->{strand} eq "+" ) {
187 2         17 $infos[$j]->{strand} = "-";
188             }
189             else {
190 0         0 $infos[$j]->{strand} = "+";
191             }
192             }
193             else {
194 6         20 $infos[$j]->{strand} = $infos[$i]->{strand};
195             }
196 8         131 $unhandled->remove($j);
197 8         1123 $assigned->add($j);
198              
199             # break bad links
200 8         550 my $size_i = $infos[$i]->{intspan}->size;
201 8         193 my $size_j = $infos[$j]->{intspan}->size;
202 8         184 my ( $size_min, $size_max )
203             = List::MoreUtils::PP::minmax( $size_i, $size_j );
204 8         154 my $diff_ratio = sprintf "%.3f", $size_min / $size_max;
205              
206 8 50       28 if ( $diff_ratio < $opt->{ratio} ) {
207             printf STDERR " " x 4 . "Break links between %s %s\n",
208             $ranges[$i], $ranges[$j]
209 0 0       0 if $opt->{verbose};
210             printf STDERR " " x 4
211             . "Ratio[%s]\tMin [%s]\tMax[%s]\n",
212             $diff_ratio, $size_min, $size_max
213 0 0       0 if $opt->{verbose};
214             }
215             else {
216 8         33 push @edges, [ $i, $j ];
217             }
218             }
219             }
220             }
221              
222             # transform array_ref back to string
223 6         118 my @nodes_new = map { App::RL::Common::encode_header( $_, 1 ) } @infos;
  14         560  
224              
225 6         361 for my $edge (@edges) {
226 8         251 $new_graph->add_edge( $nodes_new[ $edge->[0] ],
227             $nodes_new[ $edge->[1] ] );
228             }
229             }
230              
231             #----------------------------#
232             # Recreate cc
233             #----------------------------#
234 1         128 my @new_lines;
235 1         6 for my $cc ( $new_graph->connected_components ) {
236 6         4201 my @ranges = @{$cc};
  6         13  
237 6         11 my $copy = scalar @ranges;
238              
239 6 50       13 next if $copy == 1;
240              
241 6         14 my $line = join "\t", @ranges;
242 6         14 push @new_lines, $line;
243             }
244              
245             my @new_sorted_lines
246 1         3 = @{ App::Rangeops::Common::sort_links( \@new_lines, $opt->{numeric} )
247 1         7 };
248              
249             #----------------------------#
250             # Output
251             #----------------------------#
252 1         3 my $out_fh;
253 1 50       7 if ( lc( $opt->{outfile} ) eq "stdout" ) {
254 1         4 $out_fh = \*STDOUT;
255             }
256             else {
257 0         0 open $out_fh, ">", $opt->{outfile};
258             }
259              
260 1         3 print {$out_fh} "$_\n" for @new_sorted_lines;
  6         77  
261              
262 1         16 close $out_fh;
263             }
264              
265             1;