Coverage for chempropstereo/tests/test_rdkit.py: 100%

36 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-03-22 21:04 +0000

1"""Unit tests for chiral center detection and canonical ranking using RDKit.""" 

2 

3import numpy as np 

4from rdkit import Chem 

5 

6 

7def test_chiral_center_detection(): 

8 """Test detection of chiral centers in molecules using RDKit. 

9 

10 This test verifies that the FindPotentialStereo function from RDKit 

11 accurately identifies chiral centers in given SMILES strings. It checks 

12 both legacy and non-legacy stereo perception modes. The chiral centers 

13 detected are compared against expected descriptors (clockwise 'CW' or 

14 counterclockwise 'CCW'). The test ensures that chiral centers are correctly 

15 specified and that their controlling atoms match the expected neighbors. 

16 It also asserts that non-chiral atoms are marked as unspecified. 

17 """ 

18 

19 def check_molecule(smiles, descriptions, use_legacy): 

20 Chem.SetUseLegacyStereoPerception(use_legacy) 

21 mol = Chem.MolFromSmiles(smiles) 

22 info_list = Chem.FindPotentialStereo(mol, cleanIt=False, flagPossible=False) 

23 assert len(info_list) == len(descriptions) 

24 for center, desc in zip(info_list, descriptions): 

25 assert center.type == Chem.StereoType.Atom_Tetrahedral 

26 assert center.specified == Chem.StereoSpecified.Specified 

27 atom = mol.GetAtomWithIdx(center.centeredOn) 

28 neighbors = [n.GetIdx() for n in atom.GetNeighbors()] 

29 assert atom.GetChiralTag() == getattr( 

30 Chem.ChiralType, f"CHI_TETRAHEDRAL_{desc}" 

31 ) 

32 assert center.descriptor == getattr(Chem.StereoDescriptor, f"Tet_{desc}") 

33 assert all(i == j for i, j in zip(center.controllingAtoms, neighbors)) 

34 for atom in mol.GetAtoms(): 

35 if atom.GetIdx() not in [center.centeredOn for center in info_list]: 

36 assert atom.GetChiralTag() == Chem.ChiralType.CHI_UNSPECIFIED 

37 

38 original_use_legacy = Chem.GetUseLegacyStereoPerception() 

39 for use_legacy in [True, False]: 

40 check_molecule("C[C@@H](O)N", ["CW"], use_legacy) 

41 check_molecule("C[C@H](O)N", ["CCW"], use_legacy) 

42 check_molecule("C[C@H](N)O", ["CCW"], use_legacy) 

43 check_molecule("C[C@@H](O)[C@@H](C)N", ["CW", "CW"], use_legacy) 

44 check_molecule("C[C@@H](O)[C@H](C)N", ["CW", "CCW"], use_legacy) 

45 Chem.SetUseLegacyStereoPerception(original_use_legacy) 

46 

47 

48def test_canonical_ranking(): 

49 """Test canonical ranking of atoms in a molecule. 

50 

51 Checks that the canonical ranking from RDKit produces the expected 

52 order of atoms in a molecule. The test molecule is a chiral molecule 

53 with a stereocenter and a tetrahedral stereobond. The test also checks 

54 that the hydrogen atoms are correctly sorted last in the canonical 

55 ranking. 

56 """ 

57 mol = Chem.MolFromSmiles("C[C@@H]([C@@H](C)N)O") 

58 order = np.argsort(Chem.CanonicalRankAtoms(mol)).tolist() 

59 assert order == [0, 3, 4, 5, 1, 2] 

60 mol = Chem.AddHs(mol) 

61 order = np.argsort(Chem.CanonicalRankAtoms(mol)).tolist() 

62 assert order == [16, 14, 15, 6, 7, 8, 11, 12, 13, 9, 10, 5, 4, 0, 3, 1, 2] 

63 ranked_elements = "".join([mol.GetAtomWithIdx(i).GetSymbol() for i in order]) 

64 assert ranked_elements == "HHHHHHHHHHHONCCCC" # Hydrogens first