chython 3.3.2__tar.gz → 3.4__tar.gz

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 (469) hide show
  1. {chython-3.3.2/chython.egg-info → chython-3.4}/PKG-INFO +1 -1
  2. {chython-3.3.2 → chython-3.4}/chython/chemistry/_canonicalize.py +52 -8
  3. {chython-3.3.2 → chython-3.4}/chython/chemistry/_isomers.py +33 -11
  4. {chython-3.3.2 → chython-3.4}/chython/chemistry/_protomers.py +8 -2
  5. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/acids.tsv +2 -2
  6. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/standardize_groups.tsv +1 -1
  7. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_canonicalize.py +39 -0
  8. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_isomers.py +37 -0
  9. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_protomers.py +2 -2
  10. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_thiele_is_single_purpose.py +4 -11
  11. {chython-3.3.2 → chython-3.4}/chython/core/_features.pxi +9 -2
  12. {chython-3.3.2 → chython-3.4}/chython/core/_reaction_passes.py +34 -13
  13. {chython-3.3.2 → chython-3.4}/chython/core/_smirks_patch.pxi +53 -9
  14. {chython-3.3.2 → chython-3.4}/chython/core/_smirks_read.pxi +9 -2
  15. {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_passes.py +27 -1
  16. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smarts_read.py +15 -6
  17. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_patch.py +52 -1
  18. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_query.py +5 -8
  19. {chython-3.3.2 → chython-3.4}/chython/reactions/_enumerate.py +174 -17
  20. {chython-3.3.2 → chython-3.4}/chython/reactions/_reconstruct.py +98 -34
  21. {chython-3.3.2 → chython-3.4}/chython/reactions/_stickers.py +46 -14
  22. {chython-3.3.2 → chython-3.4}/chython/reactions/_tables.py +2 -2
  23. {chython-3.3.2 → chython-3.4}/chython/reactions/tables/functional.tsv +26 -12
  24. {chython-3.3.2 → chython-3.4}/chython/reactions/tables/protective.tsv +3 -0
  25. chython-3.4/chython/reactions/tables/reactions.tsv +857 -0
  26. {chython-3.3.2 → chython-3.4}/chython/reactions/tables/roles.tsv +2 -0
  27. {chython-3.3.2 → chython-3.4}/chython/reactions/test/_frozen_ids.py +35 -3
  28. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_enumerate.py +18 -0
  29. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_protective.py +67 -2
  30. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_reconstruct.py +86 -3
  31. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_roles.py +1 -1
  32. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_stickers.py +87 -0
  33. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_tables.py +8 -2
  34. {chython-3.3.2 → chython-3.4}/chython/test/test_facade_names.py +3 -3
  35. {chython-3.3.2 → chython-3.4/chython.egg-info}/PKG-INFO +1 -1
  36. {chython-3.3.2 → chython-3.4}/pyproject.toml +1 -1
  37. chython-3.3.2/chython/reactions/tables/reactions.tsv +0 -427
  38. {chython-3.3.2 → chython-3.4}/LICENSE +0 -0
  39. {chython-3.3.2 → chython-3.4}/MANIFEST.in +0 -0
  40. {chython-3.3.2 → chython-3.4}/README.md +0 -0
  41. {chython-3.3.2 → chython-3.4}/build_inchi.py +0 -0
  42. {chython-3.3.2 → chython-3.4}/chython/__init__.py +0 -0
  43. {chython-3.3.2 → chython-3.4}/chython/_functions.py +0 -0
  44. {chython-3.3.2 → chython-3.4}/chython/chemistry/__init__.py +0 -0
  45. {chython-3.3.2 → chython-3.4}/chython/chemistry/_abbreviations.py +0 -0
  46. {chython-3.3.2 → chython-3.4}/chython/chemistry/_counts.py +0 -0
  47. {chython-3.3.2 → chython-3.4}/chython/chemistry/_crippen.py +0 -0
  48. {chython-3.3.2 → chython-3.4}/chython/chemistry/_hydrogens.py +0 -0
  49. {chython-3.3.2 → chython-3.4}/chython/chemistry/_implicit.py +0 -0
  50. {chython-3.3.2 → chython-3.4}/chython/chemistry/_kekule_form.py +0 -0
  51. {chython-3.3.2 → chython-3.4}/chython/chemistry/_maccs.py +0 -0
  52. {chython-3.3.2 → chython-3.4}/chython/chemistry/_organometallics.py +0 -0
  53. {chython-3.3.2 → chython-3.4}/chython/chemistry/_perceive.py +0 -0
  54. {chython-3.3.2 → chython-3.4}/chython/chemistry/_pharmacophore.py +0 -0
  55. {chython-3.3.2 → chython-3.4}/chython/chemistry/_qed.py +0 -0
  56. {chython-3.3.2 → chython-3.4}/chython/chemistry/_residues.py +0 -0
  57. {chython-3.3.2 → chython-3.4}/chython/chemistry/_resonance.py +0 -0
  58. {chython-3.3.2 → chython-3.4}/chython/chemistry/_salts.py +0 -0
  59. {chython-3.3.2 → chython-3.4}/chython/chemistry/_saturate.py +0 -0
  60. {chython-3.3.2 → chython-3.4}/chython/chemistry/_smarts.py +0 -0
  61. {chython-3.3.2 → chython-3.4}/chython/chemistry/_standardize.py +0 -0
  62. {chython-3.3.2 → chython-3.4}/chython/chemistry/_tables.py +0 -0
  63. {chython-3.3.2 → chython-3.4}/chython/chemistry/_tpsa.py +0 -0
  64. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/abbreviations.tsv +0 -0
  65. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/covalent_radii.tsv +0 -0
  66. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/crippen.tsv +0 -0
  67. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/hbond.tsv +0 -0
  68. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/maccs.tsv +0 -0
  69. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/maccs_corpus.tsv +0 -0
  70. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/pharmacophore.tsv +0 -0
  71. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/qed_alerts.tsv +0 -0
  72. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/residues.tsv +0 -0
  73. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/resonance.tsv +0 -0
  74. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/rotatable.tsv +0 -0
  75. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/salts.tsv +0 -0
  76. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/standardize_metals.tsv +0 -0
  77. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/sybyl_types.tsv +0 -0
  78. {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/tpsa.tsv +0 -0
  79. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/__init__.py +0 -0
  80. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/_corpus.py +0 -0
  81. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/_oracle.py +0 -0
  82. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/gen_standardize_rules.py +0 -0
  83. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_abbreviations.py +0 -0
  84. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_acids_tsv.py +0 -0
  85. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_counts.py +0 -0
  86. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_covalent_radii_tsv.py +0 -0
  87. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_crippen.py +0 -0
  88. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_crippen_tsv.py +0 -0
  89. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_dependency_direction.py +0 -0
  90. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_featurizer_injection.py +0 -0
  91. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_featurizer_tables_lazy.py +0 -0
  92. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_kekule_form.py +0 -0
  93. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_maccs.py +0 -0
  94. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_maccs_corpus.py +0 -0
  95. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_maccs_tsv.py +0 -0
  96. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_organometallics.py +0 -0
  97. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_perceive.py +0 -0
  98. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_pharmacophore.py +0 -0
  99. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_qed.py +0 -0
  100. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_qed_alerts_tsv.py +0 -0
  101. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_reaction_hydrogen_repair.py +0 -0
  102. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_reaction_passes.py +0 -0
  103. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_residues.py +0 -0
  104. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_resonance.py +0 -0
  105. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_resonance_tsv.py +0 -0
  106. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_salts.py +0 -0
  107. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_saturate.py +0 -0
  108. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_smarts.py +0 -0
  109. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_differential.py +0 -0
  110. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_groups_port.py +0 -0
  111. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_overvalent_nitrogen.py +0 -0
  112. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_rules_examples.py +0 -0
  113. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_rules_merges.py +0 -0
  114. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_rules_tsv.py +0 -0
  115. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_tpsa.py +0 -0
  116. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_tpsa_tsv.py +0 -0
  117. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_valence_report.py +0 -0
  118. {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_z_translation.py +0 -0
  119. {chython-3.3.2 → chython-3.4}/chython/core/RULES.md +0 -0
  120. {chython-3.3.2 → chython-3.4}/chython/core/__init__.py +0 -0
  121. {chython-3.3.2 → chython-3.4}/chython/core/_canonical.pxi +0 -0
  122. {chython-3.3.2 → chython-3.4}/chython/core/_cip.pxi +0 -0
  123. {chython-3.3.2 → chython-3.4}/chython/core/_core.pyx +0 -0
  124. {chython-3.3.2 → chython-3.4}/chython/core/_descriptors.pxi +0 -0
  125. {chython-3.3.2 → chython-3.4}/chython/core/_elements.pxi +0 -0
  126. {chython-3.3.2 → chython-3.4}/chython/core/_facade.py +0 -0
  127. {chython-3.3.2 → chython-3.4}/chython/core/_fingerprints.pxi +0 -0
  128. {chython-3.3.2 → chython-3.4}/chython/core/_hydrogens.pxi +0 -0
  129. {chython-3.3.2 → chython-3.4}/chython/core/_inchi.pxi +0 -0
  130. {chython-3.3.2 → chython-3.4}/chython/core/_isomorphism.pxi +0 -0
  131. {chython-3.3.2 → chython-3.4}/chython/core/_kekule.pxi +0 -0
  132. {chython-3.3.2 → chython-3.4}/chython/core/_log.py +0 -0
  133. {chython-3.3.2 → chython-3.4}/chython/core/_ml.pxi +0 -0
  134. {chython-3.3.2 → chython-3.4}/chython/core/_molecule_arena.pxi +0 -0
  135. {chython-3.3.2 → chython-3.4}/chython/core/_molecule_container.pxi +0 -0
  136. {chython-3.3.2 → chython-3.4}/chython/core/_molecule_topology.pxi +0 -0
  137. {chython-3.3.2 → chython-3.4}/chython/core/_molecule_views.pxi +0 -0
  138. {chython-3.3.2 → chython-3.4}/chython/core/_morgan.pxi +0 -0
  139. {chython-3.3.2 → chython-3.4}/chython/core/_pach.pxi +0 -0
  140. {chython-3.3.2 → chython-3.4}/chython/core/_pach3.pxi +0 -0
  141. {chython-3.3.2 → chython-3.4}/chython/core/_query_arena.pxi +0 -0
  142. {chython-3.3.2 → chython-3.4}/chython/core/_query_boxes.pxi +0 -0
  143. {chython-3.3.2 → chython-3.4}/chython/core/_query_container.pxi +0 -0
  144. {chython-3.3.2 → chython-3.4}/chython/core/_query_seal.pxi +0 -0
  145. {chython-3.3.2 → chython-3.4}/chython/core/_rings.pxi +0 -0
  146. {chython-3.3.2 → chython-3.4}/chython/core/_smarts_read.pxi +0 -0
  147. {chython-3.3.2 → chython-3.4}/chython/core/_smiles_read.pxi +0 -0
  148. {chython-3.3.2 → chython-3.4}/chython/core/_smiles_write.pxi +0 -0
  149. {chython-3.3.2 → chython-3.4}/chython/core/_sssr.pxi +0 -0
  150. {chython-3.3.2 → chython-3.4}/chython/core/_stereo.pxi +0 -0
  151. {chython-3.3.2 → chython-3.4}/chython/core/_thiele.pxi +0 -0
  152. {chython-3.3.2 → chython-3.4}/chython/core/_valence.pxi +0 -0
  153. {chython-3.3.2 → chython-3.4}/chython/core/elements.tsv +0 -0
  154. {chython-3.3.2 → chython-3.4}/chython/core/isotopes.tsv +0 -0
  155. {chython-3.3.2 → chython-3.4}/chython/core/reaction.py +0 -0
  156. {chython-3.3.2 → chython-3.4}/chython/core/test/__init__.py +0 -0
  157. {chython-3.3.2 → chython-3.4}/chython/core/test/arena_v4_corpus.bin.gz +0 -0
  158. {chython-3.3.2 → chython-3.4}/chython/core/test/bench_ml.py +0 -0
  159. {chython-3.3.2 → chython-3.4}/chython/core/test/chytorch_oracle.py +0 -0
  160. {chython-3.3.2 → chython-3.4}/chython/core/test/gen_element_tables.py +0 -0
  161. {chython-3.3.2 → chython-3.4}/chython/core/test/gen_modeling_view_corpus.py +0 -0
  162. {chython-3.3.2 → chython-3.4}/chython/core/test/gen_pach3_corpus.py +0 -0
  163. {chython-3.3.2 → chython-3.4}/chython/core/test/gen_reaction_pach_corpus.py +0 -0
  164. {chython-3.3.2 → chython-3.4}/chython/core/test/gen_v3_fixtures.py +0 -0
  165. {chython-3.3.2 → chython-3.4}/chython/core/test/gen_v4_fixtures.py +0 -0
  166. {chython-3.3.2 → chython-3.4}/chython/core/test/gen_valence_rules.py +0 -0
  167. {chython-3.3.2 → chython-3.4}/chython/core/test/modeling_view_corpus.json.gz +0 -0
  168. {chython-3.3.2 → chython-3.4}/chython/core/test/modeling_view_corpus.py +0 -0
  169. {chython-3.3.2 → chython-3.4}/chython/core/test/oracle.py +0 -0
  170. {chython-3.3.2 → chython-3.4}/chython/core/test/pach3_corpus.py +0 -0
  171. {chython-3.3.2 → chython-3.4}/chython/core/test/pach_bond_group_corpus.bin.gz +0 -0
  172. {chython-3.3.2 → chython-3.4}/chython/core/test/pach_corpus.py +0 -0
  173. {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v0_corpus.bin.gz +0 -0
  174. {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v0_native_corpus.bin.gz +0 -0
  175. {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v2_corpus.bin.gz +0 -0
  176. {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v3_corpus.bin.gz +0 -0
  177. {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v4_corpus.bin.gz +0 -0
  178. {chython-3.3.2 → chython-3.4}/chython/core/test/reaction_pach_corpus.py +0 -0
  179. {chython-3.3.2 → chython-3.4}/chython/core/test/reaction_pach_v2_corpus.bin.gz +0 -0
  180. {chython-3.3.2 → chython-3.4}/chython/core/test/test_aggregates.py +0 -0
  181. {chython-3.3.2 → chython-3.4}/chython/core/test/test_alternative_spellings.py +0 -0
  182. {chython-3.3.2 → chython-3.4}/chython/core/test/test_apply_scratch_probe.py +0 -0
  183. {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_f60.py +0 -0
  184. {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_identity.py +0 -0
  185. {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_v3_compat.py +0 -0
  186. {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_v4_compat.py +0 -0
  187. {chython-3.3.2 → chython-3.4}/chython/core/test/test_aromatic_storage.py +0 -0
  188. {chython-3.3.2 → chython-3.4}/chython/core/test/test_canonical.py +0 -0
  189. {chython-3.3.2 → chython-3.4}/chython/core/test/test_canonical_mirror.py +0 -0
  190. {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_assign.py +0 -0
  191. {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_cases.py +0 -0
  192. {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_digraph.py +0 -0
  193. {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_ranking.py +0 -0
  194. {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_storage.py +0 -0
  195. {chython-3.3.2 → chython-3.4}/chython/core/test/test_clean_isotopes_and_coordinate_bonds.py +0 -0
  196. {chython-3.3.2 → chython-3.4}/chython/core/test/test_clean_stereo.py +0 -0
  197. {chython-3.3.2 → chython-3.4}/chython/core/test/test_conformers.py +0 -0
  198. {chython-3.3.2 → chython-3.4}/chython/core/test/test_container_log.py +0 -0
  199. {chython-3.3.2 → chython-3.4}/chython/core/test/test_copy_caches.py +0 -0
  200. {chython-3.3.2 → chython-3.4}/chython/core/test/test_derive.py +0 -0
  201. {chython-3.3.2 → chython-3.4}/chython/core/test/test_descriptors.py +0 -0
  202. {chython-3.3.2 → chython-3.4}/chython/core/test/test_element_tables.py +0 -0
  203. {chython-3.3.2 → chython-3.4}/chython/core/test/test_enrich.py +0 -0
  204. {chython-3.3.2 → chython-3.4}/chython/core/test/test_facade.py +0 -0
  205. {chython-3.3.2 → chython-3.4}/chython/core/test/test_features.py +0 -0
  206. {chython-3.3.2 → chython-3.4}/chython/core/test/test_featurizer_injection.py +0 -0
  207. {chython-3.3.2 → chython-3.4}/chython/core/test/test_fingerprints.py +0 -0
  208. {chython-3.3.2 → chython-3.4}/chython/core/test/test_geometry.py +0 -0
  209. {chython-3.3.2 → chython-3.4}/chython/core/test/test_h_unknown.py +0 -0
  210. {chython-3.3.2 → chython-3.4}/chython/core/test/test_hydrogens.py +0 -0
  211. {chython-3.3.2 → chython-3.4}/chython/core/test/test_inchi.py +0 -0
  212. {chython-3.3.2 → chython-3.4}/chython/core/test/test_interop_injection.py +0 -0
  213. {chython-3.3.2 → chython-3.4}/chython/core/test/test_isomorphism.py +0 -0
  214. {chython-3.3.2 → chython-3.4}/chython/core/test/test_kekule.py +0 -0
  215. {chython-3.3.2 → chython-3.4}/chython/core/test/test_log.py +0 -0
  216. {chython-3.3.2 → chython-3.4}/chython/core/test/test_magic.py +0 -0
  217. {chython-3.3.2 → chython-3.4}/chython/core/test/test_meta.py +0 -0
  218. {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_encoding.py +0 -0
  219. {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_reaction_transition.py +0 -0
  220. {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_state_view.py +0 -0
  221. {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_transition_view.py +0 -0
  222. {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_unpack_differential.py +0 -0
  223. {chython-3.3.2 → chython-3.4}/chython/core/test/test_modeling_view_frozen.py +0 -0
  224. {chython-3.3.2 → chython-3.4}/chython/core/test/test_molecule.py +0 -0
  225. {chython-3.3.2 → chython-3.4}/chython/core/test/test_morgan.py +0 -0
  226. {chython-3.3.2 → chython-3.4}/chython/core/test/test_no_chython_two_imports.py +0 -0
  227. {chython-3.3.2 → chython-3.4}/chython/core/test/test_oracle.py +0 -0
  228. {chython-3.3.2 → chython-3.4}/chython/core/test/test_pach.py +0 -0
  229. {chython-3.3.2 → chython-3.4}/chython/core/test/test_pach3.py +0 -0
  230. {chython-3.3.2 → chython-3.4}/chython/core/test/test_pack.py +0 -0
  231. {chython-3.3.2 → chython-3.4}/chython/core/test/test_query.py +0 -0
  232. {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_edit.py +0 -0
  233. {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_query.py +0 -0
  234. {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_semantics.py +0 -0
  235. {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_serialisation.py +0 -0
  236. {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_smirks.py +0 -0
  237. {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_storage.py +0 -0
  238. {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_container.py +0 -0
  239. {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_identity.py +0 -0
  240. {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_pach.py +0 -0
  241. {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_patch_stereo.py +0 -0
  242. {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_smiles.py +0 -0
  243. {chython-3.3.2 → chython-3.4}/chython/core/test/test_rings.py +0 -0
  244. {chython-3.3.2 → chython-3.4}/chython/core/test/test_rings_c60.py +0 -0
  245. {chython-3.3.2 → chython-3.4}/chython/core/test/test_set_element.py +0 -0
  246. {chython-3.3.2 → chython-3.4}/chython/core/test/test_sgroups.py +0 -0
  247. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_r.py +0 -0
  248. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_read.py +0 -0
  249. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_roundtrip.py +0 -0
  250. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write.py +0 -0
  251. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_aromatic.py +0 -0
  252. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_cis_trans.py +0 -0
  253. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_detached.py +0 -0
  254. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_differential.py +0 -0
  255. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_h_unknown.py +0 -0
  256. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_stereo.py +0 -0
  257. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_sticky.py +0 -0
  258. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_filter.py +0 -0
  259. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_read.py +0 -0
  260. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_report.py +0 -0
  261. {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_stereo.py +0 -0
  262. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_acceptance.py +0 -0
  263. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_anchor_identity.py +0 -0
  264. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_group_storage.py +0 -0
  265. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_owners.py +0 -0
  266. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_parity.py +0 -0
  267. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_perception.py +0 -0
  268. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_units.py +0 -0
  269. {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_v2_differential.py +0 -0
  270. {chython-3.3.2 → chython-3.4}/chython/core/test/test_structure.py +0 -0
  271. {chython-3.3.2 → chython-3.4}/chython/core/test/test_thiele.py +0 -0
  272. {chython-3.3.2 → chython-3.4}/chython/core/test/test_title.py +0 -0
  273. {chython-3.3.2 → chython-3.4}/chython/core/test/test_topology.py +0 -0
  274. {chython-3.3.2 → chython-3.4}/chython/core/test/test_union_stereo.py +0 -0
  275. {chython-3.3.2 → chython-3.4}/chython/core/test/test_valence.py +0 -0
  276. {chython-3.3.2 → chython-3.4}/chython/core/test/test_view_surface.py +0 -0
  277. {chython-3.3.2 → chython-3.4}/chython/core/test/v3_fixtures.py +0 -0
  278. {chython-3.3.2 → chython-3.4}/chython/core/test/v4_fixtures.py +0 -0
  279. {chython-3.3.2 → chython-3.4}/chython/core/valence_rules.tsv +0 -0
  280. {chython-3.3.2 → chython-3.4}/chython/core/wedge.py +0 -0
  281. {chython-3.3.2 → chython-3.4}/chython/depict/__init__.py +0 -0
  282. {chython-3.3.2 → chython-3.4}/chython/depict/_config.py +0 -0
  283. {chython-3.3.2 → chython-3.4}/chython/depict/_hooks.py +0 -0
  284. {chython-3.3.2 → chython-3.4}/chython/depict/bonds.py +0 -0
  285. {chython-3.3.2 → chython-3.4}/chython/depict/colorbar.py +0 -0
  286. {chython-3.3.2 → chython-3.4}/chython/depict/colormap.py +0 -0
  287. {chython-3.3.2 → chython-3.4}/chython/depict/field.py +0 -0
  288. {chython-3.3.2 → chython-3.4}/chython/depict/figure.py +0 -0
  289. {chython-3.3.2 → chython-3.4}/chython/depict/label.py +0 -0
  290. {chython-3.3.2 → chython-3.4}/chython/depict/layout/__init__.py +0 -0
  291. {chython-3.3.2 → chython-3.4}/chython/depict/layout/clean2d.js +0 -0
  292. {chython-3.3.2 → chython-3.4}/chython/depict/layout/molecule.py +0 -0
  293. {chython-3.3.2 → chython-3.4}/chython/depict/layout/reaction.py +0 -0
  294. {chython-3.3.2 → chython-3.4}/chython/depict/metrics/__init__.py +0 -0
  295. {chython-3.3.2 → chython-3.4}/chython/depict/metrics/helvetica.tsv +0 -0
  296. {chython-3.3.2 → chython-3.4}/chython/depict/metrics/times.tsv +0 -0
  297. {chython-3.3.2 → chython-3.4}/chython/depict/overlay.py +0 -0
  298. {chython-3.3.2 → chython-3.4}/chython/depict/render/__init__.py +0 -0
  299. {chython-3.3.2 → chython-3.4}/chython/depict/render/svg.py +0 -0
  300. {chython-3.3.2 → chython-3.4}/chython/depict/scene.py +0 -0
  301. {chython-3.3.2 → chython-3.4}/chython/depict/style.py +0 -0
  302. {chython-3.3.2 → chython-3.4}/chython/depict/test/__init__.py +0 -0
  303. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_bonds.py +0 -0
  304. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_clean2d.py +0 -0
  305. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_colorbar.py +0 -0
  306. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_colormap.py +0 -0
  307. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_field.py +0 -0
  308. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_figure.py +0 -0
  309. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_label.py +0 -0
  310. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_metrics.py +0 -0
  311. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_overlay.py +0 -0
  312. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_package.py +0 -0
  313. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_r_atom.py +0 -0
  314. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_scene.py +0 -0
  315. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_style.py +0 -0
  316. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_svg.py +0 -0
  317. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_wedge_draw.py +0 -0
  318. {chython-3.3.2 → chython-3.4}/chython/depict/test/test_x3dom.py +0 -0
  319. {chython-3.3.2 → chython-3.4}/chython/depict/wedge.py +0 -0
  320. {chython-3.3.2 → chython-3.4}/chython/depict/x3dom.py +0 -0
  321. {chython-3.3.2 → chython-3.4}/chython/exceptions.py +0 -0
  322. {chython-3.3.2 → chython-3.4}/chython/formats/__init__.py +0 -0
  323. {chython-3.3.2 → chython-3.4}/chython/formats/_text.py +0 -0
  324. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/CTFILE.md +0 -0
  325. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/__init__.py +0 -0
  326. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_ctab.py +0 -0
  327. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_errors.py +0 -0
  328. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_facade.py +0 -0
  329. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_hydrogens.py +0 -0
  330. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_rdf.py +0 -0
  331. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_rxn.py +0 -0
  332. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_sdf.py +0 -0
  333. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_sgroup.py +0 -0
  334. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_stream.py +0 -0
  335. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_tokens.py +0 -0
  336. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_v2000.py +0 -0
  337. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_v3000.py +0 -0
  338. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/__init__.py +0 -0
  339. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/conftest.py +0 -0
  340. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_aromatic.py +0 -0
  341. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_container_log.py +0 -0
  342. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_data_labels.py +0 -0
  343. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_facade.py +0 -0
  344. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_fidelity.py +0 -0
  345. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_hydrogens.py +0 -0
  346. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_r_atom.py +0 -0
  347. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_rdf.py +0 -0
  348. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_rxn.py +0 -0
  349. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_sdf.py +0 -0
  350. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_sgroup.py +0 -0
  351. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_stereo_label.py +0 -0
  352. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_stream.py +0 -0
  353. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_tokens.py +0 -0
  354. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_v2000.py +0 -0
  355. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_v3000.py +0 -0
  356. {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_wedge.py +0 -0
  357. {chython-3.3.2 → chython-3.4}/chython/formats/mol2.py +0 -0
  358. {chython-3.3.2 → chython-3.4}/chython/formats/pdb/__init__.py +0 -0
  359. {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_builder.py +0 -0
  360. {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_legacy.py +0 -0
  361. {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_mmcif.py +0 -0
  362. {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_records.py +0 -0
  363. {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_star.py +0 -0
  364. {chython-3.3.2 → chython-3.4}/chython/formats/test/__init__.py +0 -0
  365. {chython-3.3.2 → chython-3.4}/chython/formats/test/conftest.py +0 -0
  366. {chython-3.3.2 → chython-3.4}/chython/formats/test/oracles.py +0 -0
  367. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_conformance.py +0 -0
  368. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_done_when.py +0 -0
  369. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_isolation.py +0 -0
  370. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_log_prefix.py +0 -0
  371. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_mmcif.py +0 -0
  372. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_mol2.py +0 -0
  373. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_no_review_bookkeeping.py +0 -0
  374. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_oracles.py +0 -0
  375. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_pdb.py +0 -0
  376. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_pdb_builder.py +0 -0
  377. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_perceive_agreement.py +0 -0
  378. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_read_only_facades.py +0 -0
  379. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_xyz.py +0 -0
  380. {chython-3.3.2 → chython-3.4}/chython/formats/test/test_xyz_builder.py +0 -0
  381. {chython-3.3.2 → chython-3.4}/chython/formats/xml/__init__.py +0 -0
  382. {chython-3.3.2 → chython-3.4}/chython/formats/xml/_cml.py +0 -0
  383. {chython-3.3.2 → chython-3.4}/chython/formats/xml/_dialect.py +0 -0
  384. {chython-3.3.2 → chython-3.4}/chython/formats/xml/_errors.py +0 -0
  385. {chython-3.3.2 → chython-3.4}/chython/formats/xml/_facade.py +0 -0
  386. {chython-3.3.2 → chython-3.4}/chython/formats/xml/_mrv.py +0 -0
  387. {chython-3.3.2 → chython-3.4}/chython/formats/xml/_tree.py +0 -0
  388. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/__init__.py +0 -0
  389. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/conftest.py +0 -0
  390. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_cml.py +0 -0
  391. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_container_log.py +0 -0
  392. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_dialect.py +0 -0
  393. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_equivalence.py +0 -0
  394. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_facade.py +0 -0
  395. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_mrv.py +0 -0
  396. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_mrv_census.py +0 -0
  397. {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_tree.py +0 -0
  398. {chython-3.3.2 → chython-3.4}/chython/formats/xyz.py +0 -0
  399. {chython-3.3.2 → chython-3.4}/chython/interop/__init__.py +0 -0
  400. {chython-3.3.2 → chython-3.4}/chython/interop/_cdk.py +0 -0
  401. {chython-3.3.2 → chython-3.4}/chython/interop/_cdpkit.py +0 -0
  402. {chython-3.3.2 → chython-3.4}/chython/interop/_indigo.py +0 -0
  403. {chython-3.3.2 → chython-3.4}/chython/interop/_iupac.py +0 -0
  404. {chython-3.3.2 → chython-3.4}/chython/interop/_java.py +0 -0
  405. {chython-3.3.2 → chython-3.4}/chython/interop/_openbabel.py +0 -0
  406. {chython-3.3.2 → chython-3.4}/chython/interop/_pandas.py +0 -0
  407. {chython-3.3.2 → chython-3.4}/chython/interop/_rdkit.py +0 -0
  408. {chython-3.3.2 → chython-3.4}/chython/interop/_records.py +0 -0
  409. {chython-3.3.2 → chython-3.4}/chython/interop/_stereo.py +0 -0
  410. {chython-3.3.2 → chython-3.4}/chython/interop/config.py +0 -0
  411. {chython-3.3.2 → chython-3.4}/chython/interop/conformers.py +0 -0
  412. {chython-3.3.2 → chython-3.4}/chython/interop/test/__init__.py +0 -0
  413. {chython-3.3.2 → chython-3.4}/chython/interop/test/conftest.py +0 -0
  414. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_cdk.py +0 -0
  415. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_cdpkit.py +0 -0
  416. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_config.py +0 -0
  417. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_conformers.py +0 -0
  418. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_coordinate_honesty.py +0 -0
  419. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_dispatch.py +0 -0
  420. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_indigo.py +0 -0
  421. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_iupac.py +0 -0
  422. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_log_delivery.py +0 -0
  423. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_openbabel.py +0 -0
  424. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_pandas.py +0 -0
  425. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_rdkit.py +0 -0
  426. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_stereo.py +0 -0
  427. {chython-3.3.2 → chython-3.4}/chython/interop/test/test_v2_oracle.py +0 -0
  428. {chython-3.3.2 → chython-3.4}/chython/reactions/__init__.py +0 -0
  429. {chython-3.3.2 → chython-3.4}/chython/reactions/_numbering.py +0 -0
  430. {chython-3.3.2 → chython-3.4}/chython/reactions/attention/__init__.py +0 -0
  431. {chython-3.3.2 → chython-3.4}/chython/reactions/attention/_assign.py +0 -0
  432. {chython-3.3.2 → chython-3.4}/chython/reactions/attention/_encode.py +0 -0
  433. {chython-3.3.2 → chython-3.4}/chython/reactions/attention/_session.py +0 -0
  434. {chython-3.3.2 → chython-3.4}/chython/reactions/test/__init__.py +0 -0
  435. {chython-3.3.2 → chython-3.4}/chython/reactions/test/gen_corpus_glossary.py +0 -0
  436. {chython-3.3.2 → chython-3.4}/chython/reactions/test/golden_subset.smi +0 -0
  437. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention.py +0 -0
  438. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention_assign.py +0 -0
  439. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention_encode.py +0 -0
  440. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention_isolation.py +0 -0
  441. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_corpus_glossary.py +0 -0
  442. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_dependency_direction.py +0 -0
  443. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_functional.py +0 -0
  444. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_id_stability.py +0 -0
  445. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_numbering.py +0 -0
  446. {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_probes.py +0 -0
  447. {chython-3.3.2 → chython-3.4}/chython/test/__init__.py +0 -0
  448. {chython-3.3.2 → chython-3.4}/chython/test/test_code_hygiene.py +0 -0
  449. {chython-3.3.2 → chython-3.4}/chython/test/test_container_methods.py +0 -0
  450. {chython-3.3.2 → chython-3.4}/chython/test/test_doc_figures.py +0 -0
  451. {chython-3.3.2 → chython-3.4}/chython/test/test_doc_references.py +0 -0
  452. {chython-3.3.2 → chython-3.4}/chython/test/test_doc_samples.py +0 -0
  453. {chython-3.3.2 → chython-3.4}/chython/test/test_hydrogen_parity.py +0 -0
  454. {chython-3.3.2 → chython-3.4}/chython/test/test_libinchi_staging.py +0 -0
  455. {chython-3.3.2 → chython-3.4}/chython/test/test_log_records.py +0 -0
  456. {chython-3.3.2 → chython-3.4}/chython/test/test_optional_numpy.py +0 -0
  457. {chython-3.3.2 → chython-3.4}/chython/test/test_packaging.py +0 -0
  458. {chython-3.3.2 → chython-3.4}/chython/test/test_performance.py +0 -0
  459. {chython-3.3.2 → chython-3.4}/chython/test/test_r_atom_integration.py +0 -0
  460. {chython-3.3.2 → chython-3.4}/chython/test/test_release_build.py +0 -0
  461. {chython-3.3.2 → chython-3.4}/chython/test/test_stereo_bluebook.py +0 -0
  462. {chython-3.3.2 → chython-3.4}/chython/test/test_v2_boundary.py +0 -0
  463. {chython-3.3.2 → chython-3.4}/chython/test/test_writer_posture.py +0 -0
  464. {chython-3.3.2 → chython-3.4}/chython.egg-info/SOURCES.txt +0 -0
  465. {chython-3.3.2 → chython-3.4}/chython.egg-info/dependency_links.txt +0 -0
  466. {chython-3.3.2 → chython-3.4}/chython.egg-info/requires.txt +0 -0
  467. {chython-3.3.2 → chython-3.4}/chython.egg-info/top_level.txt +0 -0
  468. {chython-3.3.2 → chython-3.4}/setup.cfg +0 -0
  469. {chython-3.3.2 → chython-3.4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chython
3
- Version: 3.3.2
3
+ Version: 3.4
4
4
  Summary: Library for processing molecules and reactions in python way
5
5
  Author-email: Ramil Nugmanov <nougmanoff@protonmail.com>
6
6
  License-Expression: LGPL-3.0-or-later
@@ -48,6 +48,43 @@ _RULE_ROUNDS = 'canonicalize:rounds'
48
48
  _ROUNDS_MAX = 5
49
49
 
50
50
 
51
+ def _pin_lactim(molecule: MoleculeContainer) -> bool:
52
+ """Kekulise `molecule` with the C=N of one aromatic lactim pinned, when that lets a row repair it.
53
+
54
+ A site is `[O,S;D1]-c:n` with a bare two-connected ring nitrogen. Sites are tried in `atoms_order`,
55
+ which the placement stage has already made spelling-independent, and a site is taken only when a
56
+ trial `standardize()` on the pinned form turns its C-OH into C=O -- the rows stay the only authority
57
+ on which lactims are lactams. Returns True with the molecule kekulised and the repair still to run.
58
+ """
59
+ ranks = molecule.atoms_order
60
+ sites = []
61
+ for o in molecule.atom_numbers:
62
+ if molecule.element_of(o) not in (8, 16) or molecule.charge_of(o) or molecule.implicit_h_of(o) != 1:
63
+ continue
64
+ neighbors = tuple(molecule.neighbors_of(o))
65
+ if len(neighbors) != 1 or molecule.order_of(o, neighbors[0]) != 1:
66
+ continue
67
+ c = neighbors[0]
68
+ for n in molecule.neighbors_of(c):
69
+ if (molecule.element_of(n) == 7 and molecule.order_of(c, n) == 4 and not molecule.charge_of(n)
70
+ and not molecule.implicit_h_of(n) and len(tuple(molecule.neighbors_of(n))) == 2):
71
+ sites.append((ranks[o], ranks[n], o, c, n))
72
+ for *_, o, c, n in sorted(sites):
73
+ trial = molecule.copy()
74
+ with trial.edit():
75
+ trial.set_order(c, n, 2)
76
+ if trial.kekule().unresolved or trial.order_of(c, n) != 2:
77
+ continue
78
+ standardize(trial)
79
+ if trial.order_of(o, c) != 2:
80
+ continue
81
+ with molecule.edit():
82
+ molecule.set_order(c, n, 2)
83
+ molecule.kekule()
84
+ return True
85
+ return False
86
+
87
+
51
88
  def canonicalize(molecule: MoleculeContainer, *, fix_tautomers: bool = True,
52
89
  keep_kekule: bool = False) -> bool:
53
90
  """Bring `molecule` to the representation two drawings of one compound share. Did it change?
@@ -149,25 +186,32 @@ def canonicalize(molecule: MoleculeContainer, *, fix_tautomers: bool = True,
149
186
  #
150
187
  # `kekule()` leads, and not for the reason step 1 does: the `tautomer` rows are written against
151
188
  # definite bond orders, so on the aromatic form step 7 left behind they match nothing at all and
152
- # re-running step 3 would be a guaranteed no-op. Step 5 is re-entered only behind a repair,
153
- # which is the only thing that can hand it a charged site it has not already seen. Step 6 rides
189
+ # re-running step 3 would be a guaranteed no-op. Step 5 is re-entered every round: a repair and
190
+ # the placement both hand it charged sites it has not seen -- the placement moves an N-oxide's
191
+ # charge onto an azole NH, `c1ccc2[nH]cnc2[n+]1[O-]` to `c1ccc2nc[nH+]c2n1[O-]`, a zwitterion
192
+ # step 5 pairs off -- and what it pairs off can unblock the placement again. It cannot cycle:
193
+ # step 5 only ever reduces the number of charged atoms and step 8 never adds one. Step 6 rides
154
194
  # with the aromatisation and not with the repair: the kekulisation above is free to come back with
155
195
  # a different Kekule form than the one it was handed, which is the form step 7 would then read.
156
196
  # Step 2 is not re-entered: the parities it can justify are a property of the constitution, which
157
197
  # no stage from here on changes.
198
+ #
199
+ # A round the placement did not ask for is opened by a lactim `kekule()` left unrepairable: the
200
+ # hydroxy-azine rows need the C=N beside the C-OH, and which Kekule form comes back depends on the
201
+ # atom order. `_pin_lactim` pins that C=N and kekulises around it, so 3-hydroxyisoquinoline
202
+ # reaches isoquinolin-3(2H)-one from every order.
158
203
  for _ in range(_ROUNDS_MAX):
159
- if not moved:
204
+ if moved:
205
+ molecule.kekule()
206
+ elif not (fix_tautomers and _pin_lactim(molecule)):
160
207
  break
161
- molecule.kekule()
162
208
  changed = standardize(molecule, fix_tautomers=fix_tautomers)
163
209
  if changed:
164
210
  implicify_hydrogens(molecule)
165
- neutralize(molecule)
211
+ changed |= neutralize(molecule)
166
212
  standardize_kekule(molecule)
167
213
  molecule.thiele() # unconditional: step 6's form is what a caller compares, and
168
- if not changed: # the kekulisation above has to be undone either way
169
- break
170
- moved = standardize_isomers(molecule)
214
+ moved = changed and standardize_isomers(molecule) # the kekulisation has to be undone either way
171
215
  else:
172
216
  with recording(molecule, stage='canonicalize') as log:
173
217
  log.append(LogRecord(_RULE_ROUNDS, (), f'repair and placement were still changing the '
@@ -217,8 +217,9 @@ def _deal(sites: tuple[int, ...], states: tuple[tuple, ...]):
217
217
 
218
218
 
219
219
  def _admissible(molecule: MoleculeContainer, group: list[int],
220
- placement: dict[int, tuple[int, int]]) -> bool:
221
- """Does this placement give a Kekule form for this group? The kekuliser is the oracle.
220
+ placement: dict[int, tuple[int, int]]) -> int | None:
221
+ """How many bonds `thiele()` writes aromatic under this placement, or `None` when it gives no Kekule
222
+ form for this group. The kekuliser is the oracle.
222
223
 
223
224
  This group's systems and not the whole molecule's: one ring nobody can kekulise (`c1cccc1`) would
224
225
  otherwise make every placement of every other ring inadmissible.
@@ -238,17 +239,32 @@ def _admissible(molecule: MoleculeContainer, group: list[int],
238
239
  work.set_hydrogens(n, hydrogens)
239
240
  members = frozenset(group)
240
241
  if any(members.intersection(system) for system in work.kekule().unresolved):
241
- return False
242
- return all((work.implicit_h_of(n), work.charge_of(n)) == state for n, state in placement.items())
242
+ return None
243
+ if any((work.implicit_h_of(n), work.charge_of(n)) != state for n, state in placement.items()):
244
+ return None
245
+ work.thiele()
246
+ return work.aromatic_bond_count
243
247
 
244
248
 
245
249
  def _choose(molecule: MoleculeContainer, group: list[int], ranks: dict[int, int]):
246
250
  """The canonical placement for one group, `None` when nothing must move, `'budget'` when too big.
247
251
 
248
252
  The hydrogens and the charges are read off the group rather than assumed, and dealt back over it. The
249
- key is the sorted ranks of the sites holding the hydrogens, then of those holding each charge in turn
250
- -- a strict total order, because `atoms_order` is a permutation and those two sets fix the placement,
251
- so no two placements share a key and no tie is left for an arbitrary rule to break.
253
+ key is the aromatic bond count `thiele()` gives the placement, most first, then the hydrogens beside a
254
+ C=O or C=S carbon, then the hydrogens on five-membered rings, each most first, then the sorted ranks
255
+ of the sites holding the hydrogens, then of those holding each charge in turn -- a strict total order,
256
+ because `atoms_order` is a permutation and those two sets fix the placement, so no two placements
257
+ share a key and no tie is left for an arbitrary rule to break.
258
+
259
+ A lactam N-H sits next to its carbonyl: 6-methylpyrimidin-4(3H)-one, not its 1H form. An azole N-H
260
+ outranks an azine N-H whenever both forms are equally aromatic, so every 7-azaindole reads 1H
261
+ (`c1cnc2[nH]ccc2c1`), its carboxylic acid too. Ring size and bond order are graph facts, so both
262
+ terms are as spelling-independent as the ranks after them.
263
+
264
+ Aromaticity leads because a fused system spelled aromatic joins rings a hydrogen cannot cross for
265
+ free: pyrido[4,3-d]pyrimidine-2,4-dione `O=C1NC(=O)c2cnccc2N1` with its N1-H moved onto the pyridine
266
+ N kekulises, as a quinoid imine with no pyridine sextet. Only nitrogen sites deal here and `thiele()`
267
+ leaves a lactam non-aromatic, so the term never weighs an enol against its ketone.
252
268
 
253
269
  A hydrogen deals over the sites with two heavy neighbours and a charge over all of them, which is the
254
270
  whole difference a substituted nitrogen makes. That much is not left to the oracle: `kekule()` decides
@@ -268,16 +284,22 @@ def _choose(molecule: MoleculeContainer, group: list[int], ranks: dict[int, int]
268
284
  if trials == 1:
269
285
  return None # one way to deal them: there is no distribution to choose
270
286
  signs = [q for q in sorted(charges) if q]
287
+ azole = {n for n in carriers if 5 in molecule.ring_sizes_of(n)}
288
+ lactam = {n for n in carriers
289
+ if any(any(molecule.order_of(c, x) == 2 and molecule.element_of(x) in _DONOR
290
+ for x in molecule.neighbors_of(c)) for c in molecule.neighbors_of(n))}
271
291
  current = {n: (molecule.implicit_h_of(n) or 0, molecule.charge_of(n)) for n in group}
272
292
  best = None
273
293
  for protonated in combinations(carriers, hydrogens):
274
294
  for dealt in _deal(tuple(group), order):
275
295
  placement = {n: (1 if n in protonated else 0, dealt[n]) for n in group}
276
- key = [sorted(ranks[n] for n in protonated)]
296
+ aromatic = _admissible(molecule, group, placement)
297
+ if aromatic is None:
298
+ continue
299
+ key = [[-aromatic, -sum(n in lactam for n in protonated), -sum(n in azole for n in protonated)],
300
+ sorted(ranks[n] for n in protonated)]
277
301
  key.extend(sorted(ranks[n] for n in group if dealt[n] == q) for q in signs)
278
- if best is not None and key >= best[0]:
279
- continue # cheaper than the oracle, so it goes first
280
- if _admissible(molecule, group, placement):
302
+ if best is None or key < best[0]:
281
303
  best = (key, placement)
282
304
  if best is None or best[1] == current:
283
305
  return None
@@ -66,6 +66,11 @@ def _admitted(molecule: MoleculeContainer, candidates: dict[int, AcidRow], delta
66
66
  f'atom {n} has no derivable implicit hydrogen count, so a proton '
67
67
  f'cannot be counted off or onto it', REFUSED))
68
68
  continue
69
+ if hydrogens + delta < 0:
70
+ lines.append(LogRecord(row.id, (n,),
71
+ f'atom {n} holds its proton as a drawn hydrogen atom, and this pass '
72
+ f'writes implicit counts only', REFUSED))
73
+ continue
69
74
  order_sum, environment, aromatic = environment_of(molecule, n)
70
75
  # an aromatic bond has no valence row, so the question cannot be put -- `check_valence` calls
71
76
  # that `unknown` rather than a violation, and a pyridinium must stay deprotonatable.
@@ -101,8 +106,9 @@ def neutralize(molecule: MoleculeContainer, *, keep_charge: bool = True) -> bool
101
106
  nitrate past zero. `keep_charge=False` lets a site act alone, as far as its own component's charge
102
107
  allows: `C[NH3+]` alone becomes `CN`.
103
108
 
104
- Sites are found by `tables/acids.tsv`, whose `h` primitive reads IMPLICIT hydrogens, so a molecule
105
- carrying explicit hydrogen atoms wants `implicify_hydrogens()` first. A site whose hydrogen count
109
+ Sites are found by `tables/acids.tsv`, whose `h` primitive counts drawn hydrogen atoms too, but a
110
+ proton is moved only off an implicit count, so a molecule carrying explicit hydrogen atoms wants
111
+ `implicify_hydrogens()` first. A site whose hydrogen count
106
112
  is not derivable, and one whose neutral form no valence row accepts, is refused and logged.
107
113
  """
108
114
  lines: list[LogRecord] = []
@@ -14,8 +14,8 @@
14
14
  # pattern matching nothing is invisible: the pass just stops recognizing that site.
15
15
  # comment what the row claims. Documentation only.
16
16
  #
17
- # `h` COUNTS IMPLICIT HYDROGENS ONLY, so an `acid` row does not see `[N+]([H])([H])[H]` drawn with three
18
- # hydrogen ATOMS. `implicify_hydrogens()` first; the pass says so in its docstring.
17
+ # `h` COUNTS DRAWN HYDROGENS TOO, but a proton moves only off an implicit count, so `[N+]([H])([H])[H]`
18
+ # drawn with three hydrogen ATOMS is refused. `implicify_hydrogens()` first; the pass says so in its docstring.
19
19
  #
20
20
  # AN UNSTATED CHARGE IS NEUTRAL, not "any charge", which is why nitrate has a row of its own: the anchor
21
21
  # oxygen's neighbour is `[N+]`, and `acids:alkoxide`'s `[C,N]` admits the neutral nitrogen alone.
@@ -87,7 +87,7 @@ groups:05 [B;z1]-[O,S;D3;z1] - 1:2:8 0 - BS(C)C>>[BH3]~[S](C)C A three-coordinat
87
87
  groups:06 [B;z2;-]=[N;D1,D2,D3;z2;+] 1:1:-;2:-1:- 1:2:1 0 - [B-]=[N+]>>BN A borane adduct drawn as the ylide [B-]=[N+]; the two charges cancel and the bond is an ordinary single one
88
88
  groups:07 [B;D4;z1;+3]([A;-])([A;-])([A;-])[A;-] 1:-4:-;2:1:-;3:1:-;4:1:-;5:1:- - 0 - [O-][B+3]([O-])([O-])[O-]>>[B-](O)(O)(O)O A borate drawn with the whole count of charges on the boron and an anion on every ligand as well; the boron is [B-] and the four ligands are neutral
89
89
  groups:08 [B;D4;z1]-[A;-] 1:-1:-;2:1:- - 0 - [O-]B(O)(O)O>>[B-](O)(O)(O)O A four-coordinate boron carrying an anionic ligand holds the charge itself; the negative charge moves from the ligand to the boron
90
- groups:09 [B;D4;z1] 1:-1:- - 0 groups:00;groups:08 OB(O)(O)O>>[B-](O)(O)(O)O Four single bonds on a boron is a borate; the drawing left it neutral, so the charge is added
90
+ groups:09 [B;D4;z1] 1:-1:- - 0 groups:08 OB(O)(O)O>>[B-](O)(O)(O)O Four single bonds on a boron is a borate; the drawing left it neutral, so the charge is added
91
91
  groups:10 [P&D4&x0&z1,N&D4&z1] 1:1:- - 0 - CP(C)(C)C>>C[P+](C)(C)C;CN(C)(C)C>>C[N+](C)(C)C Four single bonds on a phosphorus with no heteroatom neighbour, or on a nitrogen, is a phosphonium or a quaternary ammonium; the drawing left it neutral, so the charge is added and the phantom hydrogen goes with it
92
92
  groups:11 [N;D3;z5;x2](=[O;D1])([O;D1])=C 1:1:-;3:-1:- 1:4:1 1 - C=N(=O)O>>C[N+]([O-])=O A nitro group drawn pentavalent in its aci form, C=N(=O)OH; the nitro becomes charge-separated and the acidic hydrogen moves from oxygen to carbon
93
93
  groups:12 [N;D3;z5](=[O;D1])(=[C,N,O])-[A] 1:1:-;2:-1:- 1:2:1 0 groups:11 C=N(C)=O>>C=[N+](C)[O-] A pentavalent nitrogen carrying an N=O and a second double bond; the N=O is separated into [N+]-[O-] and the other double bond is left as drawn
@@ -228,6 +228,21 @@ def test_the_pipeline_is_a_fixed_point_and_not_merely_ordered():
228
228
  assert m.canonical_bytes == first, f'{label}: {string} is not a fixed point'
229
229
 
230
230
 
231
+ def test_a_placement_that_makes_a_zwitterion_is_neutralized():
232
+ """Step 8 can hand step 5 a charged site: it moves an N-oxide's charge onto the azole NH.
233
+
234
+ Imidazo[4,5-b]pyridine 4-oxide and its N-hydroxy tautomer are one compound, and each drawing is a
235
+ fixed point -- the zwitterion step 8 leaves behind is not.
236
+ """
237
+ for oxide, hydroxy in (('c1ccc2[nH]cnc2[n+]1[O-]', 'c1ccc2ncnc2n1O'),
238
+ ('c1[n+]([O-])c2[nH]cnc2c([N+](=O)[O-])c1', 'c1n(O)c2ncnc2c([N+](=O)[O-])c1')):
239
+ a, b = smiles(oxide), smiles(hydroxy)
240
+ a.canonicalize()
241
+ b.canonicalize()
242
+ assert a == b, f'{oxide} and {hydroxy} keep two keys: {a} vs {b}'
243
+ assert a.canonicalize() is False, f'{oxide} still moved on a second pass'
244
+
245
+
231
246
  def test_the_extra_round_costs_no_duplicate_repair_record():
232
247
  """A repair is reported once however many rounds saw the molecule.
233
248
 
@@ -604,3 +619,27 @@ def test_nothing_to_do_is_zero_and_touches_nothing():
604
619
  assert explicify_hydrogens(m) == 0, s
605
620
  assert len(m) == n
606
621
  assert not [r for r in log if r.rule == 'hydrogens:explicify']
622
+
623
+
624
+ LACTIMS = ['Oc1cc2ccccc2cn1', # 3-hydroxyisoquinoline
625
+ 'Cc1ncc2cnc(O)c(-c3ccccc3)c2n1', # a fused hydroxypyridine beside a pyrimidine
626
+ 'Oc1ncnc2ccccc12', # quinazolin-4-ol
627
+ 'Cc1cc(O)nc(C)n1'] # 2,6-dimethylpyrimidin-4-ol
628
+
629
+
630
+ def test_a_lactim_reaches_its_lactam_from_every_atom_order():
631
+ """One key per compound, whichever Kekule form `kekule()` happened to return, with the NH beside
632
+ the C=O."""
633
+ import random
634
+ random.seed(0)
635
+ for lactim in LACTIMS:
636
+ keys = set()
637
+ for _ in range(40):
638
+ molecule = smiles(format(smiles(lactim), 'r'))
639
+ molecule.canonicalize()
640
+ keys.add(molecule.canonical_bytes)
641
+ assert len(keys) == 1, lactim
642
+ assert not any(molecule.element_of(n) == 8 and molecule.implicit_h_of(n) for n in molecule.atom_numbers)
643
+ nh = [n for n in molecule.atom_numbers if molecule.element_of(n) == 7 and molecule.implicit_h_of(n)]
644
+ assert [any(molecule.element_of(x) == 8 for c in molecule.neighbors_of(n) for x in molecule.neighbors_of(c))
645
+ for n in nh] == [True], f'{lactim}: {molecule}'
@@ -192,6 +192,43 @@ def test_two_kekule_tautomers_of_one_lactam_store_the_same_molecule():
192
192
  f'{label}: {a} and {b} still differ, {ma} vs {mb}'
193
193
 
194
194
 
195
+ AROMATIC_PAIRS = [
196
+ ('O=C1NC(=O)c2cnccc2N1', 'O=C1NC(=O)C2=CNC=CC2=N1', 'pyrido[4,3-d]pyrimidine-2,4-dione'),
197
+ ('O=C1Nc2cc3ncccc3cc2N1', 'O=C1N=C2C=C3C(=CC=CN3)C=C2N1', 'imidazo[4,5-g]quinolin-2-one'),
198
+ ]
199
+
200
+
201
+ def test_a_fused_lactam_keeps_its_pyridine_aromatic():
202
+ """A hydrogen on a lactam nitrogen does not move onto a fused pyridine nitrogen, whichever form arrives.
203
+
204
+ Both placements kekulise, so the canonical order alone would take whichever ranks lower; the imine
205
+ leaves the pyridine without a sextet and is the form that loses.
206
+ """
207
+ for aromatic, quinoid, label in AROMATIC_PAIRS:
208
+ ma, mb = read_smiles(aromatic), read_smiles(quinoid)
209
+ canonicalize(ma)
210
+ canonicalize(mb)
211
+ assert ma.aromatic_rings_count == read_smiles(aromatic).aromatic_rings_count, f'{label}: {ma}'
212
+ assert ma.canonical_bytes == mb.canonical_bytes, f'{label}: {ma} vs {mb}'
213
+
214
+
215
+ AZOLE_FIRST = [('c1cnc2[nH]ccc2c1', 'C1=CNC2=NC=CC2=C1', '7-azaindole'),
216
+ ('OC(=O)c1ccc2cc[nH]c2n1', 'OC(=O)C1=CC=C2C=CN=C2N1', '7-azaindole-6-carboxylic acid'),
217
+ ('NC(=O)c1ccc2cc[nH]c2n1', 'NC(=O)C1=CC=C2C=CN=C2N1', '7-azaindole-6-carboxamide')]
218
+
219
+
220
+ def test_an_azole_nh_outranks_an_azine_nh():
221
+ """Both forms are aromatic, so the ring size decides: the hydrogen sits on the five-membered ring."""
222
+ for azole, azine, label in AZOLE_FIRST:
223
+ ma, mb = read_smiles(azole), read_smiles(azine)
224
+ canonicalize(ma)
225
+ canonicalize(mb)
226
+ assert ma.canonical_bytes == mb.canonical_bytes, f'{label}: {ma} vs {mb}'
227
+ nh = [n for n in ma.atom_numbers if ma.element_of(n) == 7 and ma.total_h_of(n)
228
+ and len(tuple(ma.neighbors_of(n))) == 2]
229
+ assert [5 in ma.ring_sizes_of(n) for n in nh] == [True], f'{label}: {ma}'
230
+
231
+
195
232
  def test_a_kekule_placement_never_changes_the_formula():
196
233
  """The working copy is spelled aromatic and kekulised back, and `kekule()` repairs when it must.
197
234
 
@@ -175,8 +175,8 @@ def test_a_site_taken_past_zero_alone_is_refused():
175
175
 
176
176
 
177
177
  def test_explicit_hydrogens_hide_the_site():
178
- """`acids.tsv` reads IMPLICIT hydrogens, so a cation drawn with hydrogen atoms is invisible until
179
- they are folded in. The docstring says `implicify_hydrogens()` first, and this is why."""
178
+ """A proton is moved only off an implicit count, so a cation drawn with hydrogen atoms is refused
179
+ until they are folded in. The docstring says `implicify_hydrogens()` first, and this is why."""
180
180
  m = smiles('[H][N+]([H])([H])C.[Cl-]')
181
181
  assert neutralize(m) is False
182
182
  implicify_hydrogens(m)
@@ -19,7 +19,7 @@
19
19
  """Asking for the aromatic spelling must not silently choose a tautomer.
20
20
 
21
21
  `kekule()` repairs, because it is the boundary where input arrives; `thiele()` must not, because by
22
- then the data is the library's own arena. The hydrogen move chython 2 did inside `thiele` is owed to
22
+ then the data is the library's own arena. The hydrogen move chython 2 did inside `thiele` belongs to
23
23
  `standardize_isomers` instead. Both halves are pinned here.
24
24
  """
25
25
  import pytest
@@ -96,16 +96,9 @@ for line in sys.stdin:
96
96
  assert on_smiles != off_smiles
97
97
 
98
98
 
99
- @pytest.mark.xfail(strict=True, reason='owed: standardize_isomers has not been ported yet, so the '
100
- 'hydrogen move chython 2 performed inside thiele is '
101
- 'currently unavailable anywhere. Delete this marker when '
102
- 'the isomer pass lands -- a strict xfail is what makes that '
103
- 'a required edit rather than an optional one')
104
- def test_the_hydrogen_move_is_owed_to_standardize_isomers():
105
- """What the split costs until the isomer pass exists, written down as a failing test.
106
-
107
- The end state is the one chython 2 reached by combining the two jobs, and is what the ported rule
108
- must reproduce: the hydrogen on atom 5, not atom 1.
99
+ def test_the_hydrogen_move_belongs_to_standardize_isomers():
100
+ """The hydrogen move chython 2 made inside `thiele` is `standardize_isomers`' own: an azole N-H
101
+ outranks an azine N-H, so the hydrogen goes to atom 5, not atom 1.
109
102
  """
110
103
  molecule = read_smiles(SHIFTED)
111
104
  molecule.thiele()
@@ -245,6 +245,9 @@ cdef int fill_features(Structure structure) except -1:
245
245
  # `rebuild_derived` derives degree as the CSR row length, `_pach.pxi` writes it as that
246
246
  # row length, and `_stereo.pxi` reads it as connectivity. Two different facts, so two
247
247
  # counts -- neither is a stale copy of the other, and each is derived in one place only.
248
+ #
249
+ # A hydrogen ATOM of any isotope is not a `D` neighbour: `[2H]C([2H])(C)O` is a primary
250
+ # alcohol, `[C;D2]`, exactly as `C(C)O` is. It counts towards `h` below instead.
248
251
  w1 = w0_element_bits(element)
249
252
  degree = 0
250
253
  heteroatoms = 0
@@ -252,8 +255,10 @@ cdef int fill_features(Structure structure) except -1:
252
255
  w1 |= w0_bond_bits(&edges[k])
253
256
  if edges[k].order == 8:
254
257
  continue
255
- degree += 1
256
258
  nb_element = atoms[edges[k].to].element
259
+ if nb_element == 1:
260
+ continue
261
+ degree += 1
257
262
  if element_is_heteroatom(nb_element):
258
263
  heteroatoms += 1
259
264
 
@@ -299,8 +304,10 @@ cdef int fill_features(Structure structure) except -1:
299
304
  # the one function-body forward reference in the core.
300
305
  w3 |= SPAN_MASK[SPAN_IMPLICIT_H] | SPAN_MASK[SPAN_TOTAL_H]
301
306
  else:
307
+ # `h` is every attached hydrogen, implicit or drawn, so a deuterium answers `h` as the
308
+ # protium `canonicalize()` would have folded into the count. `H` is the same sum.
302
309
  th = ih + eh
303
- w3 |= <uint64_t> 1 << (17 + _bit_of(<int32_t> ih, 0, 4))
310
+ w3 |= <uint64_t> 1 << (17 + _bit_of(<int32_t> th, 0, 4))
304
311
  w3 |= <uint64_t> 1 << (27 + _bit_of(<int32_t> th, 0, 5))
305
312
  w3 |= <uint64_t> 1 << (33 + _bit_of(a.charge, -4, 8))
306
313
  if a.isotope:
@@ -354,10 +354,12 @@ def reaction_reset_mapping(rxn) -> bool:
354
354
  def _side_states(molecules):
355
355
  """`({map number: state}, {colliding map numbers})` for one side.
356
356
 
357
- A state is `(element, charge, radical, implicit h, {neighbour map number: order})` -- everything a
358
- CGR's dynamic atom and dynamic bond carried between them, which is what the reaction centre is
359
- defined against. Bonds to an unmapped atom are left out: they cannot be compared across the arrow
360
- because there is nothing to compare them to.
357
+ A state is `(element, charge, radical, implicit h, {neighbour map number: order}, unmapped branches)`
358
+ -- everything a CGR's dynamic atom and dynamic bond carried between them, which is what the reaction
359
+ centre is defined against. An unmapped neighbour has no identity across the arrow, so it enters as a
360
+ description: the canonical SMILES of the unmapped region it belongs to, rooted at the mapped atom by
361
+ its map number. `[C:1](=[O:2])OC >> [C:1](=[O:2])O` changes atom 1's branch from `[C:1]OC` to
362
+ `[C:1]O`; an unmapped `OH` drawn on both sides is the same branch twice and changes nothing.
361
363
  """
362
364
  states = {}
363
365
  collisions = set()
@@ -371,23 +373,42 @@ def _side_states(molecules):
371
373
  if mn in states:
372
374
  collisions.add(mn)
373
375
  local[atom.n] = mn
374
- states[mn] = (atom.element, atom.charge, atom.is_radical, atom.implicit_h, {})
376
+ states[mn] = (atom.element, atom.charge, atom.is_radical, atom.implicit_h, {}, [])
375
377
  for bond in molecule.bonds():
376
378
  mn, mm = local.get(bond.n), local.get(bond.m)
377
- if mn is None or mm is None:
378
- continue
379
- states[mn][4][mm] = bond.order
380
- states[mm][4][mn] = bond.order
381
- return states, collisions
379
+ if mn is not None and mm is not None:
380
+ states[mn][4][mm] = bond.order
381
+ states[mm][4][mn] = bond.order
382
+ for n, mn in local.items():
383
+ states[mn][5].extend(_unmapped_branches(molecule, n, local))
384
+ return {mn: (*state[:5], tuple(sorted(state[5]))) for mn, state in states.items()}, collisions
385
+
386
+
387
+ def _unmapped_branches(molecule, n, mapped):
388
+ """The canonical SMILES of each unmapped region bonded to atom `n`, with `n` itself as the root."""
389
+ seen = set()
390
+ for start in molecule.neighbors_of(n):
391
+ if start in mapped or start in seen:
392
+ continue
393
+ region = {start}
394
+ stack = [start]
395
+ while stack:
396
+ for m in molecule.neighbors_of(stack.pop()):
397
+ if m not in mapped and m not in region:
398
+ region.add(m)
399
+ stack.append(m)
400
+ seen |= region
401
+ region.add(n)
402
+ yield format(molecule.substructure(region), 'm')
382
403
 
383
404
 
384
405
  def reaction_center(rxn) -> set[int]:
385
406
  """The map numbers of the atoms this reaction changes. Empty for a reaction with no mapping.
386
407
 
387
408
  An atom is in the centre when it appears on BOTH sides and something about it differs: its element,
388
- charge, radical state, implicit hydrogen count, or the map numbers and orders of its bonds. It is
389
- computed without building a CGR: there is no CGR container on this release, and the question does
390
- not need one.
409
+ charge, radical state, implicit hydrogen count, the map numbers and orders of its bonds, or the
410
+ unmapped branches bonded to it (`_side_states`). It is computed without building a CGR: there is
411
+ no CGR container on this release, and the question does not need one.
391
412
 
392
413
  AN ATOM PRESENT ON ONLY ONE SIDE IS NOT IN THE CENTRE, and that is a decision. Calling it dynamic
393
414
  instead -- a bond that exists on one side and not the other -- keeps sodium hydroxide a REACTANT in
@@ -53,7 +53,9 @@
53
53
  #
54
54
  # * an atom the patch WROTE (its element, charge, radical, or one of its bonds) and every surviving
55
55
  # neighbour of a deleted atom gets its count recomputed from the valence collection;
56
- # * an atom that merely sat inside the match keeps the count its input stated, exactly;
56
+ # * a pnictogen sharing an aromatic ring with a pnictogen the patch bonded is recomputed -- the
57
+ # azole NH of the tautomer the patch replaced;
58
+ # * any other atom that merely sat inside the match keeps the count its input stated, exactly;
57
59
  # * where the collection has no answer -- no row for the element in that charge and radical state,
58
60
  # or an aromatic bond reaching the atom -- the count is stored as `H_UNKNOWN`.
59
61
  #
@@ -189,6 +191,39 @@ cdef int smk_hydrogens(MoleculeContainer m, uint32_t n) except -2:
189
191
  return H_UNKNOWN
190
192
 
191
193
 
194
+ cdef bint smk_aromatic_pnictogen(MoleculeContainer m, object n) except -1:
195
+ cdef object other
196
+ if m.element_of(<uint32_t> n) not in (7, 15, 33):
197
+ return False
198
+ for other in m.neighbors_of(<uint32_t> n):
199
+ if m.order_of(<uint32_t> n, <uint32_t> other) == 4:
200
+ return True
201
+ return False
202
+
203
+
204
+ cdef int smk_ring_pnictogens(MoleculeContainer new, set gained, set alive, set changed) except -1:
205
+ """Add to `changed` every pnictogen with hydrogens sharing a ring with a bonded aromatic pnictogen.
206
+
207
+ The pyrrole-type hydrogen of an azole belongs to one tautomer. N-substituting any other ring
208
+ nitrogen makes it the other tautomer's, so the NH is recomputed although the patch never wrote
209
+ it, and comes back `H_UNKNOWN` for `kekule()`. Example: 5-methyltetrazole arylated at N2.
210
+ """
211
+ cdef set sites = set()
212
+ cdef object ring, n
213
+ for n in gained:
214
+ if n in alive and smk_aromatic_pnictogen(new, n):
215
+ sites.add(n)
216
+ if not sites:
217
+ return 0
218
+ for ring in new.rings:
219
+ if sites.isdisjoint(ring):
220
+ continue
221
+ for n in ring:
222
+ if n not in changed and new.implicit_h_of(<uint32_t> n) and smk_aromatic_pnictogen(new, n):
223
+ changed.add(n)
224
+ return 0
225
+
226
+
192
227
  # "the element is the R marker", in `smk_atom_fields`'s element slot, where 0 already means INHERIT.
193
228
  # A distinct value because those two are distinct claims, and 118 elements sit between them.
194
229
  DEF SMK_ELEMENT_R = -1
@@ -928,6 +963,8 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
928
963
  if key not in doomed:
929
964
  changed.add(key)
930
965
 
966
+ # atoms given a NEW bond: the ring-pnictogen rule below starts from them
967
+ cdef set gained = set()
931
968
  cdef set prod_bond_set = set(t.product_bonds)
932
969
  cdef tuple fields
933
970
  cdef uint32_t nid
@@ -957,7 +994,8 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
957
994
  if <int> fields[1] != work.charge_of(nid):
958
995
  new.set_charge(nid, <int> fields[1])
959
996
  changed.add(<object> nid)
960
- if <int> fields[2] != work.isotope_of(nid):
997
+ # an unstated product isotope (0) keeps the reactant's: `[C:1]` does not strip a 14C label
998
+ if <int> fields[2] and <int> fields[2] != work.isotope_of(nid):
961
999
  new.set_isotope(nid, <int> fields[2])
962
1000
  if <bint> fields[3] != work.radical_of(nid):
963
1001
  new.set_radical(nid, <bint> fields[3])
@@ -997,8 +1035,11 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
997
1035
  new.add_bond(<uint32_t> u, <uint32_t> v, order)
998
1036
  changed.add(u)
999
1037
  changed.add(v)
1038
+ gained.add(u)
1039
+ gained.add(v)
1000
1040
 
1001
1041
  cdef set alive = set(new.atom_numbers)
1042
+ smk_ring_pnictogens(new, gained, alive, changed)
1002
1043
 
1003
1044
  # Hydrogens BEFORE the stereo directives, and the order is load-bearing: whether an anchor's
1004
1045
  # fourth direction is an atom or its implicit hydrogen is a fact about the count, so a directive
@@ -1173,7 +1214,8 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
1173
1214
  (tuple(sorted(touched)), tuple(identities)), where)
1174
1215
 
1175
1216
 
1176
- def smk_apply(ReactionTemplate t, tuple molecules, bint automorphism_filter, object log, bint report):
1217
+ def smk_apply(ReactionTemplate t, tuple molecules, bint automorphism_filter, object log, bint report,
1218
+ bint dedupe):
1177
1219
  """Validate eagerly, then hand back the generator.
1178
1220
 
1179
1221
  The split is the point: a bad argument raises from the CALL, not from the first `next()`, so a
@@ -1189,10 +1231,11 @@ def smk_apply(ReactionTemplate t, tuple molecules, bint automorphism_filter, obj
1189
1231
  raise TypeError('a template applies to molecules; this one was handed a %s' % type(m).__name__)
1190
1232
  if not molecules:
1191
1233
  raise ValueError('a template needs at least one molecule to apply to')
1192
- return smk_enumerate(t, list(molecules), automorphism_filter, log, report)
1234
+ return smk_enumerate(t, list(molecules), automorphism_filter, log, report, dedupe)
1193
1235
 
1194
1236
 
1195
- def smk_enumerate(ReactionTemplate t, list inputs, bint automorphism_filter, object log, bint report):
1237
+ def smk_enumerate(ReactionTemplate t, list inputs, bint automorphism_filter, object log, bint report,
1238
+ bint dedupe):
1196
1239
  """Every distinct outcome of one template over one set of inputs.
1197
1240
 
1198
1241
  The inputs are unioned into one working container, which is why an intramolecular template needs
@@ -1259,10 +1302,11 @@ def smk_enumerate(ReactionTemplate t, list inputs, bint automorphism_filter, obj
1259
1302
  continue
1260
1303
  if made is None:
1261
1304
  continue
1262
- key = (<tuple> made)[1]
1263
- if key in seen_keys:
1264
- continue
1265
- seen_keys.add(key)
1305
+ if dedupe:
1306
+ key = (<tuple> made)[1]
1307
+ if key in seen_keys:
1308
+ continue
1309
+ seen_keys.add(key)
1266
1310
  if len(log) > start: # `_smiles_read.pxi:smi_one` states why the touch is guarded
1267
1311
  (<object> (<tuple> made)[0]).log.absorb('react', log[start:])
1268
1312
  if report:
@@ -1121,7 +1121,8 @@ cdef class ReactionTemplate:
1121
1121
  def __repr__(self):
1122
1122
  return 'read_smirks(%r)' % self.smirks
1123
1123
 
1124
- def __call__(self, *molecules, bint automorphism_filter=True, log=None, bint report=False):
1124
+ def __call__(self, *molecules, bint automorphism_filter=True, log=None, bint report=False,
1125
+ bint dedupe=True):
1125
1126
  """Apply this template to one molecule or to several, yielding one `ReactionContainer` per
1126
1127
  distinct outcome.
1127
1128
 
@@ -1158,9 +1159,15 @@ cdef class ReactionTemplate:
1158
1159
  reaction alone. The ids are the yielded products' own, and they survive `copy()` and `split()`,
1159
1160
  so a caller that must edit "the atom the product side called :1" can reach it -- by map number,
1160
1161
  the template's `:N` space, which is still not the reaction's imposed mapping.
1162
+
1163
+ `dedupe=False` yields one outcome per embedding, including outcomes that build the same
1164
+ structure from different atoms: `C1CNCCN1` cut at either nitrogen. A caller that filters
1165
+ outcomes by atom id needs every one, since the default keeps only the first of each structure.
1166
+ `automorphism_filter` is a different collapse -- over the template's own symmetry, not the
1167
+ outcome's.
1161
1168
  """
1162
1169
  return smk_apply(self, molecules, automorphism_filter,
1163
- log if log is not None else [], report)
1170
+ log if log is not None else [], report, dedupe)
1164
1171
 
1165
1172
 
1166
1173
  def read_smirks(text, log=None, *, rule_id=None):