Coverage for chempropstereo/stereochemistry/tetrahedral.py: 98%

42 statements  

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

1"""Module for tagging tetrahedral stereogenic centers in molecules. 

2 

3.. module:: stereochemistry.tetrahedral 

4.. moduleauthor:: Charlles Abreu <craabreu@mit.edu> 

5""" 

6 

7import enum 

8 

9from rdkit import Chem 

10 

11from . import base, utils 

12 

13_CIP_CODES = {False: 0, "R": 1, "S": 2} 

14 

15 

16class ScanDirection(base.SpatialArrangement): 

17 """Enumeration for scan directions in canonicalized tetrahedral stereocenters. 

18 

19 Attributes 

20 ---------- 

21 NONE : int 

22 Not a stereocenter. 

23 CW : int 

24 Second, third, and fourth vertices must be scanned clockwise. 

25 CCW : int 

26 Second, third, and fourth vertices must be scanned counterclockwise. 

27 

28 Examples 

29 -------- 

30 >>> from rdkit import Chem 

31 >>> from chempropstereo import stereochemistry 

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

33 >>> stereochemistry.tag_tetrahedral_stereocenters(mol) 

34 >>> atom = mol.GetAtomWithIdx(1) 

35 >>> stereochemistry.describe_stereocenter(atom) 

36 'C1 (CW) O3 N2 C0' 

37 >>> ScanDirection.get_from(mol.GetAtomWithIdx(1)) 

38 <ScanDirection.CW: 1> 

39 >>> ScanDirection.get_from(mol.GetAtomWithIdx(2)) 

40 <ScanDirection.NONE: 0> 

41 

42 """ 

43 

44 tag = enum.nonmember("canonicalChiralTag") 

45 

46 NONE = 0 

47 CW = 1 

48 CCW = 2 

49 

50 

51class VertexRank(base.Rank): 

52 """Enumeration of vertex ranks for tetrahedral stereochemistry. 

53 

54 Attributes 

55 ---------- 

56 NONE : int 

57 Not a tetrahedral vertex. 

58 FIRST : int 

59 The first vertex in canonical order. 

60 SECOND : int 

61 The second vertex in canonical order. 

62 THIRD : int 

63 The third vertex in canonical order. 

64 FOURTH : int 

65 The fourth vertex in canonical order. 

66 

67 

68 Examples 

69 -------- 

70 >>> from rdkit import Chem 

71 >>> from chempropstereo import stereochemistry 

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

73 >>> stereochemistry.tag_tetrahedral_stereocenters(mol) 

74 >>> bond = mol.GetBondWithIdx(1) 

75 >>> VertexRank.from_bond(bond) 

76 <VertexRank.SECOND: 2> 

77 >>> VertexRank.from_bond(bond, end_is_center=True) 

78 <VertexRank.NONE: 0> 

79 

80 """ 

81 

82 tag = enum.nonmember("canonicalChiralTag") 

83 

84 NONE = 0 

85 FIRST = 1 

86 SECOND = 2 

87 THIRD = 3 

88 FOURTH = 4 

89 

90 

91def get_cip_code(atom: Chem.Atom) -> int: 

92 """Get the CIP code of an atom as an integer. 

93 

94 Parameters 

95 ---------- 

96 atom 

97 The atom to get the CIP code of. 

98 

99 Returns 

100 ------- 

101 int 

102 The CIP code of the atom: 0 for no CIP code, 1 for R, 2 for S. 

103 

104 Examples 

105 -------- 

106 >>> from chempropstereo import stereochemistry 

107 >>> from rdkit import Chem 

108 >>> mol1 = Chem.MolFromSmiles("C[C@H](N)O") 

109 >>> [stereochemistry.get_cip_code(atom) for atom in mol1.GetAtoms()] 

110 [0, 1, 0, 0] 

111 >>> mol2 = Chem.MolFromSmiles("C[C@@H](N)O") 

112 >>> [stereochemistry.get_cip_code(atom) for atom in mol2.GetAtoms()] 

113 [0, 2, 0, 0] 

114 

115 """ 

116 return _CIP_CODES[atom.HasProp("_CIPCode") and atom.GetProp("_CIPCode")] 

117 

118 

119def describe_stereocenter(atom: Chem.Atom) -> str: 

120 """Describe a tetrahedral stereocenter. 

121 

122 Parameters 

123 ---------- 

124 atom : Chem.Atom 

125 The atom to describe. 

126 

127 Returns 

128 ------- 

129 str 

130 A string description of the tetrahedral stereocenter. 

131 

132 Examples 

133 -------- 

134 >>> from rdkit import Chem 

135 >>> from chempropstereo import stereochemistry 

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

137 >>> stereochemistry.tag_tetrahedral_stereocenters(mol) 

138 >>> stereochemistry.describe_stereocenter(mol.GetAtomWithIdx(1)) 

139 'C1 (CW) O3 N2 C0' 

140 >>> stereochemistry.describe_stereocenter(mol.GetAtomWithIdx(2)) 

141 'N2 is not a stereocenter' 

142 

143 """ 

144 direction = ScanDirection.get_from(atom) 

145 if direction == ScanDirection.NONE: 

146 return f"{utils.describe_atom(atom)} is not a stereocenter" 

147 return f"{utils.describe_atom(atom)} ({direction.name}) " + " ".join( 

148 map(utils.describe_atom, VertexRank.get_neighbors(atom)) 

149 ) 

150 

151 

152def tag_tetrahedral_stereocenters(mol: Chem.Mol, force: bool = False) -> None: 

153 """Tag tetrahedral stereocenters in a molecule. 

154 

155 Tetrahedral stereocenters are tagged as clockwise or counterclockwise based 

156 on their neighbors arranged in a descending order of their canonical ranks. 

157 

158 Parameters 

159 ---------- 

160 mol 

161 The molecule whose tetrahedral stereocenters are to be tagged. 

162 force 

163 Whether to overwrite existing chiral tags (default is False). 

164 

165 Examples 

166 -------- 

167 >>> from chempropstereo import stereochemistry 

168 >>> from rdkit import Chem 

169 >>> for smi in ["C[C@H](N)O", "C[C@@H](O)N"]: 

170 ... mol = Chem.MolFromSmiles(smi) 

171 ... stereochemistry.tag_tetrahedral_stereocenters(mol) 

172 ... for atom in mol.GetAtoms(): 

173 ... direction = stereochemistry.ScanDirection.get_from(atom) 

174 ... if direction != stereochemistry.ScanDirection.NONE: 

175 ... print(stereochemistry.describe_stereocenter(atom)) 

176 C1 (CW) O3 N2 C0 

177 C1 (CW) O2 N3 C0 

178 

179 """ 

180 if mol.HasProp("hasCanonicalStereocenters") and not force: 

181 return 

182 has_stereocenters = False 

183 for atom in mol.GetAtoms(): 

184 tag = atom.GetChiralTag() 

185 if tag in ( 

186 Chem.ChiralType.CHI_TETRAHEDRAL_CW, 

187 Chem.ChiralType.CHI_TETRAHEDRAL_CCW, 

188 ): 

189 has_stereocenters = True 

190 neighbors = [neighbor.GetIdx() for neighbor in atom.GetNeighbors()] 

191 all_ranks = list( 

192 Chem.CanonicalRankAtomsInFragment( 

193 mol, neighbors, includeChirality=False 

194 ) 

195 ) 

196 neighbor_ranks = [all_ranks[idx] for idx in neighbors] 

197 # Sorting ranks in descending order keeps explicit hydrogens at the end 

198 order, flip = utils.argsort_descending_with_parity(*neighbor_ranks) 

199 if (tag == Chem.ChiralType.CHI_TETRAHEDRAL_CCW) == flip: 

200 direction = ScanDirection.CW 

201 else: 

202 direction = ScanDirection.CCW 

203 atom.SetProp(ScanDirection.tag, utils.concat(direction, *order)) 

204 elif atom.HasProp(ScanDirection.tag): 

205 atom.ClearProp(ScanDirection.tag) 

206 mol.SetBoolProp("hasCanonicalStereocenters", has_stereocenters)