Skip to content

Commit 497fa29

Browse files
committed
Add class to handle error models
1 parent 5b5fbf6 commit 497fa29

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

lib/App/Sandy/Error.pm

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package App::Sandy::Error;
2+
# ABSTRACT: Base class for error models
3+
4+
use App::Sandy::Base 'class';
5+
6+
# VERSION
7+
8+
has '_not_base' => (
9+
is => 'ro',
10+
isa => 'HashRef',
11+
builder => '_build_not_base',
12+
lazy_build => 1
13+
);
14+
15+
sub _build_not_base {
16+
my %not_base = (
17+
A => ['T', 'C', 'G'],
18+
a => ['t', 'c', 'g'],
19+
T => ['A', 'C', 'G'],
20+
t => ['a', 'c', 'g'],
21+
C => ['A', 'T', 'G'],
22+
c => ['a', 't', 'g'],
23+
G => ['A', 'T', 'C'],
24+
g => ['a', 't', 'c']
25+
);
26+
return \%not_base;
27+
}
28+
29+
sub randb {
30+
my ($self, $base, $rng) = @_;
31+
return $self->_not_base->{$base}[$rng->get_n(3)] || $base;
32+
}

lib/App/Sandy/Error/Phred.pm

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package App::Sandy::Error::Phred;
2+
# ABSTRACT: Calculate error based on the phred score
3+
4+
use App::Sandy::Base 'class';
5+
6+
use constant ASCII_INIT => 0x21;
7+
8+
extends 'App::Sandy::Error';
9+
10+
# VERSION
11+
12+
sub insert_sequencing_error {
13+
my ($self, $read_ref, $quality_ref, $read_size, $rng) = @_;
14+
my @errors;
15+
16+
for (my $i = 0; $i < $read_size; $i++) {
17+
my $char = substr $$quality_ref, $i, 1;
18+
19+
if ($self->_is_there_any_error($char, $rng)) {
20+
my $base = substr $$read_ref, $i, 1;
21+
my $not_base = $self->randb($base, $rng);
22+
23+
substr($$read_ref, $i, 1) = $not_base;
24+
25+
push @errors => sprintf("%d:%s/%s", $i + 1, $base, $not_base);
26+
}
27+
}
28+
29+
return \@errors;
30+
}
31+
32+
sub _is_there_any_error {
33+
my ($self, $char, $rng) = @_;
34+
my $score = ord($char) - ASCII_INIT;
35+
my $prob = 10 ** (-$score / 10);
36+
return int($rng->uniform() * (1 / $prob)) == 0;
37+
}

t/lib/TestsFor/App/Sandy/Error.pm

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package TestsFor::App::Sandy::Error;
2+
# ABSTRACT: Tests for 'App::Sandy::Error' class
3+
4+
use App::Sandy::Base 'test';
5+
use App::Sandy::RNG;
6+
7+
use autodie;
8+
use base 'TestsFor';
9+
10+
use constant SEED => 17;
11+
12+
sub startup : Tests(startup) {
13+
my $test = shift;
14+
$test->SUPER::startup;
15+
my $class = ref $test;
16+
$class->mk_classdata('default_error');
17+
$class->mk_classdata('rng');
18+
}
19+
20+
sub setup : Tests(setup) {
21+
my $test = shift;
22+
my %child_arg = @_;
23+
$test->SUPER::setup;
24+
25+
$test->default_error($test->class_to_test->new());
26+
$test->rng(App::Sandy::RNG->new(SEED));
27+
}
28+
29+
sub not_base : Test(16) {
30+
my $test = shift;
31+
32+
my $class = $test->class_to_test;
33+
my $error = $test->default_error;
34+
my $rng = $test->rng;
35+
36+
my @bases = (qw/A T C G a t c g/);
37+
38+
for my $base (@bases) {
39+
my $not_base = $error->randb($base, $rng);
40+
isnt $base, $not_base,
41+
"base '$base' should not be equal ~base '$not_base'";
42+
like $not_base, qr/[ATCGatcg]/,
43+
"~base '$not_base' should be in [ATCGatcg]";
44+
}
45+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package TestsFor::App::Sandy::Error::Phred;
2+
# ABSTRACT: Tests for 'App::Sandy::Error::Phred' class
3+
4+
use App::Sandy::Base 'test';
5+
use App::Sandy::RNG;
6+
use base 'TestsFor::App::Sandy::Error';
7+
8+
use autodie;
9+
use base 'TestsFor';
10+
11+
sub startup : Tests(startup) {
12+
my $test = shift;
13+
$test->SUPER::startup;
14+
}
15+
16+
sub setup : Tests(setup) {
17+
my $test = shift;
18+
my %child_arg = @_;
19+
$test->SUPER::setup;
20+
21+
$test->default_error($test->class_to_test->new());
22+
}
23+
24+
sub insert_error : Tests(3) {
25+
my $test = shift;
26+
27+
my $class = $test->class_to_test;
28+
my $error = $test->default_error;
29+
my $rng = $test->rng;
30+
31+
my $read = 'ATCG' x 10;
32+
my %dep = (
33+
'!' => {
34+
min => 39, max => 41,
35+
msg => sub{ "n_errors '$_[0]' should be between ]39,41[" }
36+
},
37+
'$' => {
38+
min => 14, max => 26,
39+
msg => sub{ "n_errors '$_[0]' should be between ]14,26[" }
40+
},
41+
'%' => {
42+
min => 10, max => 23,
43+
msg => sub{ "n_errors '$_[0]' should be between ]10,23[" }
44+
}
45+
);
46+
47+
for my $char (keys %dep) {
48+
my $quality = $char x 40;
49+
50+
my $errors_a = $error->insert_sequencing_error(\$read,
51+
\$quality, 40, $rng);
52+
my $n_errors = scalar @$errors_a;
53+
54+
ok $n_errors < $dep{$char}{max} && $n_errors > $dep{$char}{min},
55+
$dep{$char}{msg}->($n_errors);
56+
}
57+
}

0 commit comments

Comments
 (0)