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   6638 use strict;
  9         17  
  9         370  
3 9     9   54 use warnings;
  9         17  
  9         250  
4 9     9   42 use autodie;
  9         15  
  9         50  
5              
6 9     9   43177 use MCE;
  9         222395  
  9         67  
7 9     9   117268 use MCE::Flow Sereal => 1;
  9         26541  
  9         68  
8 9     9   8117 use MCE::Candy;
  9         8061  
  9         180  
9              
10 9     9   359 use App::Rangeops -command;
  9         17  
  9         148  
11 9     9   3482 use App::Rangeops::Common;
  9         15  
  9         246  
12              
13 9     9   39 use constant abstract => 'merge overlapped ranges via overlapping graph';
  9         16  
  9         13018  
14              
15             sub opt_spec {
16             return (
17 4     4 1 46 [ "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 61080 return "rangeops merge [options] ";
32             }
33              
34             sub description {
35 2     2 1 1636 my $desc;
36 2         8 $desc .= ucfirst(abstract) . ".\n";
37 2         2 $desc .= "\tMerged ranges are always on positive strands.\n";
38              
39 2         6 return $desc;
40             }
41              
42             sub validate_args {
43 2     2 1 1828 my ( $self, $opt, $args ) = @_;
44              
45 2 50       4 if ( !@{$args} ) {
  2         12  
46 0         0 $self->usage_error("This command need one or more input files.");
47             }
48 2         14 for ( @{$args} ) {
  2         8  
49 2 50       28 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       256 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         4 my $info_of = {};
69 2         4 for my $file ( @{$args} ) {
  2         6  
70 2         12 my @lines = App::RL::Common::read_lines($file);
71 2         512 for my $line (@lines) {
72 58         1444 for my $part ( split /\t/, $line ) {
73 174         834 my $info = App::RL::Common::decode_header($part);
74 174 100       13540 next unless App::RL::Common::info_is_valid($info);
75              
76 116         2490 my $chr = $info->{chr};
77 116 100       566 if ( !exists $graph_of_chr->{$chr} ) {
78 16         60 $graph_of_chr->{$chr} = Graph->new( directed => 0 );
79             }
80              
81 116         2462 my $range = App::RL::Common::encode_header( $info, 1 );
82 116 100       5318 if ( !$graph_of_chr->{$chr}->has_vertex($range) ) {
83 48         312 $graph_of_chr->{$chr}->add_vertex($range);
84 48         1312 $info->{intspan} = AlignDB::IntSpan->new;
85 48         838 $info->{intspan}->add_pair( $info->{start}, $info->{end} );
86 48         1970 $info_of->{$range} = $info;
87 48 50       136 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   7002 my ( $self, $chunk_ref, $chunk_id ) = @_;
98              
99 8         22 my $chr = $chunk_ref->[0];
100              
101 8         18 my $g = $graph_of_chr->{$chr};
102 8         72 my @ranges = sort $g->vertices;
103 8         505 my @edges;
104 8         29 for my $i ( 0 .. $#ranges ) {
105 24         163 my $range_i = $ranges[$i];
106             printf STDERR " " x 4 . "Range %d / %d\t%s\n", $i, $#ranges,
107             $range_i
108 24 50       56 if $opt->{verbose};
109 24         120 my $set_i = $info_of->{$range_i}{intspan};
110 24         167 for my $j ( $i + 1 .. $#ranges ) {
111 94         759 my $range_j = $ranges[$j];
112 94         303 my $set_j = $info_of->{$range_j}{intspan};
113              
114 94         521 my $i_set = $set_i->intersect($set_j);
115 94 100       19241 if ( $i_set->is_not_empty ) {
116 9         105 my $coverage_i = $i_set->size / $set_i->size;
117 9         220 my $coverage_j = $i_set->size / $set_j->size;
118 9 100 66     241 if ( $coverage_i >= $opt->{coverage}
119             and $coverage_j >= $opt->{coverage} )
120             {
121 5         15 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       22 if $opt->{verbose};
126             }
127             }
128             }
129             }
130 8 50       25 printf STDERR "Gather %d edges\n", scalar @edges if $opt->{verbose};
131              
132 8         61 MCE->gather( $chr, \@edges );
133 2         64 };
134             MCE::Flow::init {
135             chunk_size => 1,
136             max_workers => $opt->{parallel},
137 2         20 };
138 2         36 my %edge_of = mce_flow $worker, ( sort keys %{$graph_of_chr} );
  2         20  
139 1         901626 MCE::Flow::finish;
140              
141 1         95 for my $chr ( sort keys %{$graph_of_chr} ) {
  1         10  
142             print STDERR " " x 4 . "Add edges to chromosome [$chr]\n"
143 8 50       81 if $opt->{verbose};
144 8         6 for my $edge ( @{ $edge_of{$chr} } ) {
  8         17  
145 5         154 $graph_of_chr->{$chr}->add_edge( @{$edge} );
  5         24  
146             }
147             }
148              
149             #----------------------------#
150             # Merging
151             #----------------------------#
152 1         4 my @lines;
153 1         2 for my $chr ( sort keys %{$graph_of_chr} ) {
  1         7  
154 8         15 my $g = $graph_of_chr->{$chr};
155 8         25 my @cc = $g->connected_components;
156              
157             # filter cc with single range
158 8         5587 @cc = grep { scalar @{$_} > 1 } @cc;
  19         18  
  19         34  
159              
160 8         16 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         37 for my $range ( @{$c} ) {
  5         6  
165 10         334 my $set = $info_of->{$range}{intspan};
166 10         69 $merge_set->merge($set);
167             }
168              
169 5         302 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         322 for my $range ( @{$c} ) {
  5         12  
178 10 100       35 next if $range eq $merge_range;
179              
180 6         18 my $line = sprintf "%s\t%s", $range, $merge_range;
181 6         9 push @lines, $line;
182 6 50       22 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       8 if ( lc( $opt->{outfile} ) eq "stdout" ) {
192 1         3 $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         69  
199              
200 1         16 close $out_fh;
201             }
202              
203             1;