chython 2.19__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. chython/__init__.py +35 -0
  2. chython/_functions.py +95 -0
  3. chython/_java.py +57 -0
  4. chython/algorithms/__init__.py +18 -0
  5. chython/algorithms/_isomorphism.cp313-win_amd64.pyd +0 -0
  6. chython/algorithms/_isomorphism.pyx +189 -0
  7. chython/algorithms/_rings.cp313-win_amd64.pyd +0 -0
  8. chython/algorithms/_rings.pyx +478 -0
  9. chython/algorithms/aromatics/__init__.py +27 -0
  10. chython/algorithms/aromatics/_rules.py +124 -0
  11. chython/algorithms/aromatics/kekule.py +532 -0
  12. chython/algorithms/aromatics/test/__init__.py +18 -0
  13. chython/algorithms/aromatics/test/test_kekule.py +160 -0
  14. chython/algorithms/aromatics/test/test_thiele.py +263 -0
  15. chython/algorithms/aromatics/thiele.py +222 -0
  16. chython/algorithms/calculate2d/__init__.py +23 -0
  17. chython/algorithms/calculate2d/clean2d.js +1 -0
  18. chython/algorithms/calculate2d/molecule.py +191 -0
  19. chython/algorithms/calculate2d/reaction.py +75 -0
  20. chython/algorithms/conformers.py +106 -0
  21. chython/algorithms/depict.py +596 -0
  22. chython/algorithms/fingerprints/__init__.py +43 -0
  23. chython/algorithms/fingerprints/linear.py +202 -0
  24. chython/algorithms/fingerprints/morgan.py +132 -0
  25. chython/algorithms/fingerprints/test/__init__.py +18 -0
  26. chython/algorithms/fingerprints/test/test_linear.py +127 -0
  27. chython/algorithms/fingerprints/test/test_morgan.py +79 -0
  28. chython/algorithms/groups/__init__.py +212 -0
  29. chython/algorithms/groups/_functional.py +290 -0
  30. chython/algorithms/groups/_oxidations.py +70 -0
  31. chython/algorithms/groups/_protective.py +964 -0
  32. chython/algorithms/groups/_reactions.py +809 -0
  33. chython/algorithms/groups/_reductions.py +88 -0
  34. chython/algorithms/groups/_transformations.py +278 -0
  35. chython/algorithms/groups/test/__init__.py +18 -0
  36. chython/algorithms/groups/test/test_deprotection.py +108 -0
  37. chython/algorithms/groups/test/test_reactions.py +377 -0
  38. chython/algorithms/isomorphism.py +664 -0
  39. chython/algorithms/mapping/__init__.py +188 -0
  40. chython/algorithms/mapping/_groups.py +94 -0
  41. chython/algorithms/mapping/_reactions.py +72 -0
  42. chython/algorithms/mapping/attention.py +266 -0
  43. chython/algorithms/mapping/reconstruct.py +346 -0
  44. chython/algorithms/mcs.py +201 -0
  45. chython/algorithms/morgan.py +77 -0
  46. chython/algorithms/rings.py +178 -0
  47. chython/algorithms/smiles.py +597 -0
  48. chython/algorithms/standardize/__init__.py +30 -0
  49. chython/algorithms/standardize/_groups.py +958 -0
  50. chython/algorithms/standardize/_isomers.py +330 -0
  51. chython/algorithms/standardize/_metal_organics.py +189 -0
  52. chython/algorithms/standardize/_reagents.py +44 -0
  53. chython/algorithms/standardize/_salts.py +68 -0
  54. chython/algorithms/standardize/molecule.py +475 -0
  55. chython/algorithms/standardize/reaction.py +384 -0
  56. chython/algorithms/standardize/resonance.py +191 -0
  57. chython/algorithms/standardize/salts.py +128 -0
  58. chython/algorithms/standardize/saturation.py +373 -0
  59. chython/algorithms/standardize/test/__init__.py +18 -0
  60. chython/algorithms/standardize/test/test_canonicalize.py +66 -0
  61. chython/algorithms/standardize/test/test_groups.py +146 -0
  62. chython/algorithms/standardize/test/test_isomers.py +198 -0
  63. chython/algorithms/stereo.py +1215 -0
  64. chython/algorithms/tautomers/__init__.py +167 -0
  65. chython/algorithms/tautomers/_acid.py +59 -0
  66. chython/algorithms/tautomers/_base.py +116 -0
  67. chython/algorithms/tautomers/_keto_enol.py +107 -0
  68. chython/algorithms/tautomers/acid_base.py +208 -0
  69. chython/algorithms/tautomers/heteroarenes.py +103 -0
  70. chython/algorithms/tautomers/keto_enol.py +143 -0
  71. chython/algorithms/tautomers/test/__init__.py +18 -0
  72. chython/algorithms/tautomers/test/test_tautomers.py +80 -0
  73. chython/algorithms/test/__init__.py +18 -0
  74. chython/algorithms/test/test_isomorphism.py +58 -0
  75. chython/algorithms/test/test_smiles.py +76 -0
  76. chython/algorithms/x3dom.py +350 -0
  77. chython/containers/__init__.py +48 -0
  78. chython/containers/_pack_v2.cp313-win_amd64.pyd +0 -0
  79. chython/containers/_pack_v2.pyx +271 -0
  80. chython/containers/_unpack_v0v2.cp313-win_amd64.pyd +0 -0
  81. chython/containers/_unpack_v0v2.pyx +302 -0
  82. chython/containers/bonds.py +255 -0
  83. chython/containers/cgr.py +115 -0
  84. chython/containers/chimera.py +63 -0
  85. chython/containers/graph.py +199 -0
  86. chython/containers/molecule.py +817 -0
  87. chython/containers/query.py +71 -0
  88. chython/containers/rdkit.py +183 -0
  89. chython/containers/reaction.py +324 -0
  90. chython/containers/test/__init__.py +18 -0
  91. chython/containers/test/test_molecule.py +117 -0
  92. chython/containers/test/test_pack.py +60 -0
  93. chython/containers/test/test_rdkit.py +70 -0
  94. chython/containers/test/test_reaction.py +40 -0
  95. chython/exceptions.py +114 -0
  96. chython/files/MRVrw.py +532 -0
  97. chython/files/PDBrw.py +255 -0
  98. chython/files/RDFrw.py +309 -0
  99. chython/files/SDFrw.py +221 -0
  100. chython/files/__init__.py +30 -0
  101. chython/files/_convert.py +207 -0
  102. chython/files/_mapping.py +117 -0
  103. chython/files/_xyz.cp313-win_amd64.pyd +0 -0
  104. chython/files/_xyz.pyx +74 -0
  105. chython/files/daylight/__init__.py +24 -0
  106. chython/files/daylight/parser.py +157 -0
  107. chython/files/daylight/smarts.py +107 -0
  108. chython/files/daylight/smiles.py +260 -0
  109. chython/files/daylight/test/__init__.py +18 -0
  110. chython/files/daylight/test/test_daylight_smarts.py +289 -0
  111. chython/files/daylight/test/test_daylight_smiles.py +101 -0
  112. chython/files/daylight/test/test_parser.py +118 -0
  113. chython/files/daylight/test/test_tokenize.py +77 -0
  114. chython/files/daylight/tokenize.py +376 -0
  115. chython/files/libinchi/__init__.py +22 -0
  116. chython/files/libinchi/libinchi.dll +0 -0
  117. chython/files/libinchi/wrapper.py +554 -0
  118. chython/files/mdl/__init__.py +25 -0
  119. chython/files/mdl/emol.py +251 -0
  120. chython/files/mdl/erxn.py +67 -0
  121. chython/files/mdl/mol.py +186 -0
  122. chython/files/mdl/read.py +223 -0
  123. chython/files/mdl/rxn.py +67 -0
  124. chython/files/mdl/stereo.py +63 -0
  125. chython/files/mdl/write.py +201 -0
  126. chython/files/opsin.py +35 -0
  127. chython/files/test/__init__.py +0 -0
  128. chython/files/test/test_inchi.py +27 -0
  129. chython/files/test/test_mrv.py +47 -0
  130. chython/files/test/test_sdf_rdf.py +64 -0
  131. chython/files/test/test_xyz.py +20 -0
  132. chython/files/xyz.py +78 -0
  133. chython/periodictable/__init__.py +69 -0
  134. chython/periodictable/base/__init__.py +25 -0
  135. chython/periodictable/base/dynamic.py +149 -0
  136. chython/periodictable/base/element.py +451 -0
  137. chython/periodictable/base/groups.py +90 -0
  138. chython/periodictable/base/periods.py +46 -0
  139. chython/periodictable/base/query.py +473 -0
  140. chython/periodictable/base/vector.py +119 -0
  141. chython/periodictable/groupI.py +252 -0
  142. chython/periodictable/groupII.py +223 -0
  143. chython/periodictable/groupIII.py +1154 -0
  144. chython/periodictable/groupIV.py +210 -0
  145. chython/periodictable/groupIX.py +196 -0
  146. chython/periodictable/groupV.py +192 -0
  147. chython/periodictable/groupVI.py +183 -0
  148. chython/periodictable/groupVII.py +162 -0
  149. chython/periodictable/groupVIII.py +161 -0
  150. chython/periodictable/groupX.py +166 -0
  151. chython/periodictable/groupXI.py +164 -0
  152. chython/periodictable/groupXII.py +158 -0
  153. chython/periodictable/groupXIII.py +239 -0
  154. chython/periodictable/groupXIV.py +262 -0
  155. chython/periodictable/groupXV.py +276 -0
  156. chython/periodictable/groupXVI.py +457 -0
  157. chython/periodictable/groupXVII.py +324 -0
  158. chython/periodictable/groupXVIII.py +260 -0
  159. chython/periodictable/test/__init__.py +0 -0
  160. chython/periodictable/test/test_element.py +100 -0
  161. chython/reactor/__init__.py +24 -0
  162. chython/reactor/base.py +222 -0
  163. chython/reactor/reactor.py +177 -0
  164. chython/reactor/test/__init__.py +18 -0
  165. chython/reactor/test/test_reactor.py +40 -0
  166. chython/reactor/test/test_transformer.py +78 -0
  167. chython/reactor/transformer.py +72 -0
  168. chython/utils/__init__.py +50 -0
  169. chython/utils/free_wilson.py +114 -0
  170. chython/utils/grid.py +130 -0
  171. chython/utils/retro.py +139 -0
  172. chython-2.19.dist-info/METADATA +105 -0
  173. chython-2.19.dist-info/RECORD +175 -0
  174. chython-2.19.dist-info/WHEEL +4 -0
  175. chython-2.19.dist-info/licenses/LICENSE +165 -0
@@ -0,0 +1,212 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from collections.abc import Iterator
20
+ from functools import cached_property
21
+ from itertools import permutations
22
+ from ._functional import rules as functional_rules
23
+ from ._oxidations import rules as oxidation_rules
24
+ from ._protective import rules as protective_rules
25
+ from ._reactions import rules as reaction_rules
26
+ from ._reductions import rules as reduction_rules
27
+ from ._transformations import rules as transformation_rules
28
+
29
+
30
+ class FunctionalGroups:
31
+ __slots__ = ()
32
+
33
+ @cached_property
34
+ def functional_groups(self) -> dict[str, int]:
35
+ """
36
+ Dict of functional group names to their count in the molecule.
37
+ """
38
+ found = {}
39
+ for name, q in functional_rules.items():
40
+ c = sum(1 for _ in q.get_mapping(self))
41
+ if c:
42
+ found[name] = c
43
+ return found
44
+
45
+ @cached_property
46
+ def protective_groups(self) -> dict[str, int]:
47
+ """
48
+ Dict of protective group names to their count in the molecule.
49
+ """
50
+ found = {}
51
+ seen = set()
52
+ for name, (q, keep, *_) in protective_rules.items():
53
+ c = 0
54
+ for mp in q.get_mapping(self, automorphism_filter=False):
55
+ atoms = {m for n, m in mp.items() if n not in keep}
56
+ if seen.isdisjoint(atoms):
57
+ seen.update(atoms)
58
+ c += 1
59
+ if c:
60
+ found[name] = c
61
+ return found
62
+
63
+ def remove_protection(self, name=None, canonicalize=True,
64
+ fix_tautomers=True, ignore_pyrrole_hydrogen=False) -> bool:
65
+ """
66
+ Remove protective groups from the given molecule if applicable.
67
+
68
+ :param name: Specific protective group name to remove (None = all).
69
+ :param canonicalize: Run full canonicalization after removal.
70
+ :param fix_tautomers: Canonicalize tautomer forms. Passed to canonicalize().
71
+ :param ignore_pyrrole_hydrogen: Fix invalid rings like Cn1cc[nH]c1. Passed to canonicalize().
72
+ """
73
+ to_delete = set()
74
+ to_add = []
75
+ if name is None:
76
+ rules = protective_rules.values()
77
+ elif name in protective_rules:
78
+ rules = [protective_rules[name]]
79
+ else:
80
+ raise ValueError(f'Unknown protective group: {name}')
81
+
82
+ kept_atoms = set()
83
+ for q, keep, add, *_ in rules:
84
+ for mp in q.get_mapping(self, automorphism_filter=False):
85
+ delete = {m for n, m in mp.items() if n not in keep}
86
+ if not to_delete.isdisjoint(delete):
87
+ continue
88
+ to_delete.update(delete)
89
+ for n in keep:
90
+ kept_atoms.add(mp[n])
91
+ for n, a, b in add:
92
+ to_add.append((mp[n], a, b))
93
+
94
+ for n, a, b in to_add:
95
+ m = self.add_atom(a, _skip_calculation=True)
96
+ self.add_bond(m, n, b, _skip_calculation=True)
97
+ for n in to_delete:
98
+ self.delete_atom(n, _skip_calculation=True)
99
+ if to_delete or to_add:
100
+ self.fix_structure()
101
+ if canonicalize:
102
+ self.canonicalize(fix_tautomers=fix_tautomers,
103
+ ignore_pyrrole_hydrogen=ignore_pyrrole_hydrogen)
104
+ else:
105
+ # fix implicit H on aromatic N freed from PG
106
+ for n in kept_atoms:
107
+ a = self.atom(n)
108
+ if a.atomic_symbol == 'N' and a.hybridization == 4 and a.implicit_hydrogens is None:
109
+ a._implicit_hydrogens = 1
110
+ self.fix_stereo()
111
+ return True
112
+ return False
113
+
114
+ def react(self, *others, reaction=None) -> Iterator[tuple[str, 'ReactionContainer']]:
115
+ """
116
+ Enumerate possible reaction products between molecules.
117
+
118
+ mol1.react(mol2) -> [(reaction_name, ReactionContainer), ...]
119
+ mol1.react(mol2, mol3) -> [(reaction_name, ReactionContainer), ...] # multi-component
120
+ mol1.react(mol2, reaction='suzuki') -> only suzuki coupling
121
+
122
+ :param reaction: optional reaction name to apply selectively.
123
+ """
124
+ mols = [self, *others]
125
+
126
+ for name, fg_names, reactor in reaction_rules:
127
+ if reaction is not None and name != reaction:
128
+ continue
129
+ if len(fg_names) != len(mols):
130
+ continue
131
+ for perm in permutations(mols):
132
+ if all(fg in mol.functional_groups for mol, fg in zip(perm, fg_names)):
133
+ for rxn in reactor(*perm):
134
+ yield name, rxn
135
+ break
136
+
137
+ def oxidize(self, reaction=None) -> Iterator[tuple[str, 'ReactionContainer']]:
138
+ """
139
+ Enumerate possible single-step oxidation products.
140
+
141
+ mol.oxidize() -> [(reaction_name, ReactionContainer), ...]
142
+ mol.oxidize(reaction='alcohol_to_aldehyde') -> only this oxidation
143
+
144
+ :param reaction: optional reaction name to apply selectively.
145
+ """
146
+ fgs = self.functional_groups
147
+ for name, fg_name, _, reactor in oxidation_rules:
148
+ if reaction is not None and name != reaction:
149
+ continue
150
+ if fg_name in fgs:
151
+ for rxn in reactor(self):
152
+ yield name, rxn
153
+
154
+ def reduce(self, reaction=None) -> Iterator[tuple[str, 'ReactionContainer']]:
155
+ """
156
+ Enumerate possible single-step reduction products.
157
+
158
+ mol.reduce() -> [(reaction_name, ReactionContainer), ...]
159
+ mol.reduce(reaction='ketone_to_alcohol') -> only this reduction
160
+
161
+ :param reaction: optional reaction name to apply selectively.
162
+ """
163
+ fgs = self.functional_groups
164
+ for name, fg_name, _, reactor in reduction_rules:
165
+ if reaction is not None and name != reaction:
166
+ continue
167
+ if fg_name in fgs:
168
+ for rxn in reactor(self):
169
+ yield name, rxn
170
+
171
+ def transform(self, reaction=None) -> Iterator[tuple[str, 'ReactionContainer']]:
172
+ """
173
+ Enumerate possible single-molecule functional group interconversions
174
+ (ring formations from open-chain precursors with implicit reagents).
175
+
176
+ mol.transform() -> [(reaction_name, ReactionContainer), ...]
177
+ mol.transform(reaction='appel') -> only Appel reaction
178
+
179
+ :param reaction: optional reaction name to apply selectively.
180
+ """
181
+ fgs = self.functional_groups
182
+ for name, fg_name, _, reactor in transformation_rules:
183
+ if reaction is not None and name != reaction:
184
+ continue
185
+ if fg_name in fgs:
186
+ for rxn in reactor(self):
187
+ yield name, rxn
188
+
189
+ def __invert__(self):
190
+ """
191
+ Enumerate all possible single-step molecular transformations
192
+ (oxidations, reductions, and functional group interconversions).
193
+
194
+ ~mol -> [(reaction_name, ReactionContainer), ...]
195
+ """
196
+ yield from self.oxidize()
197
+ yield from self.reduce()
198
+ yield from self.transform()
199
+
200
+ def __matmul__(self, other):
201
+ """
202
+ Enumerate possible reaction products between molecules.
203
+
204
+ mol1 @ mol2 -> [(reaction_name, ReactionContainer), ...]
205
+ mol1 @ [mol2, mol3] -> [(reaction_name, ReactionContainer), ...] # multi-component
206
+ """
207
+ if isinstance(other, (list, tuple)):
208
+ return self.react(*other)
209
+ return self.react(other)
210
+
211
+
212
+ __all__ = ['FunctionalGroups']
@@ -0,0 +1,290 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2024-2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from lazy_object_proxy import Proxy
20
+
21
+
22
+ def _rules():
23
+ from ... import smarts
24
+
25
+ rules = {}
26
+
27
+ # carbohydrides
28
+ rules['terminal_alkene'] = smarts('[C;z2;x0;D1:1]=[C;z2;x0;D2,D3:2]')
29
+ rules['alkene'] = smarts('[C;z2;x0;D2,D3:1]=[C;z2;x0;D2,D3:2]')
30
+ rules['terminal_alkyne'] = smarts('[C;z3;x0;D1:1]#[C;x0;D2:2]')
31
+ rules['alkyne'] = smarts('[C;z3;x0;D2:1]#[C;x0;D2:2]')
32
+
33
+ # enamines/enol ethers: C=C bonded to N or O (for hydrogenation)
34
+ rules['enamine'] = smarts('[C;z2;D2,D3:1]=[C;z2;D2,D3:2]-[N;z1;D2,D3:3]')
35
+ rules['enol_ether'] = smarts('[C;z2;D2,D3:1]=[C;z2;D2,D3:2]-[O;D2:3]')
36
+
37
+ rules['vicinal_diol'] = smarts('[O;D1;z1;x0:1]-[C;z1;x1]-[C;z1;x1]-[O;D1;z1;x0:2]')
38
+
39
+ rules['benzylic_ch'] = smarts('[C;z1;h1,h2;D2,D3:1]-[C;a:2]')
40
+
41
+ # halides
42
+ rules['aryl_fluoride'] = smarts('[F;D1:100]-[C;a:1]')
43
+ rules['aryl_chloride'] = smarts('[Cl;D1:100]-[C;a:1]')
44
+ rules['aryl_bromide'] = smarts('[Br;D1:100]-[C;a:1]')
45
+ rules['aryl_iodide'] = smarts('[I;D1:100]-[C;a:1]')
46
+
47
+ rules['alkyl_fluoride'] = smarts('[F;D1:100][C;z1;x1:1]')
48
+ rules['alkyl_chloride'] = smarts('[Cl;D1:100][C;z1;x1:1]')
49
+ rules['alkyl_bromide'] = smarts('[Br;D1:100][C;z1;x1:1]')
50
+ rules['alkyl_iodide'] = smarts('[I;D1:100][C;z1;x1:1]')
51
+
52
+ rules['alkenyl_fluoride'] = smarts('[F;D1:100][C;z2;x1:1]=[C:2]')
53
+ rules['alkenyl_chloride'] = smarts('[Cl;D1:100][C;z2;x1:1]=[C:2]')
54
+ rules['alkenyl_bromide'] = smarts('[Br;D1:100][C;z2;x1:1]=[C:2]')
55
+ rules['alkenyl_iodide'] = smarts('[I;D1:100][C;z2;x1:1]=[C:2]')
56
+
57
+ rules['alkynyl_fluoride'] = smarts('[F;D1:100][C;z3;x1:1]')
58
+ rules['alkynyl_chloride'] = smarts('[Cl;D1:100][C;z3;x1:1]')
59
+ rules['alkynyl_bromide'] = smarts('[Br;D1:100][C;z3;x1:1]')
60
+ rules['alkynyl_iodide'] = smarts('[I;D1:100][C;z3;x1:1]')
61
+
62
+ # pseudohalides
63
+ rules['aryl_triflate'] = smarts('[S;D4](=O)(=O)(-[O:100]-;!@[C;a:1])-[C;D4](F)(F)F')
64
+ rules['aryl_mesylate'] = smarts('[S;D4](=O)(=O)(-[O:100]-;!@[C;a:1])-[C;D1]')
65
+ rules['aryl_tosylate'] = smarts('[S;D4](=O)(=O)(-[O:100]-;!@[C;a:1])-[C;a]:1:[C;D2]:[C;D2]:[C](-[C;D1]):[C;D2]:[C;D2]:1')
66
+
67
+ rules['alkyl_triflate'] = smarts('[S;D4](=O)(=O)(-[O:100]-;!@[C;z1;x1:1])-[C;D4](F)(F)F')
68
+ rules['alkyl_mesylate'] = smarts('[S;D4](=O)(=O)(-[O:100]-;!@[C;z1;x1:1])-[C;D1]')
69
+ rules['alkyl_tosylate'] = smarts('[S;D4](=O)(=O)(-[O:100]-;!@[C;z1;x1:1])-[C;a]:1:[C;D2]:[C;D2]:[C](-[C;D1]):[C;D2]:[C;D2]:1')
70
+
71
+ # boronic acids and esters
72
+ rules['aryl_boronic_acid'] = smarts('[B;D3;z1;x2:100](-[O;D1])(-[O;D1])-;!@[C;a:1]')
73
+ rules['aryl_boronic_ester'] = smarts('[B;D3;z1;x2:100](-[O;D2;x1])(-[O;D2;x1])-;!@[C;a:1]')
74
+
75
+ rules['alkyl_boronic_acid'] = smarts('[B;D3;z1;x2:100](-[O;D1])(-[O;D1])-;!@[C;z1;x1:1]')
76
+ rules['alkyl_boronic_ester'] = smarts('[B;D3;z1;x2:100](-[O;D2;x1])(-[O;D2;x1])-;!@[C;z1;x1:1]')
77
+
78
+ rules['alkenyl_boronic_acid'] = smarts('[B;D3;z1;x2:100](-[O;D1])(-[O;D1])-;!@[C;z2;x1:1]=[C:2]')
79
+ rules['alkenyl_boronic_ester'] = smarts('[B;D3;z1;x2:100](-[O;D2;x1])(-[O;D2;x1])-;!@[C;z2;x1:1]=[C:2]')
80
+
81
+ rules['alkynyl_boronic_acid'] = smarts('[B;D3;z1;x2:100](-[O;D1])(-[O;D1])-;!@[C;z3;x1:1]')
82
+ rules['alkynyl_boronic_ester'] = smarts('[B;D3;z1;x2:100](-[O;D2;x1])(-[O;D2;x1])-;!@[C;z3;x1:1]')
83
+
84
+ # molander salts (trifluoroborates)
85
+ rules['aryl_molander_salt'] = smarts('[B;D4;z1;x3;-:100](F)(F)(F)-;!@[C;a:1]')
86
+ rules['alkyl_molander_salt'] = smarts('[B;D4;z1;x3;-:100](F)(F)(F)-;!@[C;z1:1]')
87
+ rules['alkenyl_molander_salt'] = smarts('[B;D4;z1;x3;-:100](F)(F)(F)-;!@[C;z2:1]=[C:2]')
88
+ rules['alkynyl_molander_salt'] = smarts('[B;D4;z1;x3;-:100](F)(F)(F)-;!@[C;z3;x1:1]')
89
+
90
+ # alcohols and phenols
91
+ rules['primary_alcohol'] = smarts('[O;D1;z1;x0:1][C;D2;x1;z1:2]')
92
+ rules['secondary_alcohol'] = smarts('[O;D1;z1;x0:1][C;D3;x1;z1:2]')
93
+ rules['tertiary_alcohol'] = smarts('[O;D1;z1;x0:1][C;D4;x1;z1:2]')
94
+ # tertiary alcohol with adjacent sp3 CH (eliminable): for dehydration
95
+ rules['tertiary_alcohol_with_alpha_h'] = smarts('[O;D1;z1;x0:1]-[C;D4;x1;z1:2]-[C;z1;h1,h2:3]')
96
+ rules['phenol'] = smarts('[O;D1;z1;x0:1]-[C;a:2]')
97
+
98
+ # aldehydes and ketones
99
+ rules['aldehyde'] = smarts('[O;z2;x0:2]=[C;D2;x1;z2:1]')
100
+ rules['ketone'] = smarts('[O;z2;x0:2]=[C;D3;x1;z2:1]')
101
+ # enal: alpha,beta-unsaturated aldehyde (for Doebner-Miller)
102
+ rules['enal'] = smarts('[O;z2;x0:2]=[C;D2;x1;z2:1]-[C;z2;x0:3]=[C;x0:4]')
103
+ # fisher, friedlander
104
+ rules['alpha_ketone'] = smarts('[O;z2;x0:2]=[C;D3;x1:1]-[C;z1;D1,D2;x0:3]')
105
+ # hantzsch thiazole, imidazo[1,2-a]pyridine
106
+ rules['alpha_haloketone'] = smarts('[O;z2;x0:2]=[C;D3;x1:1]-[C;z1;D2,D3;x1:3]([Cl,Br,I;D1:100])')
107
+ # imidazo[1,2-a]pyridine from alpha-haloester (ester O masked, carbonyl O deleted)
108
+ rules['alpha_haloester'] = smarts('[O;D2;x0;M]-[C;D3;x2;z2:1](=[O:2])-[C;z1;D2,D3;x1:3]([Cl,Br,I;D1:100])')
109
+
110
+ rules['1_2_diketone'] = smarts('[O;z2;x0:2]=[C;D3;x1:1]-[C;z2;x1;D3:3]=[O:4]')
111
+ rules['1_3_diketone'] = smarts('[O;z2;x0:2]=[C;D3;x1:1]-[C;z1;D2,D3:5]-[C;z2;x1;D3:3]=[O:4]')
112
+ rules['1_4_diketone'] = smarts('[O;z2;x0:2]=[C;D3;x1:1]-[C;z1;D2,D3:5]-[C;z1;D2,D3:6]-[C;z2;x1;D3:3]=[O:4]')
113
+ rules['beta_ketoester'] = smarts('[O;z2;x0:2]=[C;D3;x1:1]-[C;z1;D2;x0:5]-[C;z2;x2;D3:3](=[O:4])[O;D2:100]')
114
+
115
+ # acids
116
+ rules['alkyl_carboxylic_acid'] = smarts('[O;D1;z1;x0:100][C;z2;x2;D3:1](=[O:2])[C;z1:3]')
117
+ rules['aryl_carboxylic_acid'] = smarts('[O;D1;z1;x0:100][C;z2;x2;D3:1](=[O:2])[C;a:3]')
118
+ rules['carboxylic_acid'] = smarts('[O;D1;z1;x0:100][C;z2;x2;D3:1]=[O:2]')
119
+ rules['acyl_chloride'] = smarts('[Cl:100][C;z2;x2;D3:1]=[O:2]')
120
+ rules['acyl_fluoride'] = smarts('[F:100][C;z2;x2;D3:1]=[O:2]')
121
+ rules['chloroformate'] = smarts('[Cl:100][C;z2;x3;D3:1](=[O:2])-[O;D2:3]')
122
+ rules['fluoroformate'] = smarts('[F:100][C;z2;x3;D3:1](=[O:2])-[O;D2:3]')
123
+ rules['carbamoyl_chloride'] = smarts('[Cl:100][C;z2;x3;D3:1](=[O:2])-[N;D2,D3:3]')
124
+ rules['carbamoyl_fluoride'] = smarts('[F:100][C;z2;x3;D3:1](=[O:2])-[N;D2,D3:3]')
125
+
126
+ # amines
127
+ rules['primary_amine'] = smarts('[N;D1;z1;x0:1][C;z1:2]')
128
+ rules['primary_aniline'] = smarts('[N;D1;z1;x0:1][C;a:2]')
129
+ # NH2 on sp2 C=N (e.g. pyrazoline, amidine-like)
130
+ rules['primary_amidine_amine'] = smarts('[N;D1;z1;x0:1]-[C;z2:2]=[N;M]')
131
+ rules['secondary_amine'] = smarts('[N;D2;z1;x0:1]([C;z1:2])[C;z1:3]')
132
+ rules['secondary_aniline'] = smarts('[N;D2;z1;x0:1]([C;a:2])[C;z1:3]')
133
+ rules['biaryl_aniline'] = smarts('[N;D2;z1;x0:1]([C;a:2])[C;a:3]')
134
+
135
+ # esters and amides
136
+ rules['ester'] = smarts('[O;z2;x0:2]=[C;D3;x2;z2:1]-[O;D2;x0:100]')
137
+ rules['primary_amide'] = smarts('[N;D1;z1;x0:1][C;z2;x2;D3:2]=[O:3]')
138
+ rules['secondary_amide'] = smarts('[N;D2;z1;x0:1][C;z2;x2;D3:2]=[O:3]')
139
+
140
+ # sulfonyl
141
+ rules['sulfonyl_chloride'] = smarts('[S;D4:1](=[O:2])(=[O:3])[Cl;D1:100]')
142
+ rules['sulfonyl_fluoride'] = smarts('[S;D4:1](=[O:2])(=[O:3])[F;D1:100]')
143
+ rules['sulfonamide'] = smarts('[S;x3;D4:1](=[O:2])(=[O:3])-[N;z1:100]')
144
+ rules['sulfonyl_anhydride'] = smarts('[S;x3;D4:1](=[O:2])(=[O:3])-[O:100]-[S;x3;D4](=O)(=O)')
145
+
146
+ # nitrogen functional groups
147
+ rules['nitrile'] = smarts('[N;D1;z3;x0:2]#[C;D2;x1:1]')
148
+ rules['azide'] = smarts('[N;x1;D2:1]=[N+:2]=[N-:3]')
149
+ rules['isocyanate'] = smarts('[N;z2;x0;D2:1]=[C:2]=[O:3]')
150
+ rules['isocyano'] = smarts('[N;D2;x0;+:1]#[C;-:2]')
151
+ rules['guanidine'] = smarts('[N;z1;x0:1][C;!R:2]([N;z1;x0:3])=[N;x0:4]')
152
+ rules['nitro'] = smarts('[N;D3;x2;+:1]([O;-:2])=[O:3]')
153
+
154
+ # grignard reagents (RMgX)
155
+ rules['alkyl_grignard'] = smarts('[Mg;D2:100](-[F,Cl,Br,I])-[C;z1:1]')
156
+ rules['aryl_grignard'] = smarts('[Mg;D2:100](-[F,Cl,Br,I])-[C;a:1]')
157
+ rules['alkenyl_grignard'] = smarts('[Mg;D2:100](-[F,Cl,Br,I])-[C;z2:1]=[C:2]')
158
+
159
+ # organozinc reagents (RZnX)
160
+ rules['alkyl_zinc'] = smarts('[Zn;D2:100](-[F,Cl,Br,I])-[C;z1:1]')
161
+ rules['aryl_zinc'] = smarts('[Zn;D2:100](-[F,Cl,Br,I])-[C;a:1]')
162
+ rules['alkenyl_zinc'] = smarts('[Zn;D2:100](-[F,Cl,Br,I])-[C;z2:1]=[C:2]')
163
+
164
+ # boronate alkyl halides: B-CH2-X (one-carbon, for SN2)
165
+ rules['boronate_alkyl_chloride'] = smarts('[Cl;D1:100]-[C;z1;D2;x2:1]-[B;M]')
166
+ rules['boronate_alkyl_bromide'] = smarts('[Br;D1:100]-[C;z1;D2;x2:1]-[B;M]')
167
+ rules['boronate_alkyl_iodide'] = smarts('[I;D1:100]-[C;z1;D2;x2:1]-[B;M]')
168
+
169
+ # stannanes (R-SnR3)
170
+ rules['aryl_stannane'] = smarts('[Sn;D4;z1:100]-;!@[C;a:1]')
171
+ rules['alkenyl_stannane'] = smarts('[Sn;D4;z1:100]-;!@[C;z2:1]=[C:2]')
172
+ rules['alkyl_stannane'] = smarts('[Sn;D4;z1:100]-;!@[C;z1;D2,D3,D4:1]')
173
+
174
+ # silanes (R-SiR3, for Hiyama coupling)
175
+ rules['aryl_silane'] = smarts('[Si;D4:100]-;!@[C;a:1]')
176
+ rules['alkenyl_silane'] = smarts('[Si;D4:100]-;!@[C;z2:1]=[C:2]')
177
+
178
+ # phosphorus ylides and phosphonates
179
+ rules['phosphonium_ylide'] = smarts('[P;D4;z2;x0:100]=[C:1]')
180
+ rules['phosphonate'] = smarts('[P;D4;x3:100](=O)([O;D2;x1])([O;D2;x1])-[C:1]')
181
+
182
+ # weinreb amide
183
+ rules['weinreb_amide'] = smarts('[O:2]=[C;D3;x2:1]-[N;D3;x1:100][O;D2;x1]')
184
+
185
+ # arene C-H (for electrophilic aromatic substitution)
186
+ rules['arene_ch'] = smarts('[C;a;D2:1]')
187
+
188
+ # sulfur
189
+ rules['thiol'] = smarts('[S;x0;D1;z1:1][C;z1:2]')
190
+ rules['thioether'] = smarts('[S;D2;z1;x0:1]([C:2])[C:3]')
191
+ rules['sulfoxide'] = smarts('[S;D3;z2:1](=[O:2])([C:3])[C:4]')
192
+ rules['sulfone'] = smarts('[S;D4:1](=[O:2])(=[O:3])([C:4])[C:5]')
193
+
194
+ # lactam halides: ring-closure patterns for activated C(sp2)-X in 6-membered lactam rings
195
+ # covers pyridone, pyridazinone, quinazolinone, pyrimidinone, benzo-fused lactams
196
+ # 6 positional patterns per halide (all relative positions of X to N-C=O in ring)
197
+ for _x, _name in (('F', 'fluoride'), ('Cl', 'chloride'), ('Br', 'bromide'), ('I', 'iodide')):
198
+ # X=C-C/N-C(=O)-N-C/N e.g. CN1C=CC(Br)=CC1=O
199
+ rules[f'lactam_1_{_name}'] = smarts(f'[{_x};D1:100]-[C;z2;r6:1]1=[C,N;z2;M]-[C;D3;z2;M]-[N;D3;M]-[C,N;z2,z4;M]=,:[C,N;z2,z4;M]1')
200
+ # X=C-N-C(=O)-C/N-C/N e.g. CN1C(Br)=CC=CC1=O
201
+ rules[f'lactam_2_{_name}'] = smarts(f'[{_x};D1:100]-[C;z2;r6:1]=1-[N;D3;M]-[C;D3;z2;M]-[C,N;z2,z4;M]=,:[C,N;z2,z4;M]-[C,N;z2;M]=1')
202
+ # X=C-N-C/N-C/N-C(=O) e.g. CN1C=CC(=O)C=C1Br
203
+ rules[f'lactam_3_{_name}'] = smarts(f'[{_x};D1:100]-[C;z2;r6:1]=1-[N;D3;M]-[C,N;z2,z4;M]=,:[C,N;z2,z4;M]-[C;D3;z2;M]-[C,N;z2;M]=1')
204
+ # X=C-C(=O)-N-C/N-C/N e.g. CN1C=CC=C(Br)C1=O
205
+ rules[f'lactam_4_{_name}'] = smarts(f'[{_x};D1:100]-[C;z2;r6:1]=1-[C;D3;z2;M]-[N;D3;M]-[C,N;z2,z4;M]=,:[C,N;z2,z4;M]-[C,N;z2;M]=1')
206
+ # X=C-C/N-C/N-N-C(=O) e.g. CN1C=CC(=O)C(Br)=C1
207
+ rules[f'lactam_5_{_name}'] = smarts(f'[{_x};D1:100]-[C;z2;r6:1]1=[C,N;z2;M]-[N;D3;M]-[C,N;z2,z4;M]=,:[C,N;z2,z4;M]-[C;D3;z2;M]1')
208
+ # X=C-C/N-N-C(=O)-C/N e.g. CN1C=C(Br)C=CC1=O
209
+ rules[f'lactam_6_{_name}'] = smarts(f'[{_x};D1:100]-[C;z2;r6:1]1=[C,N;z2;M]-[N;D3;M]-[C;D3;z2;M]-[C,N;z2,z4;M]=,:[C,N;z2,z4;M]1')
210
+
211
+ # pyrrole. for tautomerism handling H not in template.
212
+ rules['pyrrole'] = smarts('[N;h1;D2;a;r5:1]')
213
+ rules['pyrazole'] = smarts('[N;h1;D2;a;r5:1]:[N;h0;D2;r5:2]')
214
+ rules['imidazole'] = smarts('[N;h1;D2;a;r5:1]:[A:2]:[N;h0;D2;r5:3]')
215
+
216
+ # heterocycles (for screening)
217
+ rules['isoxazole'] = smarts('[O;a;D2;r5:1]:[N;a;D2;r5:2]')
218
+ rules['pyridazine'] = smarts('[N;a;D2;r6:1]:[N;a;D2;r6:2]')
219
+
220
+ # hydrazines
221
+ rules['alkyl_hydrazine'] = smarts('[N;D1;z1;x1:2]-[N;D2;z1;x1:1]-[C;z1:3]')
222
+ rules['aryl_hydrazine'] = smarts('[N;D1;z1;x1:2]-[N;D2;z1;x1:1]-[C;a:3]:[C;a;D2:4]')
223
+
224
+ # hydrazone (C=N-NH-R, product of carbonyl + hydrazine condensation)
225
+ rules['hydrazone'] = smarts('[C;z2:1]=[N;D2;z2;x1:2]-[N;D2;z1;x1:3]')
226
+
227
+ # thioamide (for Hantzsch thiazole)
228
+ rules['thioamide'] = smarts('[S;z2;x0;D1:2]=[C;D3;x2:1]-[N;D1:3]')
229
+
230
+ # ortho-bifunctional arenes
231
+ rules['o_diaminoarene'] = smarts('[N;D1;z1;x0:1]-[C;a:3]:[C;a:4]-[N;D1,D2;z1;x0:2]')
232
+ rules['o_aminophenol'] = smarts('[N;D1;z1;x0:1]-[C;a:3]:[C;a:4]-[O;D1:2]')
233
+ rules['o_aminothiophenol'] = smarts('[N;D1;z1;x0:1]-[C;a:3]:[C;a:4]-[S;D1:2]')
234
+ rules['o_aminobenzaldehyde'] = smarts('[N;D1;z1;x0:1]-[C;a:3]:[C;a:4]-[C;D2;z2;x1:5]=[O:6]')
235
+ rules['anthranilic_acid'] = smarts('[N;D1;z1;x0:1]-[C;a:3]:[C;a:4]-[C;z2;x2;D3:5](=[O:6])[O;D1:100]')
236
+
237
+ # amidoxime: RC(=NH)NHOH canonical form (for 1,2,4-oxadiazole)
238
+ rules['amidoxime'] = smarts('[N;D1;z2;x0:3]=[C;D3;x2:1]-[N;D2;z1;x1:2]-[O;D1:4]')
239
+
240
+ # amidine: RC(=NH)NH2 (for pyrimidine)
241
+ rules['amidine'] = smarts('[N;D1;z1;x0:3]-[C;D3;z2;x2:1]=[N;D1:2]')
242
+ # urea/thiourea (for Biginelli)
243
+ rules['urea'] = smarts('[N;D1;z1;x0:1]-[C;D3;z2;x3:2](=[O:3])-[N;D1:4]')
244
+ rules['thiourea'] = smarts('[N;D1;z1;x0:1]-[C;D3;z2;x3:2](=[S;D1:3])-[N;D1:4]')
245
+
246
+ # beta-arylethylamine (for Pictet-Spengler)
247
+ rules['beta_arylethylamine'] = smarts('[N;D1;z1;x0:1]-[C;z1:2]-[C;z1:3]-[C;a:4]:[C;a;D2:5]')
248
+ # 2-aminopyridine / 2-aminoazine (for GBB, imidazo[1,2-a]pyridine)
249
+ rules['aminopyridine'] = smarts('[N;D1;z1;x0:1]-[C;a:2]:[N;a;h0;D2:3]')
250
+
251
+ # amino alcohol (for oxazoline formation): H2N-C-C-OH
252
+ rules['amino_alcohol'] = smarts('[N;D1;z1;x0:1]-[C;z1:2]-[C;z1:3]-[O;D1:4]')
253
+
254
+ # hydroxamic acid: R-C(=O)-NH-OH
255
+ rules['hydroxamic_acid'] = smarts('[O;D1;z1;x1:1]-[N;D2;z1;x1:2]-[C;z2;x2:3]=[O:4]')
256
+
257
+ # oxime: C=N-OH
258
+ rules['oxime'] = smarts('[O;D1;z1;x1:1]-[N;D2;z2;x1:2]=[C:3]')
259
+
260
+ # O-alkylhydroxylamine: R-O-NH2 (for oxime ether formation)
261
+ rules['O_alkylhydroxylamine'] = smarts('[N;D1;z1;x1:1]-[O;D2;z1;x1:2]-[C:3]')
262
+
263
+ # alpha-isocyano (for Van Leusen oxazole)
264
+ rules['tosyl_isocyanide'] = smarts('[C;-;D1:2]#[N;+;D2:1]-[C;D2,D3;z1;x2:3]-[S;D4;x2:100](=O)=O')
265
+
266
+ # thioester (for Liebeskind-Srogl): R-C(=O)-S-R'
267
+ rules['thioester'] = smarts('[O;z2;x0:2]=[C;D3;x2;z2:1]-[S;D2;z1;x0:100]')
268
+
269
+ # active methylene (for Knoevenagel): CH flanked by 2 EWGs
270
+ rules['active_methylene'] = smarts('[C;z1;D2,D3;x0:1](-[C;z2,z3;x1,x2:2])-[C;z2,z3;x1,x2:3]')
271
+
272
+ # aniline with ortho C-H (for Doebner-Miller)
273
+ rules['aniline_ortho_ch'] = smarts('[N;D1;z1;x0:1]-[C;a:2]:[C;a;D2:3]')
274
+
275
+ # ortho-haloaniline (for Larock indole)
276
+ rules['o_haloaniline'] = smarts('[N;D1;z1;x0:1]-[C;a:2]:[C;a:3]-[Cl,Br,I;D1:100]')
277
+
278
+ # tertiary amine (for N-oxidation): NR3 where all substituents are C
279
+ rules['tertiary_amine'] = smarts('[N;D3;z1;x0:1]')
280
+
281
+ # pyridine-like nitrogen (for N-oxidation): aromatic N with no H, degree 2
282
+ rules['pyridine_n'] = smarts('[N;a;D2;h0:1]')
283
+
284
+ return rules
285
+
286
+
287
+ rules = Proxy(_rules)
288
+
289
+
290
+ __all__ = ['rules']
@@ -0,0 +1,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2026 Ramil Nugmanov <nougmanoff@protonmail.com>
4
+ # This file is part of chython.
5
+ #
6
+ # chython is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program; if not, see <https://www.gnu.org/licenses/>.
18
+ #
19
+ from lazy_object_proxy import Proxy
20
+
21
+
22
+ def _make_reactor(rxn_name, fg_name, output_fg, product_smarts):
23
+ from ._functional import rules
24
+ from ...reactor import Reactor
25
+ from ... import smarts
26
+
27
+ q = rules[fg_name]
28
+ product = smarts(product_smarts)
29
+ return rxn_name, fg_name, output_fg, Reactor((q,), (product,),
30
+ delete_atoms=True, one_shot=True, ignore_pyrrole_hydrogen=True)
31
+
32
+
33
+ def _rules():
34
+ rules = []
35
+
36
+ # primary_alcohol → aldehyde (Swern, Dess-Martin, PCC)
37
+ rules.append(_make_reactor('alcohol_to_aldehyde', 'primary_alcohol', 'aldehyde', '[A:1]=[A:2]'))
38
+
39
+ # secondary_alcohol → ketone (Dess-Martin, Jones, PCC)
40
+ rules.append(_make_reactor('alcohol_to_ketone', 'secondary_alcohol', 'ketone', '[A:1]=[A:2]'))
41
+
42
+ # aldehyde → carboxylic_acid (Pinnick, Jones, KMnO4)
43
+ rules.append(_make_reactor('aldehyde_to_acid', 'aldehyde', 'carboxylic_acid', '[A:2]=[A:1]-[O:20]'))
44
+
45
+ # alkene → 1,2-diol (Sharpless dihydroxylation, OsO4/KMnO4)
46
+ rules.append(_make_reactor('dihydroxylation', 'terminal_alkene', 'vicinal_diol', '[A:1](-[O:20])-[A:2](-[O:21])'))
47
+ rules.append(_make_reactor('dihydroxylation', 'alkene', 'vicinal_diol', '[A:1](-[O:20])-[A:2](-[O:21])'))
48
+
49
+ # thioether → sulfoxide (mCPBA, H2O2, NaIO4)
50
+ rules.append(_make_reactor('thioether_to_sulfoxide', 'thioether', 'sulfoxide', '[A:1](=[O:20])(-[A:2])-[A:3]'))
51
+
52
+ # thioether → sulfone (excess mCPBA, H2O2/AcOH, Oxone)
53
+ rules.append(_make_reactor('thioether_to_sulfone', 'thioether', 'sulfone', '[A:1](=[O:20])(=[O:21])(-[A:2])-[A:3]'))
54
+
55
+ # sulfoxide → sulfone
56
+ rules.append(_make_reactor('sulfoxide_to_sulfone', 'sulfoxide', 'sulfone', '[A:1](=[A:2])(=[O:20])(-[A:3])-[A:4]'))
57
+
58
+ # tertiary amine → N-oxide (mCPBA, H2O2)
59
+ rules.append(_make_reactor('nitrogen_oxidation', 'tertiary_amine', None, '[N;+:1]([O;-:20])'))
60
+
61
+ # pyridine → N-oxide (mCPBA, H2O2)
62
+ rules.append(_make_reactor('nitrogen_oxidation', 'pyridine_n', None, '[N;+:1]([O;-:20])'))
63
+
64
+ return rules
65
+
66
+
67
+ rules = Proxy(_rules)
68
+
69
+
70
+ __all__ = ['rules']