#!/usr/bin/env python #test_rnaview.py """Provides tests for code in the rnaview.py file. """ from cogent.util.unit_test import TestCase, main from cogent.parse.rnaview import is_roman_numeral, is_edge, is_orientation,\ parse_annotation, parse_uncommon_residues, parse_base_pairs,\ parse_base_multiplets, parse_pair_counts, MinimalRnaviewParser,\ RnaviewParser, Base, BasePair, BasePairs, BaseMultiplet, BaseMultiplets,\ PairCounts, RnaViewObjectError, RnaViewParseError, MinimalRnaviewParser,\ parse_filename, parse_number_of_pairs, verify_bp_counts,\ in_chain, is_canonical, is_not_canonical, is_stacked, is_not_stacked,\ is_tertiary, is_not_stacked_or_tertiary, is_tertiary_base_base __author__ = "Greg Caporaso and Sandra Smit" __copyright__ = "Copyright 2007-2012, The Cogent Project" __credits__ = ["Greg Caporaso", "Sandra Smit", "Rob Knight"] __license__ = "GPL" __version__ = "1.5.3" __maintainer__ = "Sandra Smit" __email__ = "sandra.smit@colorado.edu" __status__ = "Production" #============================================================================== # RNAVIEW OBJECTS TESTS #============================================================================== class BaseTests(TestCase): """Tests for Base class""" def test_init(self): """Base __init__: should initialize on standard data""" b = Base('A','30','G') self.assertEqual(b.ChainId, 'A') self.assertEqual(b.ResId, '30') self.assertEqual(b.ResName, 'G') #ResId or ResName can't be None or empty string self.assertRaises(RnaViewObjectError, Base, None, None, 'G') self.assertRaises(RnaViewObjectError, Base, '1', 'A', '') self.assertRaises(RnaViewObjectError, Base, None, '', 'C') #Can pass RnaViewSeqPos (str) b = Base('C','12','A','10') self.assertEqual(b.RnaViewSeqPos, '10') def test_str(self): """Base __str__: should return correct string""" b = Base('A','30','G') self.assertEqual(str(b), 'A 30 G') def test_eq(self): """Base ==: functions as expected """ # Define a standard to compare others b = Base('A','30','G') # Identical to b b_a = Base('A','30','G') # Differ in Chain from b b_b = Base('B','30','G') # Differ in ResId from b b_c = Base('A','25','G') # Differ in ResName from b b_d = Base('A','30','C') # Differ in RnaViewSeqPos b_e = Base('A','30','G','2') # Differ in everything from b b_e = Base('C','12','U','1') self.assertEqual(b == b, True) self.assertEqual(b_a == b, True) self.assertEqual(b_b == b, False) self.assertEqual(b_c == b, False) self.assertEqual(b_d == b, False) self.assertEqual(b_e == b, False) def test_ne(self): """Base !=: functions as expected""" # Define a standard to compare others b = Base('A','30','G') # Identical to b b_a = Base('A','30','G') # Differ in Chain from b b_b = Base('B','30','G') # Differ in ResId from b b_c = Base('A','25','G') # Differ in ResName from b b_d = Base('A','30','C') # Differ in everything from b b_e = Base('C','12','U') self.assertEqual(b != b, False) self.assertEqual(b_a != b, False) self.assertEqual(b_b != b, True) self.assertEqual(b_c != b, True) self.assertEqual(b_d != b, True) self.assertEqual(b_e != b, True) class BasePairTests(TestCase): """Tests for BasePair object""" def setUp(self): """setUp method for all tests in BasePairTests""" self.b1 = Base('A','30','G') self.b2 = Base('A','36','C') self.bp = BasePair(self.b1, self.b2, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation=None) def test_init(self): """BasePair __init__: should initialize on standard data""" self.failUnless(self.bp.Up is self.b1) self.failUnless(self.bp.Down is self.b2) self.failUnless(self.bp.Conformation is None) self.assertEqual(self.bp.Edges, 'H/W') self.assertEqual(self.bp.Orientation, 'cis') self.assertEqual(self.bp.Saenger, 'XI') def test_str(self): """BasePair __str__: should return correct string""" self.assertEqual(str(self.bp), "Bases: A 30 G -- A 36 C; Annotation: H/W -- cis -- "+\ "None -- XI;") def test_eq(self): "BasePair ==: should function as expected" # identical up = Base('A','30','G') down = Base('A','36','C') bp = BasePair(up, down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp == self.bp, True) # diff up base diff_up = Base('C','12','A') bp = BasePair(diff_up, down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp == self.bp, False) # diff down base diff_down = Base('D','13','U') bp = BasePair(up, diff_down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp == self.bp, False) # diff edges bp = BasePair(up, down, Edges='W/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp == self.bp, False) # diff orientation bp = BasePair(up, down, Edges='H/W', Saenger='XI',\ Orientation='tran', Conformation=None) self.assertEqual(bp == self.bp, False) # diff conformation bp = BasePair(up, down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation='syn') self.assertEqual(bp == self.bp, False) # diff saenger bp = BasePair(up, down, Edges='H/W', Saenger='XIX',\ Orientation='cis', Conformation=None) self.assertEqual(bp == self.bp, False) def test_ne(self): "BasePair !=: should function as expected" # identical up = Base('A','30','G') down = Base('A','36','C') bp = BasePair(up, down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp != self.bp, False) # diff up base diff_up = Base('C','12','A') bp = BasePair(diff_up, down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp != self.bp, True) # diff down base diff_down = Base('D','13','U') bp = BasePair(up, diff_down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp != self.bp, True) # diff edges bp = BasePair(up, down, Edges='W/W', Saenger='XI',\ Orientation='cis', Conformation=None) self.assertEqual(bp != self.bp, True) # diff orientation bp = BasePair(up, down, Edges='H/W', Saenger='XI',\ Orientation='tran', Conformation=None) self.assertEqual(bp != self.bp, True) # diff conformation bp = BasePair(up, down, Edges='H/W', Saenger='XI',\ Orientation='cis', Conformation='syn') self.assertEqual(bp != self.bp, True) # diff saenger bp = BasePair(up, down, Edges='H/W', Saenger='XIX',\ Orientation='cis', Conformation=None) self.assertEqual(bp != self.bp, True) def test_isWC(self): """BasePair isWC: should return True for GC or AU pair""" bp = BasePair(Base('A','30','G'), Base('A','36','C')) self.assertEqual(bp.isWC(), True) bp = BasePair(Base('A','30','g'), Base('A','36','C')) self.assertEqual(bp.isWC(), True) bp = BasePair(Base('A','30','C'), Base('A','36','G')) self.assertEqual(bp.isWC(), True) bp = BasePair(Base('A','30','U'), Base('A','36','a')) self.assertEqual(bp.isWC(), True) bp = BasePair(Base('A','30','G'), Base('A','36','U')) self.assertEqual(bp.isWC(), False) def test_isWobble(self): """BasePair isWobble: should return True for GU pair""" bp = BasePair(Base('A','30','G'), Base('A','36','U')) self.assertEqual(bp.isWobble(), True) bp = BasePair(Base('A','30','g'), Base('A','36','U')) self.assertEqual(bp.isWobble(), True) bp = BasePair(Base('A','30','u'), Base('A','36','g')) self.assertEqual(bp.isWobble(), True) bp = BasePair(Base('A','30','A'), Base('A','36','U')) self.assertEqual(bp.isWobble(), False) class BasePairsTests(TestCase): """Tests for BasePairs object""" def setUp(self): """setUp method for all BasePairs tests""" self.a1 = BasePair(Base('A','30','G'), Base('A','36','C'), Saenger='XX') self.a2 = BasePair(Base('A','31','A'), Base('A','35','U'), Saenger='XX') self.a3 = BasePair(Base('A','40','G'), Base('A','60','U'), Saenger='V') self.a4 = BasePair(Base('A','41','A'), Base('A','58','U'),\ Saenger=None) self.ab1 = BasePair(Base('A','41','A'), Base('B','58','U')) self.ac1 = BasePair(Base('A','10','C'), Base('C','3','G')) self.bc1 = BasePair(Base('B','41','A'), Base('C','1','U')) self.bn1 = BasePair(Base('B','41','A'), Base(None,'1','U')) self.cd1 = BasePair(Base('C','41','A'), Base('D','1','U')) self.bp1 = BasePair(Base('A','34','U'), Base('A','40','A')) self.bp2 = BasePair(Base('A','35','C'), Base('A','39','G')) self.bp3 = BasePair(Base('B','32','G'), Base('B','38','U')) self.bp4 = BasePair(Base('B','33','G'), Base('B','37','C')) self.bp5 = BasePair(Base('A','31','C'), Base('B','41','G')) self.bp6 = BasePair(Base('A','32','U'), Base('B','40','A')) self.bp7 = BasePair(Base('A','37','U'), Base('B','35','A')) self.pairs = BasePairs([self.bp1, self.bp2, self.bp3, self.bp4,\ self.bp5, self.bp6, self.bp7]) def test_init(self): """BasePairs __init__: should work with or without Model""" # init from list bps = BasePairs([self.a1, self.a2]) self.failUnless(bps[0] is self.a1) self.failUnless(bps[1] is self.a2) # init from tuple bps = BasePairs((self.a1, self.a2)) self.failUnless(bps[0] is self.a1) self.failUnless(bps[1] is self.a2) def test_str(self): """BasePairs __str__: should produce expected string""" b1 = BasePair(Base('A','30','G'), Base('A','36','C'), Saenger='XX') b2 = BasePair(Base('A','31','A'), Base('A','35','U'),\ Orientation='cis',Edges='W/W') bps = BasePairs([b1, b2]) exp_lines = [ "===================================================================",\ "Bases: Up -- Down; Annotation: Edges -- Orient. -- Conf. -- Saenger",\ "===================================================================",\ "Bases: A 30 G -- A 36 C; Annotation: None -- None -- None -- XX;",\ "Bases: A 31 A -- A 35 U; Annotation: W/W -- cis -- None -- None;"] self.assertEqual(str(bps), '\n'.join(exp_lines)) def test_select(self): """BasePairs select: should work with any good function""" def xx(bp): if bp.Saenger == 'XX': return True return False bps = BasePairs([self.a1, self.a2, self.a3, self.a4]) obs = bps.select(xx) self.assertEqual(len(obs), 2) self.failUnless(obs[0] is self.a1) self.failUnless(obs[1] is self.a2) for i in obs: self.assertEqual(i.Saenger, 'XX') def test_PresentChains(self): """BasePairs PresentChains: should work on single/multiple chain(s)""" bps = BasePairs([self.a1, self.a2, self.a3, self.a4]) self.assertEqual(bps.PresentChains, ['A']) bps = BasePairs([self.a1, self.ab1]) self.assertEqualItems(bps.PresentChains, ['A','B']) bps = BasePairs([self.a1, self.ab1, self.ac1, self.bc1]) self.assertEqualItems(bps.PresentChains, ['A','B', 'C']) bps = BasePairs([self.a1, self.ab1, self.bn1]) self.assertEqualItems(bps.PresentChains, [None, 'A','B']) def test_cliques(self): """BasePairs cliques: single/multiple chains and cliques""" #one chain, one clique bps = BasePairs([self.a1, self.a2, self.a3, self.a4]) obs_cl = list(bps.cliques()) self.assertEqual(len(obs_cl), 1) #3 chains, 2 cliques bps = BasePairs([self.a1, self.a2, self.cd1]) obs_cl = list(bps.cliques()) self.assertEqual(len(obs_cl), 2) self.assertEqual(len(obs_cl[0]), 2) self.assertEqual(len(obs_cl[1]), 1) self.failUnless(obs_cl[1][0] is self.cd1) self.assertEqual(obs_cl[1].PresentChains, ['C','D']) #5 chains, 1 clique bps = BasePairs([self.a1, self.ab1, self.bc1, self.bn1, self.cd1]) obs_cl = list(bps.cliques()) self.assertEqual(len(obs_cl), 1) self.assertEqual(len(obs_cl[0]), 5) self.failUnless(obs_cl[0][0] is self.a1) self.assertEqualItems(obs_cl[0].PresentChains, ['A','B','C','D', None]) def test_hasConflicts(self): """BasePairs hadConflicts: handle chains and residue IDs""" # no conflict b1 = BasePair(Base('A','30','G'), Base('A','36','C'), Saenger='XX') b2 = BasePair(Base('A','31','A'), Base('A','35','U'),\ Orientation='cis',Edges='W/W') b3 = BasePair(Base('A','15','G'), Base('A','42','C')) bps = BasePairs([b1, b2, b3]) self.assertEqual(bps.hasConflicts(), False) self.assertEqual(bps.hasConflicts(return_conflict=True), (False, None)) # conflict within chain b1 = BasePair(Base('A','30','G'), Base('A','36','C'), Saenger='XX') b2 = BasePair(Base('A','31','A'), Base('A','35','U'),\ Orientation='cis',Edges='W/W') b3 = BasePair(Base('A','30','G'), Base('A','42','C')) bps = BasePairs([b1, b2, b3]) self.assertEqual(bps.hasConflicts(), True) # conflict within chain -- return conflict b1 = BasePair(Base('A','30','G'), Base('A','36','C'), Saenger='XX') b2 = BasePair(Base('A','31','A'), Base('A','35','U'),\ Orientation='cis',Edges='W/W') b3 = BasePair(Base('A','30','G'), Base('A','42','C')) bps = BasePairs([b1, b2, b3]) self.assertEqual(bps.hasConflicts(return_conflict=True),\ (True, "A 30 G")) # no conflict, same residue ID, different chain b1 = BasePair(Base('A','30','G'), Base('A','36','C'), Saenger='XX') b2 = BasePair(Base('A','31','A'), Base('A','35','U'),\ Orientation='cis',Edges='W/W') b3 = BasePair(Base('C','30','G'), Base('A','42','C')) bps = BasePairs([b1, b2, b3]) self.assertEqual(bps.hasConflicts(), False) class BaseMultipletTests(TestCase): """Tests for BaseMultiplet object""" def test_init(self): """BaseMultiplet __init__: should work as expected""" b1 = Base('A','30','A') b2 = Base('A','35','G') b3 = Base('A','360','U') bm = BaseMultiplet([b1, b2, b3]) self.failUnless(bm[0] is b1) self.failUnless(bm[2] is b3) #should work from tuple also bm = BaseMultiplet((b1, b2, b3)) self.failUnless(bm[0] is b1) self.failUnless(bm[2] is b3) def test_str(self): """BaseMultiplet __str__: should give expected string""" b1 = Base('A','30','A') b2 = Base('A','35','G') b3 = Base('A','360','U') bm = BaseMultiplet([b1, b2, b3]) exp = "A 30 A -- A 35 G -- A 360 U;" self.assertEqual(str(bm), exp) class BaseMultipletsTests(TestCase): """Tests for BaseMultiplets object""" def test_init(self): """BaseMultiplets __init__: from list and tuple""" b1 = Base('A','30','A') b2 = Base('A','35','G') b3 = Base('A','360','U') bm1 = BaseMultiplet([b1, b2, b3]) b4 = Base('B','12','C') b5 = Base('B','42','U') b6 = Base('C','2','A') bm2 = BaseMultiplet([b4, b5, b6]) bms = BaseMultiplets([bm1, bm2]) self.failUnless(bms[0] is bm1) self.failUnless(bms[1] is bm2) self.assertEqual(bms[1][2].ResId, '2') #should work from tuple also bms = BaseMultiplets((bm1, bm2)) self.failUnless(bms[0] is bm1) self.failUnless(bms[1] is bm2) self.assertEqual(bms[1][2].ResId, '2') def test_str(self): """BaseMultiplets __str__: should give expected string""" b1 = Base('A','30','A') b2 = Base('A','35','G') b3 = Base('A','360','U') bm1 = BaseMultiplet([b1, b2, b3]) b4 = Base('B','12','C') b5 = Base('B','42','U') b6 = Base('C','2','A') bm2 = BaseMultiplet([b4, b5, b6]) bms = BaseMultiplets([bm1, bm2]) exp_lines = [\ "A 30 A -- A 35 G -- A 360 U;",\ "B 12 C -- B 42 U -- C 2 A;"] self.assertEqual(str(bms), '\n'.join(exp_lines)) class TestPairCounts(TestCase): """Tests for PairCounts object. Contains only test for __init__. Everything should fucntion as a dict. """ def test_init(self): """PairCounts __init__: should work as dict""" res = PairCounts(\ {'Standard':1, 'WS--cis':300, 'Bifurcated': 2, 'HS-tran':0}) self.assertEqual(res['Standard'], 1) self.assertEqual(res['WS--cis'], 300) self.assertEqual(res['Bifurcated'], 2) self.assertEqual(res['HS-tran'], 0) #============================================================================== # SELECTION FUNCTIONS TESTS #============================================================================== class SelectionFunctionTests(TestCase): def test_in_chain(self): b1, b2 = Base('A','30','C'), Base('A','40','G') bp1 = BasePair(b1, b2) self.assertEqual(in_chain("A")(bp1), True) self.assertEqual(in_chain(["A",'B'])(bp1), True) self.assertEqual(in_chain("B")(bp1), False) b3, b4 = Base('A','30','C'), Base('B','40','G') bp2 = BasePair(b3,b4) self.assertEqual(in_chain("A")(bp2), False) self.assertEqual(in_chain(["A",'B'])(bp2), True) self.assertEqual(in_chain("AB")(bp2), True) self.assertEqual(in_chain("AC")(bp2), False) b5, b6 = Base('A','30','C'), Base('C','40','G') bp3 = BasePair(b5,b6) self.assertEqual(in_chain("A")(bp3), False) self.assertEqual(in_chain("C")(bp3), False) self.assertEqual(in_chain(["A",'B'])(bp3), False) self.assertEqual(in_chain("AC")(bp3), True) def test_is_canocical(self): """is_canonical: work on annotation, not base identity""" b1, b2 = Base('A','30','C'), Base('A','40','G') bp = BasePair(b1, b2, Edges='+/+') self.assertEqual(is_canonical(bp), True) bp = BasePair(b1, b2, Edges='-/-') self.assertEqual(is_canonical(bp), True) bp = BasePair(b1, b2, Edges='W/W') self.assertEqual(is_canonical(bp), False) bp = BasePair(b1, b2, Edges='W/W', Orientation='cis',Saenger='XXVIII') self.assertEqual(is_canonical(bp), True) def test_is_not_canocical(self): """is_not_canonical: opposite of is_canonical""" b1, b2 = Base('A','30','C'), Base('A','40','G') bp = BasePair(b1, b2, Edges='+/+') self.assertEqual(is_not_canonical(bp), False) bp = BasePair(b1, b2, Edges='-/-') self.assertEqual(is_not_canonical(bp), False) bp = BasePair(b1, b2, Edges='W/W') self.assertEqual(is_not_canonical(bp), True) bp = BasePair(b1, b2, Edges='W/W', Orientation='cis',Saenger='XXVIII') self.assertEqual(is_not_canonical(bp), False) def test_is_stacked(self): """is_stacked: checks annotation, not base identity""" b1, b2 = Base('A','30','C'), Base('A','40','A') bp = BasePair(b1, b2, Edges='stacked') self.assertEqual(is_stacked(bp), True) bp = BasePair(b1, b2, Edges='H/?') self.assertEqual(is_stacked(bp), False) def test_is_not_stacked(self): """is_not_stacked: opposite of is_stacked""" b1, b2 = Base('A','30','C'), Base('A','40','A') bp = BasePair(b1, b2, Edges='stacked') self.assertEqual(is_not_stacked(bp), False) bp = BasePair(b1, b2, Edges='H/?') self.assertEqual(is_not_stacked(bp), True) def test_is_tertiary(self): """is_tertiary: checks annotation, not base identity""" b1, b2 = Base('A','30','C'), Base('A','40','U') bp = BasePair(b1, b2, Saenger='!1H(b_b)') self.assertEqual(is_tertiary(bp), True) bp = BasePair(b1, b2, Edges='H/?', Saenger='XX') self.assertEqual(is_tertiary(bp), False) bp = BasePair(b1,b2, Edges='stacked') self.assertEqual(is_tertiary(bp), False) def test_is_not_stacked_or_tertiary(self): """is_not_stacked_or_tertiary: checks annotation, not base identity""" b1, b2 = Base('A','30','C'), Base('A','40','U') bp = BasePair(b1, b2, Saenger='!1H(b_b)') self.assertEqual(is_not_stacked_or_tertiary(bp), False) bp = BasePair(b1, b2, Edges='stacked') self.assertEqual(is_not_stacked_or_tertiary(bp), False) bp = BasePair(b1, b2, Edges='W/W', Saenger='XX') self.assertEqual(is_not_stacked_or_tertiary(bp), True) def test_is_tertiary_base_base(self): """is_tertiary_base_base: checks annotation, not base identity""" b1, b2 = Base('A','30','C'), Base('A','40','U') bp = BasePair(b1, b2, Saenger='!1H(b_b)') self.assertEqual(is_tertiary_base_base(bp), True) bp = BasePair(b1, b2, Edges='H/?', Saenger='!(s_s)') self.assertEqual(is_tertiary_base_base(bp), False) #============================================================================== # RNAVIEW PARSER TESTS #============================================================================== class RnaviewParserTests(TestCase): """Tests for RnaviewParser and related code""" def test_is_roman_numeral(self): """is_roman_numeral: should work for all, including comma""" self.assertEqual(is_roman_numeral('XIII'),True) self.assertEqual(is_roman_numeral('Xiii'),False) self.assertEqual(is_roman_numeral('MMCDXXVIII'),True) self.assertEqual(is_roman_numeral('XII,XIII'),True) self.assertEqual(is_roman_numeral('n/a'),False) def test_is_edge(self): """is_edge: should identify valid edges correctly""" self.assertEqual(is_edge('H/W'),True) self.assertEqual(is_edge('./W'),True) self.assertEqual(is_edge('+/+'),True) self.assertEqual(is_edge(' '),False) self.assertEqual(is_edge('P/W'),False) self.assertEqual(is_edge('X/W'),True) self.assertEqual(is_edge('X/X'),True) def test_is_orientation(self): """is_orientation: should fail on anything but 'cis' or 'tran'""" self.assertEqual(is_orientation('cis'),True) self.assertEqual(is_orientation('tran'),True) self.assertEqual(is_orientation('tranxxx'),False) def test_parse_annotation(self): """parse_annotation: should return correct tuple of 4 or raise error """ self.assertEqual(parse_annotation(['W/S', 'tran', 'syn', 'syn',\ 'n/a']), ('W/S','tran','syn syn','n/a')) self.assertEqual(parse_annotation(['syn','stacked']),\ ('stacked', None,'syn',None)) self.assertEqual(parse_annotation(['W/W','tran','syn','XII,XIII']),\ ('W/W', 'tran','syn','XII,XIII')) self.assertEqual(parse_annotation(['./W','cis','!1H(b_b)']),\ ('./W', 'cis',None,'!1H(b_b)')) self.assertEqual(parse_annotation([]),\ (None, None, None, None)) self.assertRaises(RnaViewParseError, parse_annotation, ['X--X']) def test_parse_filename(self): """parse_filename: should return name of file""" lines = ["PDB data file name: pdb1t4l.ent_nmr.pdb"] self.assertEqual(parse_filename(lines), 'pdb1t4l.ent_nmr.pdb') lines = ["PDB data file name: pdb1t4l.ent_nmr.pdb","other line"] self.assertRaises(RnaViewParseError, parse_filename, lines) def test_parse_uncommon_residues(self): """parse_uncommon_residues: should fail on some missing residue info """ lines = UC_LINES.split('\n') self.assertEqual(parse_uncommon_residues(lines),\ {('D','16','TLN'):'u',('D','17','LCG'):'g',\ ('0','2588','OMG'):'g',(' ','2621','PSU'):'P'}) for l in UC_LINES_WRONG.split('\n'): self.assertRaises(RnaViewParseError, parse_uncommon_residues, [l]) def test_parse_base_pairs_basic(self): """parse_base_pairs: basic input""" basic_lines =\ ['25_437, 0: 34 C-G 448 0: +/+ cis XIX',\ '26_436, 0: 35 U-A 447 0: -/- cis XX'] bp1 = BasePair(Up=Base('0','34','C','25'),\ Down=Base('0','448','G','437'),\ Edges='+/+', Orientation='cis',Conformation=None,Saenger='XIX') bp2 = BasePair(Up=Base('0','35','U','26'),\ Down=Base('0','447','A','436'),\ Edges='-/-', Orientation='cis',Conformation=None,Saenger='XX') bps = BasePairs([bp1,bp2]) obs = parse_base_pairs(basic_lines) for o,e in zip(obs,[bp1,bp2]): self.assertEqual(o,e) self.assertEqual(len(obs), 2) basic_lines =\ ['25_437, 0: 34 c-P 448 0: +/+ cis XIX',\ '26_436, 0: 35 U-X 447 0: -/- cis XX'] self.assertRaises(RnaViewParseError, parse_base_pairs, basic_lines) basic_lines =\ ['25_437, 0: 34 c-P 448 0: +/+ cis XIX',\ '26_436, 0: 35 I-A 447 0: -/- cis XX'] bp1 = BasePair(Up=Base('0','34','c','25'),\ Down=Base('0','448','P','437'),\ Edges='+/+', Orientation='cis',Conformation=None,Saenger='XIX') bp2 = BasePair(Up=Base('0','35','I','26'),\ Down=Base('0','447','A','436'),\ Edges='-/-', Orientation='cis',Conformation=None,Saenger='XX') bps = BasePairs([bp1,bp2]) obs = parse_base_pairs(basic_lines) for o,e in zip(obs,[bp1,bp2]): self.assertEqual(o,e) self.assertEqual(len(obs), 2) lines = ['1_2, : 6 G-G 7 : stacked',\ '1_16, : 6 G-C 35 : +/+ cis XIX'] bp1 = BasePair(Up=Base(' ','6','G','1'),\ Down=Base(' ','7','G','2'), Edges='stacked') bp2 = BasePair(Up=Base(' ','6','G','1'),\ Down=Base(' ','35','C','16'),\ Edges='+/+', Orientation='cis',Conformation=None,Saenger='XIX') obs = parse_base_pairs(lines) for o,e in zip(obs,[bp1,bp2]): self.assertEqual(o,e) self.assertEqual(len(obs), 2) def test_parse_base_multiplets_basic(self): """parse_base_multiplets: basic input""" basic_lines =\ ['235_237_254_| [20 3] 0: 246 G + 0: 248 A + 0: 265 U',\ '273_274_356_| [21 3] 0: 284 C + 0: 285 A + 0: 367 G'] bm1 = BaseMultiplet([Base('0','246','G','235'),\ Base('0','248','A','237'), Base('0','265','U','254')]) bm2 = BaseMultiplet([Base('0','284','C','273'),\ Base('0','285','A','274'), Base('0','367','G','356')]) bms = BaseMultiplets([bm1,bm2]) obs = parse_base_multiplets(basic_lines) for o,e in zip(obs,bms): for base_x, base_y in zip(o,e): self.assertEqual(base_x,base_y) self.assertEqual(len(obs), 2) self.assertEqual(len(obs[0]), 3) basic_lines =\ ['235_237_254_| [20 3] 0: 246 G + 0: 248 A + 0: 265 I',\ '273_274_356_| [21 3] 0: 284 P + 0: 285 a + 0: 367 G'] bm1 = BaseMultiplet([Base('0','246','G','235'),\ Base('0','248','A','237'), Base('0','265','I','254')]) bm2 = BaseMultiplet([Base('0','284','P','273'),\ Base('0','285','a','274'), Base('0','367','G','356')]) bms = BaseMultiplets([bm1,bm2]) obs = parse_base_multiplets(basic_lines) for o,e in zip(obs,bms): for base_x, base_y in zip(o,e): self.assertEqual(base_x,base_y) self.assertEqual(len(obs), 2) self.assertEqual(len(obs[0]), 3) def test_parse_base_multiplets_errors(self): """parse_base_multiplets: error checking""" # Unknown base basic_lines =\ ['235_237_254_| [20 3] 0: 246 X + 0: 248 A + 0: 265 U',\ '273_274_356_| [21 3] 0: 284 C + 0: 285 A + 0: 367 G'] self.assertRaises(RnaViewParseError, parse_base_multiplets,\ basic_lines) # number of rnaview_seqpos doesn't match number of bases basic_lines =\ ['235_237_| [20 3] 0: 246 X + 0: 248 A + 0: 265 U',\ '273_274_356_| [21 3] 0: 284 C + 0: 285 A + 0: 367 G'] self.assertRaises(RnaViewParseError, parse_base_multiplets,\ basic_lines) # Number of reported bases incorrect basic_lines =\ ['235_237_254_| [20 3] 0: 246 X + 0: 248 A + 0: 265 U',\ '273_274_356_| [21 5] 0: 284 C + 0: 285 A + 0: 367 G'] self.assertRaises(RnaViewParseError, parse_base_multiplets,\ basic_lines) def test_parse_number_of_pairs(self): """parse_number_of_pairs: good/bad input""" lines = ["The total base pairs = 31 (from 65 bases)"] exp = {'NUM_PAIRS':31, 'NUM_BASES':65} self.assertEqual(parse_number_of_pairs(lines), exp) lines = ["The total base pairs = 31 (from 65 bases)","XXX"] self.assertRaises(RnaViewParseError, parse_number_of_pairs, lines) lines = ["The total base pairs = 31(from 65 bases)"] self.assertRaises(RnaViewParseError, parse_number_of_pairs, lines) def test_parse_pair_counts(self): """parse_pair_counts: should work for even number of lines""" lines = PC_COUNTS1.split('\n') res = parse_pair_counts(lines) self.assertEqual(res['Standard'], 1) self.assertEqual(res['WS--cis'], 300) self.assertEqual(res['Bifurcated'], 2) self.assertEqual(res['HS-tran'], 0) lines = PC_COUNTS2.split('\n') res = parse_pair_counts(lines) self.assertEqual(res['Standard'], 19) self.assertEqual(res['WW-tran'], 1) self.assertEqual(res['HS-tran'], 0) self.failIf('Bifurcated' in res) self.assertRaises(RnaViewParseError, parse_pair_counts,\ PC_COUNTS2.split('\n')[:-1]) self.assertEqual(parse_pair_counts([]),{}) def test_verify_bp_counts(self): """verify_bp_count: should raise an error if bp counts are wrong""" lines = RNAVIEW_PDB_REAL.split('\n') obs = RnaviewParser(lines) # this shouldn't raise an error verify_bp_counts(obs['BP'],11,obs['PC']) # reported number isn't right self.assertRaises(RnaViewParseError,\ verify_bp_counts, obs['BP'], 12, obs['PC']) # No longer checks for the base pair counts reported in the # dictionary, b/c this number doens't match the total when # modified bases are present. ## PREVIOUS TEST: # pair_counts isn't right #obs['PC']['Standard'] = 14 #self.assertRaises(RnaViewParseError,\ # verify_bp_counts, obs['BP'], 11, obs['PC']) ## NEW TEST obs['PC']['Standard'] = 14 verify_bp_counts(obs['BP'],11,obs['PC']) def test_MinimalRnaviewParser(self): """MinimalRnaviewParser: should divide lines into right classes""" exp = {'FN': ['PDB data file name: 1EHZ.pdb'], 'UC':\ ['uncommon residue I 1 on chain A [#1] assigned to: I', 'uncommon residue 2MG 10 on chain A [#10] assigned to: g'], 'BP':['1_72, A: 1 I-C 72 A: X/X cis n/a', '58_60, A: 58 a-C 60 A: S/S tran syn !(s_s)'], 'BM':['9_12_23_| [1 3] A: 9 A + A: 12 U + A: 23 A', '13_22_46_| [2 3] A: 13 C + A: 22 G + A: 46 g'], 'PC':['Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran', '19 3 1 0 1 0 0', 'WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran', '0 3 0 2 0 0'], 'NP':['The total base pairs = 30 (from 76 bases)']} obs = MinimalRnaviewParser(RNAVIEW_LINES.split('\n')) self.assertEqual(len(obs), len(exp)) self.assertEqual(obs, exp) def test_MinimalRnaviewParser_short(self): """MinimalRnaviewparser: should leave lists empty if no lines found""" lines = RNAVIEW_LINES_SHORT.split('\n') res = MinimalRnaviewParser(lines) self.assertEqual(len(res['FN']), 1) self.assertEqual(res['UC'], []) self.assertEqual(res['BM'], []) self.assertEqual(len(res['PC']), 4) self.assertEqual(len(res['BP']), 11) self.assertEqual(len(res['NP']), 1) def test_RnaviewParser(self): """RnaviewParser: should work with/without model and/or verification """ rnaview_lines = RNAVIEW_PDB_REAL.split('\n') obs = RnaviewParser(rnaview_lines) self.assertEqual(obs['FN'], 'pdb430d.ent') self.assertEqual(len(obs['UC']),1) self.assertEqual(len(obs['BP']),19) self.assertEqual(len(obs['BM']),0) self.assertEqual(obs['BM'],BaseMultiplets()) self.assertEqual(obs['PC']['Standard'],7) self.assertEqual(obs['BP'][2].Down.ResName,'c') self.assertEqual(obs['BP'][6].Edges,'stacked') self.assertEqual(obs['NP']['NUM_PAIRS'], 11) self.assertEqual(obs['NP']['NUM_BASES'], 29) def test_RnaviewParser_error(self): """RnaviewParser: strict or not""" lines = RNAVIEW_LINES_ERROR.split('\n') self.assertRaises(RnaViewParseError, RnaviewParser, lines, strict=True) obs = RnaviewParser(lines, strict=False) self.assertEqual(obs['NP'], None) self.assertEqual(obs['BP'][1].Up.ResId, '2') self.assertEqual(obs['PC']['Standard'], 6) RNAVIEW_LINES=\ """PDB data file name: 1EHZ.pdb uncommon residue I 1 on chain A [#1] assigned to: I uncommon residue 2MG 10 on chain A [#10] assigned to: g BEGIN_base-pair 1_72, A: 1 I-C 72 A: X/X cis n/a 58_60, A: 58 a-C 60 A: S/S tran syn !(s_s) END_base-pair Summary of triplets and higher multiplets BEGIN_multiplets 9_12_23_| [1 3] A: 9 A + A: 12 U + A: 23 A 13_22_46_| [2 3] A: 13 C + A: 22 G + A: 46 g END_multiplets The total base pairs = 30 (from 76 bases) ------------------------------------------------ Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran 19 3 1 0 1 0 0 WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran 0 3 0 2 0 0 ------------------------------------------------ """ RNAVIEW_LINES_SHORT=\ """PDB data file name: pdb17ra.ent_nmr.pdb BEGIN_base-pair 1_21, : 1 G-C 21 : +/+ cis XIX 2_20, : 2 G-C 20 : +/+ cis XIX 3_19, : 3 C-G 19 : +/+ cis XIX 4_18, : 4 G-U 18 : W/W cis XXVIII 5_6, : 5 U-A 6 : stacked 5_17, : 5 U-A 17 : -/- cis XX 7_16, : 7 A-U 16 : W/W cis n/a 8_15, : 8 G-C 15 : +/+ cis XIX 9_14, : 9 G-C 14 : +/+ cis XIX 10_14, : 10 A-C 14 : stacked 11_13, : 11 U-A 13 : S/W tran n/a END_base-pair The total base pairs = 9 (from 21 bases) ------------------------------------------------ Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran 6 2 0 0 0 0 0 WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran 0 0 0 1 0 0 ------------------------------------------------""" PC_COUNTS1=\ """ Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran 1 0 0 0 0 0 0 WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran 12 0 300 0 0 0 Single-bond Bifurcated 0 2""" PC_COUNTS2=\ """ Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran 19 3 1 0 1 0 0 WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran 0 3 0 2 0 0""" UC_LINES=\ """uncommon residue TLN 16 on chain D [#16] assigned to: u uncommon residue LCG 17 on chain D [#17] assigned to: g uncommon residue OMG 2588 on chain 0 [#2430] assigned to: g uncommon residue PSU 2621 on chain [#2463] assigned to: P""" UC_LINES_WRONG=\ """uncommon residue 16 on chain D [#16] assigned to: u uncommon residue LCG on chain D [#17] assigned to: g uncommon residue OMG 2588 on chain 0 [#2430] assigned to: """ RNAVIEW_LINES_TOTAL=\ """PDB data file name: 1EHZ.pdb uncommon residue PSU 1 on chain A [#1] assigned to: P uncommon residue 2MG 10 on chain A [#10] assigned to: g BEGIN_base-pair 1_72, A: 1 P-C 20 A: +/+ cis n/a 1_72, A: 2 A-U 19 A: H/W cis n/a 58_60, A: 10 a-U 60 A: S/S tran syn !(s_s) END_base-pair Summary of triplets and higher multiplets BEGIN_multiplets 9_12_23_| [1 3] A: 1 P + A: 2 U + A: 60 U 13_22_46_| [2 3] A: 20 C + A: 19 U + A: 60 U END_multiplets The total base pairs = 30 (from 76 bases) ------------------------------------------------ Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran 19 3 1 0 1 0 0 WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran 0 3 0 2 0 0 ------------------------------------------------ """ RNAVIEW_PDB_REAL=\ """PDB data file name: pdb430d.ent uncommon residue +C 27 on chain A [#27] assigned to: c BEGIN_base-pair 1_29, A: 1 G-C 29 A: +/+ cis XIX 2_28, A: 2 G-C 28 A: +/+ cis XIX 3_27, A: 3 G-c 27 A: +/+ cis XIX 4_26, A: 4 U-A 26 A: -/- cis XX 5_25, A: 5 G-C 25 A: +/+ cis XIX 6_24, A: 6 C-G 24 A: +/+ cis XIX 7_8, A: 7 U-C 8 A: stacked 8_9, A: 8 C-A 9 A: stacked 9_21, A: 9 A-A 21 A: H/H tran II 11_20, A: 11 U-A 20 A: W/H tran XXIV 12_19, A: 12 A-G 19 A: H/S tran XI 13_18, A: 13 C-G 18 A: +/+ cis XIX 14_17, A: 14 G-A 17 A: S/H tran XI 25_26, A: 25 C-A 26 A: stacked 7_23, A: 7 U-C 23 A: W/W tran !1H(b_b) 8_23, A: 8 C-C 23 A: S/H cis !1H(b_b). 10_11, A: 10 G-U 11 A: S/H cis !1H(b_b) 11_21, A: 11 U-A 21 A: W/H tran !1H(b_b) 10_20, A: 10 G-A 20 A: W/. tran !(s_s) END_base-pair The total base pairs = 11 (from 29 bases) ------------------------------------------------ Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran 7 0 0 0 1 0 0 WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran 0 1 0 0 0 2 ------------------------------------------------""" RNAVIEW_LINES_ERROR=\ """PDB data file name: pdb17ra.ent_nmr.pdb BEGIN_base-pair 1_21, : 1 G-C 21 : +/+ cis XIX 2_20, : 2 G-C 20 : +/+ cis XIX 3_19, : 3 C-G 19 : +/+ cis XIX 4_18, : 4 G-U 18 : W/W cis XXVIII 5_6, : 5 U-A 6 : stacked 5_17, : 5 U-A 17 : -/- cis XX 7_16, : 7 A-U 16 : W/W cis n/a 8_15, : 8 G-C 15 : +/+ cis XIX 9_14, : 9 G-C 14 : +/+ cis XIX 10_14, : 10 A-C 14 : stacked 11_13, : 11 U-A 13 : S/W tran n/a END_base-pair The total base pairs = 9(from 21 bases) ------------------------------------------------ Standard WW--cis WW-tran HH--cis HH-tran SS--cis SS-tran 6 2 0 0 0 0 0 WH--cis WH-tran WS--cis WS-tran HS--cis HS-tran 0 0 0 1 0 0 ------------------------------------------------""" #run if called from command-line if __name__ == "__main__": main()