Skip to content

Commit 46d96fd

Browse files
Merge pull request #260 from ncsa/fix/contig-variants-remove-variant
Fix remove_variant to use position1 instead of position
2 parents dae00c1 + 0be5cfd commit 46d96fd

3 files changed

Lines changed: 146 additions & 6 deletions

File tree

neat/variants/contig_variants.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def check_if_del(self, other):
4949

5050
def check_if_ins(self, other):
5151
for insert in self.all_ins:
52-
if np.array_equal(other.genotype, insert.genotype) and insert.contains(other):
52+
if np.array_equal(other.genotype, insert.genotype) and insert.contains(other.position1):
5353
return insert
5454
return None
5555

@@ -150,11 +150,11 @@ def get_sample_info(variant):
150150
return get_genotype_string(variant.genotype)
151151

152152
def remove_variant(self, variant):
153-
if variant.position in self.variant_locations:
154-
if variant in self.contig_variants[variant.position]:
155-
self.contig_variants[variant.position].remove(variant)
156-
if not self.contig_variants[variant.position]:
157-
self.variant_locations.remove(variant.position)
153+
if variant.position1 in self.variant_locations:
154+
if variant in self.contig_variants[variant.position1]:
155+
self.contig_variants[variant.position1].remove(variant)
156+
if not self.contig_variants[variant.position1]:
157+
self.variant_locations.remove(variant.position1)
158158

159159
def __getitem__(self, input_location: int) -> list:
160160
"""
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Regression test for fix/contig-variants-check-if-ins.
3+
4+
check_if_ins was passing the variant object to Insertion.contains() which
5+
expects an int (a position). This caused the method to always return None
6+
even when a variant's position falls within an insertion's span.
7+
"""
8+
import numpy as np
9+
10+
from neat.variants.contig_variants import ContigVariants
11+
from neat.variants import Insertion, SingleNucleotideVariant
12+
13+
14+
_GT = np.array([0, 1])
15+
16+
17+
def _ins(pos, alt="ACGTT", length=4, gt=None):
18+
return Insertion(pos, length, alt, gt if gt is not None else _GT.copy(), "42")
19+
20+
21+
def _snv(pos, alt="T", gt=None):
22+
return SingleNucleotideVariant(pos, alt, gt if gt is not None else _GT.copy(), "42")
23+
24+
25+
def test_check_if_ins_finds_containing_insertion():
26+
"""SNV inside insertion span is detected."""
27+
cv = ContigVariants()
28+
ins = _ins(10, "ACGTT", 4)
29+
cv.add_variant(ins)
30+
snv = _snv(11) # position1=11 is within [10, 14)
31+
result = cv.check_if_ins(snv)
32+
assert result is ins
33+
34+
35+
def test_check_if_ins_at_insertion_start():
36+
"""SNV at the insertion's own position is detected."""
37+
cv = ContigVariants()
38+
ins = _ins(10, "ACGTT", 4)
39+
cv.add_variant(ins)
40+
snv = _snv(10)
41+
assert cv.check_if_ins(snv) is ins
42+
43+
44+
def test_check_if_ins_at_insertion_end_exclusive():
45+
"""Position at insertion start + length is outside the span."""
46+
cv = ContigVariants()
47+
ins = _ins(10, "ACGTT", 4) # spans [10, 14)
48+
cv.add_variant(ins)
49+
snv = _snv(14)
50+
assert cv.check_if_ins(snv) is None
51+
52+
53+
def test_check_if_ins_outside_span_returns_none():
54+
cv = ContigVariants()
55+
ins = _ins(10, "ACGTT", 4)
56+
cv.add_variant(ins)
57+
snv = _snv(50)
58+
assert cv.check_if_ins(snv) is None
59+
60+
61+
def test_check_if_ins_empty_returns_none():
62+
cv = ContigVariants()
63+
assert cv.check_if_ins(_snv(10)) is None
64+
65+
66+
def test_check_if_ins_genotype_mismatch_returns_none():
67+
"""Even if position matches, different genotype means no match."""
68+
cv = ContigVariants()
69+
ins = _ins(10, "ACGTT", 4, gt=np.array([1, 0]))
70+
cv.add_variant(ins)
71+
snv = _snv(11, gt=np.array([0, 1])) # different genotype
72+
assert cv.check_if_ins(snv) is None
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Regression test for fix/contig-variants-remove-variant.
3+
4+
remove_variant was using variant.position instead of variant.position1,
5+
causing it to silently no-op on any variant (since base variants only
6+
define position1, not position).
7+
"""
8+
import numpy as np
9+
from Bio.Seq import Seq
10+
from Bio.SeqRecord import SeqRecord
11+
12+
from neat.variants.contig_variants import ContigVariants
13+
from neat.variants import SingleNucleotideVariant
14+
15+
16+
_GT = np.array([0, 1])
17+
18+
19+
def _snv(pos, alt="T"):
20+
return SingleNucleotideVariant(pos, alt, _GT.copy(), "42")
21+
22+
23+
def test_remove_variant_removes_existing():
24+
cv = ContigVariants()
25+
v = _snv(10, "T")
26+
cv.add_variant(v)
27+
assert 10 in cv.variant_locations
28+
cv.remove_variant(v)
29+
assert 10 not in cv.variant_locations
30+
31+
32+
def test_remove_variant_empties_contig_variants_list():
33+
cv = ContigVariants()
34+
v = _snv(5, "C")
35+
cv.add_variant(v)
36+
cv.remove_variant(v)
37+
# The location is removed from variant_locations; the dict entry is empty
38+
assert v not in cv.contig_variants.get(5, [])
39+
40+
41+
def test_remove_variant_leaves_other_locations_intact():
42+
cv = ContigVariants()
43+
v1 = _snv(5, "C")
44+
v2 = _snv(20, "G")
45+
cv.add_variant(v1)
46+
cv.add_variant(v2)
47+
cv.remove_variant(v1)
48+
assert 20 in cv.variant_locations
49+
assert 5 not in cv.variant_locations
50+
51+
52+
def test_remove_variant_with_multiple_at_same_location():
53+
"""Removing one of two variants at the same position leaves the other."""
54+
cv = ContigVariants()
55+
v1 = SingleNucleotideVariant(10, "T", np.array([1, 0]), "42")
56+
v2 = SingleNucleotideVariant(10, "G", np.array([0, 1]), "42")
57+
cv.add_variant(v1)
58+
cv.add_variant(v2)
59+
cv.remove_variant(v1)
60+
assert 10 in cv.variant_locations
61+
assert v2 in cv.contig_variants[10]
62+
63+
64+
def test_remove_variant_noop_when_absent():
65+
"""Calling remove_variant on a variant not in ContigVariants is safe."""
66+
cv = ContigVariants()
67+
v = _snv(99, "A")
68+
cv.remove_variant(v) # should not raise

0 commit comments

Comments
 (0)