Skip to content

Commit 7ea20bc

Browse files
committed
Test that the charge filtration heuristics ignore the atom order
Covers the nitroaromatic radicals whose aromatic structures were dropped under save_order and the NO2 / CH2NO / NH2CHO examples this module's docstring documents, asserting the same structure count and the same number of aromatic structures for either save_order setting. Covers the salts whose charged atoms sit in different molecular fragments, which raised TypeError from the charge proximity heuristic or were filtered away entirely. Adds a unit test for get_atom_indices.
1 parent 0a21054 commit 7ea20bc

1 file changed

Lines changed: 51 additions & 3 deletions

File tree

arc/molecule/filtration_test.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import unittest
55

66
from arc.molecule.filtration import (get_octet_deviation_list, get_octet_deviation, filter_structures,
7-
charge_filtration, get_charge_span_list, aromaticity_filtration)
7+
charge_filtration, get_atom_indices, get_charge_span_list,
8+
aromaticity_filtration)
89
from arc.molecule.molecule import Molecule
910
from arc.molecule.resonance import generate_resonance_structures, analyze_molecule
1011

@@ -126,7 +127,7 @@ def test_radical_site(self):
126127
Molecule().from_adjacency_list(adj3)]
127128

128129
for mol in mol_list:
129-
mol.update() # the charge_filtration uses the atom.sorting_label attribute
130+
mol.update()
130131

131132
filtered_list = charge_filtration(mol_list, get_charge_span_list(mol_list))
132133
self.assertEqual(len(filtered_list), 2)
@@ -213,7 +214,7 @@ def test_electronegativity(self):
213214
Molecule().from_adjacency_list(adj7)]
214215

215216
for mol in mol_list:
216-
mol.update() # the charge_filtration uses the atom.sorting_label attribute
217+
mol.update()
217218

218219
filtered_list = charge_filtration(mol_list, get_charge_span_list(mol_list))
219220
self.assertEqual(len(filtered_list), 4)
@@ -301,6 +302,53 @@ def test_aromaticity(self):
301302
filtered_list = aromaticity_filtration(mol_list, analyze_molecule(mol_list[0]))
302303
self.assertEqual(len(filtered_list), 3)
303304

305+
def test_get_atom_indices(self):
306+
"""Test that atoms are mapped to their position in mol.vertices"""
307+
mol = Molecule().from_smiles('[O]N=O')
308+
indices = get_atom_indices(mol)
309+
self.assertEqual(len(indices), len(mol.vertices))
310+
for index, atom in enumerate(mol.vertices):
311+
self.assertEqual(indices[id(atom)], index)
312+
313+
def test_charge_filtration_independent_of_atom_order(self):
314+
"""Test that the charge filtration heuristics return the same structures for either atom order"""
315+
for smiles, expected, aromatic in (('[O]c1ccc([N+](=O)[O-])cc1', 11, 2),
316+
('[CH2]c1ccc([N+](=O)[O-])cc1', 11, 2),
317+
('[O]c1ccccc1[N+](=O)[O-]', 11, 2),
318+
('[O]c1cccc([N+](=O)[O-])c1', 9, 2),
319+
('[O]N=O', 4, 0),
320+
('C=N[O]', 3, 0),
321+
('NC=O', 2, 0),
322+
):
323+
sorted_list = generate_resonance_structures(Molecule().from_smiles(smiles),
324+
keep_isomorphic=True, save_order=False)
325+
saved_list = generate_resonance_structures(Molecule().from_smiles(smiles),
326+
keep_isomorphic=True, save_order=True)
327+
self.assertEqual(len(sorted_list), expected, msg=smiles)
328+
self.assertEqual(len(saved_list), expected, msg=smiles)
329+
for mol_list in (sorted_list, saved_list):
330+
self.assertEqual(sum(1 for mol in mol_list
331+
if any(bond.is_benzene() for bond in mol.get_all_edges())),
332+
aromatic, msg=smiles)
333+
334+
def test_charge_filtration_of_disconnected_ions(self):
335+
"""Test that a species whose charges sit in disconnected components is filtered
336+
337+
Charged atoms in different molecular fragments have no path between them, so the charge
338+
proximity heuristic does not count them, and a species whose only opposite-charge pairs
339+
are of that kind is not filtered out entirely.
340+
"""
341+
for save_order in (False, True):
342+
for smiles, expected in (('[Li+].[OH-]', 1),
343+
('[NH4+].[O-]N=O', 1),
344+
('O=C([O-])[O-].[NH4+].[NH4+]', 1),
345+
('[Li+].[Li+].[O-][O-]', 1),
346+
):
347+
mol_list = generate_resonance_structures(Molecule().from_smiles(smiles),
348+
save_order=save_order)
349+
self.assertEqual(len(mol_list), expected, msg=smiles)
350+
self.assertTrue(mol_list[0].get_charge_span())
351+
304352

305353
if __name__ == '__main__':
306354
unittest.main(testRunner=unittest.TextTestRunner(verbosity=2))

0 commit comments

Comments
 (0)