File Coverage

blib/lib/App/Rangeops/Command/merge.pm
Criterion Covered Total %
statement 99 125 79.2
branch 17 36 47.2
condition 0 3 0.0
subroutine 14 15 93.3
pod 6 6 100.0
total 136 185 73.5


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