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   6114 use strict;
  9         11  
  9         286  
3 9     9   40 use warnings;
  9         19  
  9         216  
4 9     9   40 use autodie;
  9         13  
  9         47  
5              
6 9     9   30868 use App::Rangeops -command;
  9         13  
  9         83  
7 9     9   2396 use App::Rangeops::Common;
  9         14  
  9         230  
8              
9 9     9   34 use constant abstract => 'connect bilaterial links into multilateral ones';
  9         11  
  9         14208  
10              
11             sub opt_spec {
12             return (
13 2     2 1 19 [ "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 29244 return "rangeops connect [options] ";
25             }
26              
27             sub description {
28 1     1 1 776 my $desc;
29 1         4 $desc .= ucfirst(abstract) . ".\n";
30 1         2 $desc .= "\t are bilaterial links files without hit strands\n";
31 1         4 return $desc;
32             }
33              
34             sub validate_args {
35 1     1 1 928 my ( $self, $opt, $args ) = @_;
36              
37 1 50       2 if ( !@{$args} ) {
  1         6  
38 0         0 $self->usage_error("This command need one or more input files.");
39             }
40 1         10 for ( @{$args} ) {
  1         4  
41 1 50       5 next if lc $_ eq "stdin";
42 1 50       6 if ( !Path::Tiny::path($_)->is_file ) {
43 0         0 $self->usage_error("The input file [$_] doesn't exist.");
44             }
45             }
46              
47 1 50       116 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 5 my ( $self, $opt, $args ) = @_;
55              
56             #----------------------------#
57             # Loading
58             #----------------------------#
59 1         10 my $graph = Graph->new( directed => 0 );
60 1         256 my $info_of = {}; # info of ranges
61 1         2 for my $file ( @{$args} ) {
  1         3  
62 1         5 for my $line ( App::RL::Common::read_lines($file) ) {
63 8         234 $info_of = App::Rangeops::Common::build_info( [$line], $info_of );
64              
65 8         11 my @parts;
66 8         21 for my $part ( split /\t/, $line ) {
67 16 50       29 next unless exists $info_of->{$part};
68 16         20 push @parts, $part;
69             }
70              
71             # all ranges will be converted to positive strands
72 8 50       15 next unless @parts == 2;
73              
74 8         7 my %new_0 = %{ $info_of->{ $parts[0] } };
  8         32  
75 8         227 my %new_1 = %{ $info_of->{ $parts[1] } };
  8         22  
76              
77 8         272 my @strands;
78 8         11 my $hit_strand = "+";
79             {
80 8         8 push @strands, $new_0{strand};
  8         10  
81 8         11 push @strands, $new_1{strand};
82 8         26 @strands = List::MoreUtils::PP::uniq(@strands);
83              
84 8 100       96 if ( @strands != 1 ) {
85 2         3 $hit_strand = "-";
86             }
87              
88 8         10 $new_0{strand} = "+";
89 8         11 $new_1{strand} = "+";
90             }
91              
92 8         20 my $range_0 = App::RL::Common::encode_header( \%new_0, 1 );
93 8         126 $info_of->{$range_0} = \%new_0;
94 8         48 my $range_1 = App::RL::Common::encode_header( \%new_1, 1 );
95 8         123 $info_of->{$range_1} = \%new_1;
96              
97             # add range
98 8         26 for my $range ( $range_0, $range_1 ) {
99 16 100       48 if ( !$graph->has_vertex($range) ) {
100 14         103 $graph->add_vertex($range);
101 14 50       352 print STDERR "Add range $range\n" if $opt->{verbose};
102             }
103             }
104              
105             # add edge
106 8 50       20 if ( !$graph->has_edge( $range_0, $range_1 ) ) {
107 8         155 $graph->add_edge( $range_0, $range_1 );
108 8         517 $graph->set_edge_attribute( $range_0, $range_1, "strand",
109             $hit_strand );
110              
111 8 50       855 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       26 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       8 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         6 for my $cc ( $graph->connected_components ) {
129 6         3696 my @ranges = @{$cc};
  6         11  
130 6         5 my $copy = scalar @ranges;
131              
132 6 50       11 next if $copy == 1;
133              
134 6         11 my $line = join "\t", @ranges;
135 6         12 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         6 my $new_graph = Graph->new( directed => 0 );
145 1         144 for my $line (@sorted_lines) {
146 6         369 my @ranges = split /\t/, $line;
147 6         8 my $copy = scalar @ranges;
148 6 50       14 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         9 my $info = App::RL::Common::decode_header($_);
  14         32  
155 14         1277 $info->{strand} = undef;
156 14         114 $info->{intspan} = AlignDB::IntSpan->new;
157 14         223 $info->{intspan}->add_pair( $info->{start}, $info->{end} );
158 14         613 $info;
159             } @ranges;
160              
161             # set first node to positive strand
162 6         17 $infos[0]->{strand} = "+";
163              
164 6         50 my $assigned = AlignDB::IntSpan->new(0);
165 6         232 my $unhandled = AlignDB::IntSpan->new;
166 6         43 $unhandled->add_pair( 0, $copy - 1 );
167 6         156 $unhandled->remove($assigned);
168              
169 6         599 my @edges; # store edge pairs. not all nodes connected
170 6         13 while ( $assigned->size < $copy ) {
171 6         85 for my $i ( $assigned->elements ) {
172 6         114 for my $j ( $unhandled->elements ) {
173 8 50       81 next if !$graph->has_edge( $ranges[$i], $ranges[$j] );
174             next
175 8 50       168 if !$graph->has_edge_attribute( $ranges[$i],
176             $ranges[$j], "strand" );
177 8 50       638 next if !defined $infos[$i]->{strand};
178              
179             # assign strands
180 8         62 my $hit_strand
181             = $graph->get_edge_attribute( $ranges[$i], $ranges[$j],
182             "strand" );
183 8 100       550 if ( $hit_strand eq "-" ) {
184             printf " " x 4 . "change strand of %s\n", $ranges[$j]
185 2 50       6 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         105 $unhandled->remove($j);
197 8         722 $assigned->add($j);
198              
199             # break bad links
200 8         300 my $size_i = $infos[$i]->{intspan}->size;
201 8         133 my $size_j = $infos[$j]->{intspan}->size;
202 8         116 my ( $size_min, $size_max )
203             = List::MoreUtils::PP::minmax( $size_i, $size_j );
204 8         138 my $diff_ratio = sprintf "%.3f", $size_min / $size_max;
205              
206 8 50       27 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         34 push @edges, [ $i, $j ];
217             }
218             }
219             }
220             }
221              
222             # transform array_ref back to string
223 6         75 my @nodes_new = map { App::RL::Common::encode_header( $_, 1 ) } @infos;
  14         439  
224              
225 6         266 for my $edge (@edges) {
226 8         179 $new_graph->add_edge( $nodes_new[ $edge->[0] ],
227             $nodes_new[ $edge->[1] ] );
228             }
229             }
230              
231             #----------------------------#
232             # Recreate cc
233             #----------------------------#
234 1         84 my @new_lines;
235 1         5 for my $cc ( $new_graph->connected_components ) {
236 6         2719 my @ranges = @{$cc};
  6         11  
237 6         6 my $copy = scalar @ranges;
238              
239 6 50       10 next if $copy == 1;
240              
241 6         11 my $line = join "\t", @ranges;
242 6         11 push @new_lines, $line;
243             }
244              
245             my @new_sorted_lines
246 1         2 = @{ 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       9 if ( lc( $opt->{outfile} ) eq "stdout" ) {
254 1         3 $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         62  
261              
262 1         18 close $out_fh;
263             }
264              
265             1;