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,198 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2025, 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 chython import smiles
20
+ from chython.algorithms.standardize._isomers import rules as isomer_rules
21
+ from pytest import mark
22
+
23
+
24
+ def _prepare(raw):
25
+ mol = smiles(raw)
26
+ mol.kekule()
27
+ mol.thiele()
28
+ return mol
29
+
30
+
31
+ def _standardized(raw):
32
+ mol = _prepare(raw)
33
+ mol.standardize_isomers()
34
+ return mol
35
+
36
+
37
+ isomer_data = [
38
+ # fixed charge rules
39
+ ('N1C=CN2[NH+]=CC=C12', 'N1C=CC2=[NH+]C=CN12'),
40
+ ('N(C)1C=CN2[N+](C)=CC=C12', 'N(C)1C=CC2=[N+](C)C=CN12'),
41
+ ('N1C=C[N+]2=C1C=CN2', 'N1C=CC2=[NH+]C=CN12'),
42
+ ('N1C=C2C=CN[N+]2=C1', 'N1C=CC2=C[NH+]=CN12'),
43
+ ('N1C=C2NC=C[N+]2=C1', 'N1C=CN2C=[NH+]C=C12'),
44
+ ('N1C2=[NH+]C=CC2=CC=C1', 'N1C=CC2=CC=C[NH+]=C12'),
45
+ ('N1C=C2C(=CC=[NH+]2)C=C1', 'N1C=CC2=CC=[NH+]C=C12'),
46
+ ('C1=CC=2C(=[NH+]1)C=CNC=2', 'N1C=CC2=C[NH+]=CC=C12'),
47
+ ('C=1C=[NH+]C=2C=1NC=CC=2', 'N1C=CC2=[NH+]C=CC=C12'),
48
+ ('C1=2C=[NH+]C=C1NC=CC=2', 'N1C=C2C=CC=[NH+]C2=C1'),
49
+ ('C1=2C=CNC=C1C=[NH+]C=2', 'N1C=C2C=C[NH+]=CC2=C1'),
50
+
51
+ # fixed charge rules with D3([A]) charge holders
52
+ ('C12=CC=[NH+]N1C=CN2C', 'C1=C[N+](C)=C2C=CNN12'),
53
+ ('C=1N2[N+](C)=CC=C2NC=1', 'C=1N(C)N2C(=[NH+]C=C2)C=1'),
54
+ ('N12[N+](=CC=C1N(C=C2)C)C', 'C1=CC2=[N+](C)C=CN2N1C'),
55
+ ('C=1N(C=2C=CN[N+]=2C=1)C', 'C1=C[N+](C)=C2C=CNN12'),
56
+ ('C1=CC=2NC=C[N+]=2N1C', 'C=1N(C)N2C(=[NH+]C=C2)C=1'),
57
+ ('CN1C=2C=CN(C)[N+]=2C=C1', 'C1=CC2=[N+](C)C=CN2N1C'),
58
+ ('C=1C2=CN(C)C=[N+]2NC=1', '[N+]1(C)=CN2NC=CC2=C1'),
59
+ ('[N+]12=CNC=C1C=CN2C', 'C1=C2C=CN(N2C=[NH+]1)C'),
60
+ ('N1(C=C2[N+](N(C)C=C2)=C1)C', 'C1=CC=2N(N1C)C=[N+](C)C=2'),
61
+ ('C=12[N+](=CN(C=1)C)C=CN2', 'C=1N2C(NC=1)=C[N+](C)=C2'),
62
+ ('C=1N(C)C=2[N+](=CNC=2)C=1', 'C1=CN2C(=C[NH+]=C2)N1C'),
63
+ ('N1(C=[N+]2C(N(C)C=C2)=C1)C', 'C1=2N(C=CN1C)C=[N+](C)C=2'),
64
+ ('C=1N(C)C2=[NH+]C=CC2=CC=1', '[N+]=1(C)C=CC=C2C=CNC=12'),
65
+ ('[N+]=1(C)C=CC2=CC=CNC=12', 'C=1N(C)C2=[NH+]C=CC=C2C=1'),
66
+ ('C=1N(C)C2=[N+](C)C=CC2=CC=1', 'C=1N(C)C2=[N+](C)C=CC=C2C=1'),
67
+ ('C1=CC=2C(=CN1C)[NH+]=CC=2', 'C=1C2=C(C=C[N+]=1C)C=CN2'),
68
+ ('C=1C2=CC=[N+](C)C2=CNC=1', 'N1(C)C=2C=[NH+]C=CC=2C=C1'),
69
+ ('C1=CC2=CC=[N+](C)C2=CN1C', 'N1(C)C=2C=[N+](C=CC=2C=C1)C'),
70
+ ('C1=CN(C=C2C=C[NH+]=C12)C', 'C=1C2=C(C=CN2)C=[N+](C=1)C'),
71
+ ('C=1[N+](C)=C2C=CNC=C2C=1', 'N1(C)C=2C=C[NH+]=CC=2C=C1'),
72
+ ('C=1[N+](C)=C2C=CN(C=C2C=1)C', 'C=12C=CN(C)C=1C=C[N+](=C2)C'),
73
+ ('C1=CC=C2C(=CC=[NH+]2)N1C', 'C=12C=CNC=1C=CC=[N+]2C'),
74
+ ('C1=2[N+](C)=CC=C1NC=CC=2', 'N1(C)C2=C([NH+]=CC=C2)C=C1'),
75
+ ('C1=CC=C2C(=CC=[N+]2C)N1C', 'C=12N(C)C=CC=1[N+](=CC=C2)C'),
76
+ ('C1=C2N(C=CC=C2C=[NH+]1)C', 'C=1C2=CNC=C2[N+](=CC=1)C'),
77
+ ('[N+]1(C)=CC2=CC=CNC2=C1', 'C1=2C=CC=[NH+]C1=CN(C=2)C'),
78
+ ('C=1N(C=2C(=CC=1)C=[N+](C)C=2)C', 'C1=2C=CC=[N+](C)C1=CN(C=2)C'),
79
+ ('[NH+]1=CC=2C(=C1)C=CN(C=2)C', 'C=1C2=CNC=C2C=C[N+]=1C'),
80
+ ('C=1C2=C[N+](C)=CC2=CNC=1', 'C12=CN(C)C=C1C=[NH+]C=C2'),
81
+ ('C1=C2C(=CN(C=C2)C)C=[N+]1C', 'C12=CN(C)C=C1C=[N+](C)C=C2'),
82
+
83
+ # Morgan charge rules
84
+ ('N1C=CC2=CC=[NH+]N12', 'N1C=CC2=CC=[NH+]N12'),
85
+ ('N1C=CC2=[N+]1NC=C2', 'N1C=CC2=CC=[NH+]N12'),
86
+ ('N1C=CN2C=C[NH+]=C12', 'N1C=CN2C=C[NH+]=C12'),
87
+ ('N1C=C[N+]2=C1NC=C2', 'N1C=CN2C=C[NH+]=C12'),
88
+ ('C=1N(C)C=[N+](CC)C=1', 'C=1N(C=[N+](C)C=1)CC'),
89
+ ('C=1N(C=[N+](C)C=1)CC', 'C=1N(C=[N+](C)C=1)CC'),
90
+ ('C=1N(C)C=[NH+]C=1', '[N+]1(=CNC=C1)C'),
91
+ ('C=1N([N+](C)=CC=1)CC', 'C1=CC=[N+](CC)N1C'),
92
+ ('C1=CC=[N+](CC)N1C', 'C1=CC=[N+](CC)N1C'),
93
+ ('C1=CC=[N+](C)N1C', 'C1=CC=[N+](C)N1C'),
94
+
95
+ # Morgan charge rules with D3([A]) charge holders
96
+ ('C1=CC2=CC=[NH+]N2N1C', '[N+]=1(C)N2NC=CC2=CC=1'),
97
+ ('[N+]=1(C)N2NC=CC2=CC=1', '[N+]=1(C)N2NC=CC2=CC=1'),
98
+ ('C1=CC2=CC=[N+](C)N2N1C', 'C1=CC2=CC=[N+](C)N2N1C'),
99
+ ('C1=CC2=[N+](N1)N(C)C=C2', '[N+]=1(C)N2NC=CC2=CC=1'),
100
+ ('CN1C=CC=2C=CN(C)[N+]1=2', 'C1=CC2=CC=[N+](C)N2N1C'),
101
+ ('C1=CN2C(=[NH+]C=C2)N1C', 'C[N+]1=C2NC=CN2C=C1'),
102
+ ('C[N+]1=C2NC=CN2C=C1', 'C[N+]1=C2NC=CN2C=C1'),
103
+ ('C=1N(C)C=2N(C=1)C=C[N+]=2C', 'C=1N(C)C=2N(C=1)C=C[N+]=2C'),
104
+ ('CN1C2=[N+](C=C1)C=CN2', 'C[N+]1=C2NC=CN2C=C1'),
105
+ ('C1=CN(C)C=2N(C)C=C[N+]1=2', 'C=1N(C)C=2N(C=1)C=C[N+]=2C'),
106
+ ('N1C=[NH+]C=C1', 'N1C=[NH+]C=C1'),
107
+ ('N1[NH+]=CC=C1', 'C=1C=[NH+]NC=1'),
108
+ ('N(C)1[NH+]=CC=C1', 'C1=CC=[NH+]N1C'),
109
+ ('N1[N+](C)=CC=C1', 'C1=CC=[NH+]N1C'),
110
+
111
+ # ferrocene
112
+ ('[CH-]1C=CC=C1.[Fe+2].[CH-]1C=CC=C1', '[CH-]1C=CC=C1.[Fe+2].[CH-]1C=CC=C1'),
113
+ ('[CH-]1C=CC=C1.[Fe+2].C1=C[CH-]C=C1', '[CH-]1C=CC=C1.[Fe+2].[CH-]1C=CC=C1'),
114
+
115
+ # fixed tautomer rules
116
+ ('N1C=NC=N1', 'N1C=NN=C1'),
117
+ ('CC1=NC=NN1', 'N1=CNC(C)=N1'),
118
+ ('CC1=NN=CN1', 'N1=CNC(C)=N1'),
119
+ ('N1N=CN=N1', 'N1C=NN=N1'),
120
+ ('CC1=NNN=N1', 'C=1(C)NN=NN=1'),
121
+ ('CC1=NN=NN1', 'C=1(C)NN=NN=1'),
122
+
123
+ # Morgan tautomer rules
124
+ ('CC1=NNC=C1', 'N1C=CC(C)=N1'),
125
+ ('CC1=CC=NN1', 'N1C=CC(C)=N1'),
126
+ ('CC1=CN=CN1', 'C=1N=CNC=1C'),
127
+ ('CC1=CNC=N1', 'C=1N=CNC=1C'),
128
+ ('CC1=CN=NN1', 'N1N=NC(C)=C1'),
129
+ ('CC1=CNN=N1', 'N1N=NC(C)=C1'),
130
+ ('CC1=NNN=C1', 'N1N=NC(C)=C1'),
131
+ ('CC1=NC2=NNC(C)=C2N1', 'C1(C)=NC=2C(=NNC=2N1)C'),
132
+ ('CC1=NC2=C(N1)C(C)=NN2', 'C1(C)=NC=2C(=NNC=2N1)C'),
133
+ ('CC1=NC2=C(C)NN=C2N1', 'C1(C)=NC=2C(=NNC=2N1)C'),
134
+
135
+ # amidine, guanidine, etc.
136
+ ('COC(=N)NC', 'COC(N)=NC'),
137
+ ('COC(N)=NC', 'COC(N)=NC'),
138
+ ('CCN=C(N)NC', 'CCNC(=NC)N'),
139
+ ('CCNC(=N)NC', 'CCNC(=NC)N'),
140
+ ('CCNC(N)=NC', 'CCNC(=NC)N'),
141
+ ('CNC(N)=NC(=N)NC', 'CNC(=N)NC(=N)NC'),
142
+ ('CNC(=N)NC(=N)NC', 'CNC(=N)NC(=N)NC'),
143
+ ('CNC(N)=NC(N)=NC', 'CNC(=N)NC(=N)NC'),
144
+ ('CCN=CNC=NC', 'CCN=CN=CNC'),
145
+ ]
146
+
147
+
148
+ @mark.parametrize('raw,result', isomer_data)
149
+ def test_standardize_isomers(raw, result):
150
+ assert _standardized(raw) == _prepare(result), f'{raw} > {_standardized(raw)} != {result}'
151
+
152
+
153
+ changed_data = [
154
+ ('N(C)1C=CN2[N+](C)=CC=C12', True),
155
+ ('N1C=C2C=CN[N+]2=C1', True),
156
+ ('N(C)1C=C2C=CN[N+]2=C1', True),
157
+ ('N1C=C2C=CN(C)[N+]2=C1', True),
158
+ ('N(C)1C=C2C=CN(C)[N+]2=C1', True),
159
+ ('C=1N(C)C=[N+](CC)C=1', True),
160
+ ('C=1N(C=[N+](C)C=1)CC', False),
161
+ ('C=1N([N+](C)=CC=1)CC', True),
162
+ ('C1=CC=[N+](CC)N1C', False),
163
+ ('C1=CC=[N+](C)N1C', False),
164
+ ('[N+]1(=CNC=C1)C', False),
165
+ ('C[N+]1=CC=NC=C1', False),
166
+ ]
167
+
168
+
169
+ @mark.parametrize('raw,changed', changed_data)
170
+ def test_standardize_isomers_changed(raw, changed):
171
+ mol = _prepare(raw)
172
+ assert bool(mol.standardize_isomers()) is changed
173
+
174
+
175
+ idempotent_data = [
176
+ raw for raw, _ in isomer_data
177
+ ] + [
178
+ raw for raw, _ in changed_data
179
+ ]
180
+
181
+
182
+ @mark.parametrize('raw', idempotent_data)
183
+ def test_standardize_isomers_idempotent(raw):
184
+ mol = _prepare(raw)
185
+ mol.standardize_isomers()
186
+ first = str(mol)
187
+ mol.standardize_isomers()
188
+ assert str(mol) == first
189
+
190
+
191
+ def test_all_isomer_rules_have_representative():
192
+ mols = [(raw, _prepare(raw)) for raw in idempotent_data]
193
+ missing = []
194
+ for index, (query, rule_type) in enumerate(isomer_rules, 1):
195
+ if not any(next(query.get_mapping(mol, automorphism_filter=False), None) for _, mol in mols):
196
+ missing.append((index, rule_type, str(query)))
197
+
198
+ assert not missing