2222#
2323
2424import MDAnalysis as mda
25+ import numpy as np
2526
2627import pytest
2728
@@ -55,15 +56,24 @@ def test_empty_ag_error(strand):
5556 strand2 = ResidueGroup ([strand .residues [1 ]])
5657
5758 with pytest .raises (ValueError , match = "returns an empty AtomGroup" ):
58- NucPairDist .select_strand_atoms (strand1 , strand2 , "UNK1" , "O2" )
59+ NucPairDist .select_strand_atoms (
60+ strand1 , strand2 , "UNK1" , "O2" , g_name = "GUA"
61+ )
5962
6063
6164@pytest .fixture (scope = "module" )
6265def wc_rna (strand , client_NucPairDist ):
6366 strand1 = ResidueGroup ([strand .residues [0 ], strand .residues [21 ]])
6467 strand2 = ResidueGroup ([strand .residues [1 ], strand .residues [22 ]])
6568
66- WC = WatsonCrickDist (strand1 , strand2 )
69+ WC = WatsonCrickDist (
70+ strand1 ,
71+ strand2 ,
72+ g_name = "GUA" ,
73+ a_name = "ADE" ,
74+ c_name = "CYT" ,
75+ u_name = "URA" ,
76+ )
6777 WC .run (** client_NucPairDist )
6878 return WC
6979
@@ -83,10 +93,18 @@ def test_wc_dist(wc_rna):
8393
8494def test_wc_dist_invalid_residue_types (u ):
8595 strand = u .select_atoms ("resid 1-10" )
96+ # residues[0]=GUA, residues[21]=POT (non-nucleic), residues[22]=POT
8697 strand1 = ResidueGroup ([strand .residues [0 ], strand .residues [21 ]])
8798 strand2 = ResidueGroup ([strand .residues [2 ], strand .residues [22 ]])
8899 with pytest .raises (ValueError , match = "is not a valid nucleic acid" ):
89- WatsonCrickDist (strand1 , strand2 )
100+ WatsonCrickDist (
101+ strand1 ,
102+ strand2 ,
103+ g_name = "GUA" ,
104+ a_name = "ADE" ,
105+ c_name = "CYT" ,
106+ u_name = "URA" ,
107+ )
90108
91109
92110def test_selection_length_mismatch (strand ):
@@ -101,7 +119,14 @@ def test_wc_dist_deprecation_warning(strand):
101119 strand2 = [strand .residues [1 ], strand .residues [22 ]]
102120
103121 with pytest .deprecated_call ():
104- WatsonCrickDist (strand1 , strand2 )
122+ WatsonCrickDist (
123+ strand1 ,
124+ strand2 ,
125+ g_name = "GUA" ,
126+ a_name = "ADE" ,
127+ c_name = "CYT" ,
128+ u_name = "URA" ,
129+ )
105130
106131
107132def test_wc_dist_strand_verification (strand ):
@@ -122,7 +147,14 @@ def test_minor_dist(strand, client_NucPairDist):
122147 strand1 = ResidueGroup ([strand .residues [2 ], strand .residues [19 ]])
123148 strand2 = ResidueGroup ([strand .residues [16 ], strand .residues [4 ]])
124149
125- MI = MinorPairDist (strand1 , strand2 )
150+ MI = MinorPairDist (
151+ strand1 ,
152+ strand2 ,
153+ g_name = "GUA" ,
154+ a_name = "ADE" ,
155+ c_name = "CYT" ,
156+ u_name = "URA" ,
157+ )
126158 MI .run (** client_NucPairDist )
127159
128160 assert MI .results .distances [0 , 0 ] == approx (15.06506 , rel = 1e-3 )
@@ -133,8 +165,85 @@ def test_major_dist(strand, client_NucPairDist):
133165 strand1 = ResidueGroup ([strand .residues [1 ], strand .residues [4 ]])
134166 strand2 = ResidueGroup ([strand .residues [11 ], strand .residues [8 ]])
135167
136- MA = MajorPairDist (strand1 , strand2 )
168+ MA = MajorPairDist (
169+ strand1 ,
170+ strand2 ,
171+ g_name = "GUA" ,
172+ a_name = "ADE" ,
173+ c_name = "CYT" ,
174+ u_name = "URA" ,
175+ )
137176 MA .run (** client_NucPairDist )
138177
139178 assert MA .results .distances [0 , 0 ] == approx (26.884272 , rel = 1e-3 )
140179 assert MA .results .distances [0 , 1 ] == approx (13.578535 , rel = 1e-3 )
180+
181+
182+ @pytest .fixture (scope = "module" )
183+ def dna_u ():
184+ """Synthetic universe with 2-letter DNA resnames (DG, DC, DA, DT).
185+
186+ Used to test that WatsonCrickDist matches full resnames rather than only
187+ the first character.
188+
189+ Strand layout (one frame, all atoms at origin):
190+ residue 0 – DG (purine): atom N1
191+ residue 1 – DC (pyrimidine): atom N3
192+ residue 2 – DA (purine): atom N1
193+ residue 3 – DT (pyrimidine): atom N3
194+ """
195+ n_atoms = 4
196+ n_residues = 4
197+ u = mda .Universe .empty (
198+ n_atoms ,
199+ n_residues = n_residues ,
200+ atom_resindex = [0 , 1 , 2 , 3 ],
201+ trajectory = True ,
202+ )
203+ u .add_TopologyAttr ("name" , ["N1" , "N3" , "N1" , "N3" ])
204+ u .add_TopologyAttr ("resname" , ["DG" , "DC" , "DA" , "DT" ])
205+ u .add_TopologyAttr ("resid" , [1 , 2 , 3 , 4 ])
206+ coords = np .zeros ((1 , n_atoms , 3 ), dtype = np .float32 )
207+ u .load_new (coords )
208+ return u
209+
210+
211+ def test_wc_dist_multichar_resnames (dna_u ):
212+ """WatsonCrickDist must match the full resname, not just resname[0]\.
213+
214+ With 2-letter names like DG/DA, the first character 'D' does not appear in
215+ the purine/pyrimidine lists, so the buggy resname[0] check previously
216+ rasied ValueError for every residue.
217+ """
218+ strand1 = ResidueGroup ([dna_u .residues [0 ], dna_u .residues [2 ]]) # DG, DA
219+ strand2 = ResidueGroup ([dna_u .residues [1 ], dna_u .residues [3 ]]) # DC, DT
220+
221+ WC = WatsonCrickDist (
222+ strand1 ,
223+ strand2 ,
224+ g_name = "DG" ,
225+ a_name = "DA" ,
226+ c_name = "DC" ,
227+ t_name = "DT" ,
228+ )
229+ WC .run ()
230+ assert WC .results .distances .shape == (1 , 2 )
231+
232+
233+ def test_select_strand_atoms_multichar_resnames (dna_u ):
234+ """select_strand_atoms must recognise multi-character resnames."""
235+ strand1 = ResidueGroup ([dna_u .residues [0 ], dna_u .residues [2 ]]) # DG, DA
236+ strand2 = ResidueGroup ([dna_u .residues [1 ], dna_u .residues [3 ]]) # DC, DT
237+
238+ sel1 , sel2 = NucPairDist .select_strand_atoms (
239+ strand1 ,
240+ strand2 ,
241+ a1_name = "N1" ,
242+ a2_name = "N3" ,
243+ g_name = "DG" ,
244+ a_name = "DA" ,
245+ c_name = "DC" ,
246+ t_name = "DT" ,
247+ )
248+ assert len (sel1 ) == 2
249+ assert len (sel2 ) == 2
0 commit comments