File Coverage

blib/lib/App/Rangeops/Command/merge.pm
Criterion Covered Total %
statement 122 127 96.0
branch 24 36 66.6
condition 2 3 66.6
subroutine 15 15 100.0
pod 5 5 100.0
total 168 186 90.3


line stmt bran cond sub pod time code
1             package App::Rangeops::Command::merge;
2 9     9   5378 use strict;
  9         25  
  9         227  
3 9     9   45 use warnings;
  9         19  
  9         204  
4 9     9   41 use autodie;
  9         18  
  9         48  
5              
6 9     9   48977 use MCE;
  9         170155  
  9         51  
7 9     9   5074 use MCE::Flow Sereal => 1;
  9         22462  
  9         58  
8 9     9   5935 use MCE::Candy;
  9         6871  
  9         102  
9              
10 9     9   284 use App::Rangeops -command;
  9         25  
  9         121  
11 9     9   3631 use App::Rangeops::Common;
  9         23  
  9         208  
12              
13 9     9   41 use constant abstract => 'merge overlapped ranges via overlapping graph';
  9         22  
  9         10601  
14              
15             sub opt_spec {
16             return (
17 4     4 1 36 [ "coverage|c=f",
18             "When larger than this ratio, merge ranges, default is [0.95]",
19             { default => 0.95 },
20             ],
21             [ "outfile|o=s", "Output filename. [stdout] for screen." ],
22             [ "parallel|p=i",
23             "Run in parallel mode. Default is [1].",
24             { default => 1 },
25             ],
26             [ "verbose|v", "Verbose mode.", ],
27             );
28             }
29              
30             sub usage_desc {
31 4     4 1 52390 return "rangeops merge [options] ";
32             }
33              
34             sub description {
35 2     2 1 2124 my $desc;
36 2         8 $desc .= ucfirst(abstract) . ".\n";
37 2         4 $desc .= "\tMerged ranges are always on positive strands.\n";
38              
39 2         8 return $desc;
40             }
41              
42             sub validate_args {
43 2     2 1 2438 my ( $self, $opt, $args ) = @_;
44              
45 2 50       6 if ( !@{$args} ) {
  2         10  
46 0         0 $self->usage_error("This command need one or more input files.");
47             }
48 2         4 for ( @{$args} ) {
  2         6  
49 2 50       18 next if lc $_ eq "stdin";
50 2 50       16 if ( !Path::Tiny::path($_)->is_file ) {
51 0         0 $self->usage_error("The input file [$_] doesn't exist.");
52             }
53             }
54              
55 2 50       236 if ( !exists $opt->{outfile} ) {
56             $opt->{outfile}
57 0         0 = Path::Tiny::path( $args->[0] )->absolute . ".merge.tsv";
58             }
59             }
60              
61             sub execute {
62 2     2 1 14 my ( $self, $opt, $args ) = @_;
63              
64             #----------------------------#
65             # Loading
66             #----------------------------#π
67 2         6 my $graph_of_chr = {};
68 2         6 my $info_of = {};
69 2         6 for my $file ( @{$args} ) {
  2         6  
70 2         14 my @lines = App::RL::Common::read_lines($file);
71 2         4790 for my $line (@lines) {
72 58         1564 for my $part ( split /\t/, $line ) {
73 174         834 my $info = App::RL::Common::decode_header($part);
74 174 100       19736 next unless App::RL::Common::info_is_valid($info);
75              
76 116         2918 my $chr = $info->{chr};
77 116 100       716 if ( !exists $graph_of_chr->{$chr} ) {
78 16         58 $graph_of_chr->{$chr} = Graph->new( directed => 0 );
79             }
80              
81 116         3366 my $range = App::RL::Common::encode_header( $info, 1 );
82 116 100       7044 if ( !$graph_of_chr->{$chr}->has_vertex($range) ) {
83 48         388 $graph_of_chr->{$chr}->add_vertex($range);
84 48         1776 $info->{intspan} = AlignDB::IntSpan->new;
85 48         1154 $info->{intspan}->add_pair( $info->{start}, $info->{end} );
86 48         3350 $info_of->{$range} = $info;
87 48 50       146 print STDERR "Add range $range\n" if $opt->{verbose};
88             }
89             }
90             }
91             }
92              
93             #----------------------------#
94             # Coverages
95             #----------------------------#
96             my $worker = sub {
97 8     8   16800 my ( $self, $chunk_ref, $chunk_id ) = @_;
98              
99 8         21 my $chr = $chunk_ref->[0];
100              
101 8         26 my $g = $graph_of_chr->{$chr};
102 8         53 my @ranges = sort $g->vertices;
103 8         598 my @edges;
104 8         34 for my $i ( 0 .. $#ranges ) {
105 24         236 my $range_i = $ranges[$i];
106             printf STDERR " " x 4 . "Range %d / %d\t%s\n", $i, $#ranges,
107             $range_i
108 24 50       73 if $opt->{verbose};
109 24         142 my $set_i = $info_of->{$range_i}{intspan};
110 24         189 for my $j ( $i + 1 .. $#ranges ) {
111 94         1337 my $range_j = $ranges[$j];
112 94         375 my $set_j = $info_of->{$range_j}{intspan};
113              
114 94         721 my $i_set = $set_i->intersect($set_j);
115 94 100       31850 if ( $i_set->is_not_empty ) {
116 9         177 my $coverage_i = $i_set->size / $set_i->size;
117 9         326 my $coverage_j = $i_set->size / $set_j->size;
118 9 100 66     397 if ( $coverage_i >= $opt->{coverage}
119             and $coverage_j >= $opt->{coverage} )
120             {
121 5         16 push @edges, [ $ranges[$i], $ranges[$j] ];
122             printf STDERR " " x 8
123             . "Merge with Range %d / %d\t%s\n",
124             $j, $#ranges, $range_j
125 5 50       37 if $opt->{verbose};
126             }
127             }
128             }
129             }
130 8 50       34 printf STDERR "Gather %d edges\n", scalar @edges if $opt->{verbose};
131              
132 8         38 MCE->gather( $chr, \@edges );
133 2         84 };
134             MCE::Flow::init {
135             chunk_size => 1,
136             max_workers => $opt->{parallel},
137 2         20 };
138 2         40 my %edge_of = mce_flow $worker, ( sort keys %{$graph_of_chr} );
  2         24  
139 1         663631 MCE::Flow::finish;
140              
141 1         69 for my $chr ( sort keys %{$graph_of_chr} ) {
  1         11  
142             print STDERR " " x 4 . "Add edges to chromosome [$chr]\n"
143 8 50       116 if $opt->{verbose};
144 8         15 for my $edge ( @{ $edge_of{$chr} } ) {
  8         15  
145 5         207 $graph_of_chr->{$chr}->add_edge( @{$edge} );
  5         23  
146             }
147             }
148              
149             #----------------------------#
150             # Merging
151             #----------------------------#
152 1         4 my @lines;
153 1         3 for my $chr ( sort keys %{$graph_of_chr} ) {
  1         8  
154 8         17 my $g = $graph_of_chr->{$chr};
155 8         26 my @cc = $g->connected_components;
156              
157             # filter cc with single range
158 8         8687 @cc = grep { scalar @{$_} > 1 } @cc;
  19         32  
  19         43  
159              
160 8         21 for my $c (@cc) {
161 0         0 printf STDERR "\n" . " " x 4 . "Merge %s ranges\n", scalar @{$c}
162 5 50       15 if $opt->{verbose};
163 5         27 my $merge_set = AlignDB::IntSpan->new;
164 5         54 for my $range ( @{$c} ) {
  5         10  
165 10         539 my $set = $info_of->{$range}{intspan};
166 10         84 $merge_set->merge($set);
167             }
168              
169 5         513 my $merge_range = App::RL::Common::encode_header(
170             { chr => $chr,
171             strand => "+",
172             start => $merge_set->min,
173             end => $merge_set->max,
174             }
175             );
176              
177 5         429 for my $range ( @{$c} ) {
  5         11  
178 10 100       31 next if $range eq $merge_range;
179              
180 6         18 my $line = sprintf "%s\t%s", $range, $merge_range;
181 6         12 push @lines, $line;
182 6 50       28 print STDERR " " x 8 . "$line\n" if $opt->{verbose};
183             }
184             }
185             }
186              
187             #----------------------------#
188             # Output
189             #----------------------------#
190 1         3 my $out_fh;
191 1 50       9 if ( lc( $opt->{outfile} ) eq "stdout" ) {
192 1         4 $out_fh = \*STDOUT;
193             }
194             else {
195 0         0 open $out_fh, ">", $opt->{outfile};
196             }
197              
198 1         4 print {$out_fh} "$_\n" for @lines;
  6         84  
199              
200 1         18 close $out_fh;
201             }
202              
203             1;