chython 3.0.1__tar.gz → 3.1__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 (455) hide show
  1. {chython-3.0.1/chython.egg-info → chython-3.1}/PKG-INFO +1 -1
  2. {chython-3.0.1 → chython-3.1}/chython/chemistry/_canonicalize.py +7 -3
  3. chython-3.1/chython/chemistry/_organometallics.py +133 -0
  4. {chython-3.0.1 → chython-3.1}/chython/chemistry/_standardize.py +12 -1
  5. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/salts.tsv +1 -0
  6. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/standardize_metals.tsv +7 -0
  7. chython-3.1/chython/chemistry/test/test_organometallics.py +219 -0
  8. {chython-3.0.1 → chython-3.1}/chython/core/_ml.pxi +77 -75
  9. {chython-3.0.1 → chython-3.1}/chython/core/_smiles_read.pxi +20 -0
  10. {chython-3.0.1 → chython-3.1}/chython/core/_smiles_write.pxi +12 -6
  11. {chython-3.0.1 → chython-3.1}/chython/core/reaction.py +25 -9
  12. {chython-3.0.1 → chython-3.1}/chython/core/test/modeling_view_corpus.py +2 -2
  13. {chython-3.0.1 → chython-3.1}/chython/core/test/test_ml_reaction_transition.py +131 -10
  14. chython-3.1/chython/core/test/test_modeling_view_frozen.py +139 -0
  15. {chython-3.0.1 → chython-3.1}/chython/core/test/test_reaction_container.py +34 -8
  16. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_read.py +49 -0
  17. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write.py +18 -0
  18. {chython-3.0.1 → chython-3.1}/chython/reactions/tables/functional.tsv +11 -0
  19. {chython-3.0.1 → chython-3.1}/chython/reactions/test/_frozen_ids.py +5 -1
  20. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_enumerate.py +32 -0
  21. {chython-3.0.1 → chython-3.1}/chython/test/test_facade_names.py +1 -1
  22. {chython-3.0.1 → chython-3.1/chython.egg-info}/PKG-INFO +1 -1
  23. {chython-3.0.1 → chython-3.1}/chython.egg-info/SOURCES.txt +2 -0
  24. {chython-3.0.1 → chython-3.1}/pyproject.toml +1 -1
  25. chython-3.0.1/chython/core/test/test_modeling_view_frozen.py +0 -86
  26. {chython-3.0.1 → chython-3.1}/LICENSE +0 -0
  27. {chython-3.0.1 → chython-3.1}/MANIFEST.in +0 -0
  28. {chython-3.0.1 → chython-3.1}/README.md +0 -0
  29. {chython-3.0.1 → chython-3.1}/build_inchi.py +0 -0
  30. {chython-3.0.1 → chython-3.1}/chython/__init__.py +0 -0
  31. {chython-3.0.1 → chython-3.1}/chython/_functions.py +0 -0
  32. {chython-3.0.1 → chython-3.1}/chython/chemistry/__init__.py +0 -0
  33. {chython-3.0.1 → chython-3.1}/chython/chemistry/_abbreviations.py +0 -0
  34. {chython-3.0.1 → chython-3.1}/chython/chemistry/_counts.py +0 -0
  35. {chython-3.0.1 → chython-3.1}/chython/chemistry/_crippen.py +0 -0
  36. {chython-3.0.1 → chython-3.1}/chython/chemistry/_hydrogens.py +0 -0
  37. {chython-3.0.1 → chython-3.1}/chython/chemistry/_implicit.py +0 -0
  38. {chython-3.0.1 → chython-3.1}/chython/chemistry/_isomers.py +0 -0
  39. {chython-3.0.1 → chython-3.1}/chython/chemistry/_maccs.py +0 -0
  40. {chython-3.0.1 → chython-3.1}/chython/chemistry/_perceive.py +0 -0
  41. {chython-3.0.1 → chython-3.1}/chython/chemistry/_pharmacophore.py +0 -0
  42. {chython-3.0.1 → chython-3.1}/chython/chemistry/_protomers.py +0 -0
  43. {chython-3.0.1 → chython-3.1}/chython/chemistry/_qed.py +0 -0
  44. {chython-3.0.1 → chython-3.1}/chython/chemistry/_residues.py +0 -0
  45. {chython-3.0.1 → chython-3.1}/chython/chemistry/_resonance.py +0 -0
  46. {chython-3.0.1 → chython-3.1}/chython/chemistry/_salts.py +0 -0
  47. {chython-3.0.1 → chython-3.1}/chython/chemistry/_saturate.py +0 -0
  48. {chython-3.0.1 → chython-3.1}/chython/chemistry/_smarts.py +0 -0
  49. {chython-3.0.1 → chython-3.1}/chython/chemistry/_tables.py +0 -0
  50. {chython-3.0.1 → chython-3.1}/chython/chemistry/_tpsa.py +0 -0
  51. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/abbreviations.tsv +0 -0
  52. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/acids.tsv +0 -0
  53. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/covalent_radii.tsv +0 -0
  54. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/crippen.tsv +0 -0
  55. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/hbond.tsv +0 -0
  56. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/maccs.tsv +0 -0
  57. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/maccs_corpus.tsv +0 -0
  58. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/pharmacophore.tsv +0 -0
  59. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/qed_alerts.tsv +0 -0
  60. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/residues.tsv +0 -0
  61. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/resonance.tsv +0 -0
  62. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/rotatable.tsv +0 -0
  63. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/standardize_groups.tsv +0 -0
  64. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/sybyl_types.tsv +0 -0
  65. {chython-3.0.1 → chython-3.1}/chython/chemistry/tables/tpsa.tsv +0 -0
  66. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/__init__.py +0 -0
  67. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/_corpus.py +0 -0
  68. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/_oracle.py +0 -0
  69. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/gen_standardize_rules.py +0 -0
  70. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_abbreviations.py +0 -0
  71. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_acids_tsv.py +0 -0
  72. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_canonicalize.py +0 -0
  73. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_counts.py +0 -0
  74. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_covalent_radii_tsv.py +0 -0
  75. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_crippen.py +0 -0
  76. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_crippen_tsv.py +0 -0
  77. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_dependency_direction.py +0 -0
  78. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_featurizer_injection.py +0 -0
  79. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_featurizer_tables_lazy.py +0 -0
  80. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_isomers.py +0 -0
  81. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_maccs.py +0 -0
  82. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_maccs_corpus.py +0 -0
  83. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_maccs_tsv.py +0 -0
  84. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_perceive.py +0 -0
  85. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_pharmacophore.py +0 -0
  86. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_protomers.py +0 -0
  87. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_qed.py +0 -0
  88. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_qed_alerts_tsv.py +0 -0
  89. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_reaction_hydrogen_repair.py +0 -0
  90. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_reaction_passes.py +0 -0
  91. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_residues.py +0 -0
  92. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_resonance.py +0 -0
  93. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_resonance_tsv.py +0 -0
  94. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_salts.py +0 -0
  95. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_saturate.py +0 -0
  96. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_smarts.py +0 -0
  97. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_standardize_differential.py +0 -0
  98. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_standardize_groups_port.py +0 -0
  99. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_standardize_overvalent_nitrogen.py +0 -0
  100. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_standardize_rules_examples.py +0 -0
  101. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_standardize_rules_merges.py +0 -0
  102. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_standardize_rules_tsv.py +0 -0
  103. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_thiele_is_single_purpose.py +0 -0
  104. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_tpsa.py +0 -0
  105. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_tpsa_tsv.py +0 -0
  106. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_valence_report.py +0 -0
  107. {chython-3.0.1 → chython-3.1}/chython/chemistry/test/test_z_translation.py +0 -0
  108. {chython-3.0.1 → chython-3.1}/chython/core/RULES.md +0 -0
  109. {chython-3.0.1 → chython-3.1}/chython/core/__init__.py +0 -0
  110. {chython-3.0.1 → chython-3.1}/chython/core/_canonical.pxi +0 -0
  111. {chython-3.0.1 → chython-3.1}/chython/core/_core.pyx +0 -0
  112. {chython-3.0.1 → chython-3.1}/chython/core/_descriptors.pxi +0 -0
  113. {chython-3.0.1 → chython-3.1}/chython/core/_elements.pxi +0 -0
  114. {chython-3.0.1 → chython-3.1}/chython/core/_facade.py +0 -0
  115. {chython-3.0.1 → chython-3.1}/chython/core/_features.pxi +0 -0
  116. {chython-3.0.1 → chython-3.1}/chython/core/_fingerprints.pxi +0 -0
  117. {chython-3.0.1 → chython-3.1}/chython/core/_hydrogens.pxi +0 -0
  118. {chython-3.0.1 → chython-3.1}/chython/core/_inchi.pxi +0 -0
  119. {chython-3.0.1 → chython-3.1}/chython/core/_isomorphism.pxi +0 -0
  120. {chython-3.0.1 → chython-3.1}/chython/core/_kekule.pxi +0 -0
  121. {chython-3.0.1 → chython-3.1}/chython/core/_log.py +0 -0
  122. {chython-3.0.1 → chython-3.1}/chython/core/_molecule_arena.pxi +0 -0
  123. {chython-3.0.1 → chython-3.1}/chython/core/_molecule_container.pxi +0 -0
  124. {chython-3.0.1 → chython-3.1}/chython/core/_molecule_topology.pxi +0 -0
  125. {chython-3.0.1 → chython-3.1}/chython/core/_molecule_views.pxi +0 -0
  126. {chython-3.0.1 → chython-3.1}/chython/core/_morgan.pxi +0 -0
  127. {chython-3.0.1 → chython-3.1}/chython/core/_pach.pxi +0 -0
  128. {chython-3.0.1 → chython-3.1}/chython/core/_pach3.pxi +0 -0
  129. {chython-3.0.1 → chython-3.1}/chython/core/_query_arena.pxi +0 -0
  130. {chython-3.0.1 → chython-3.1}/chython/core/_query_boxes.pxi +0 -0
  131. {chython-3.0.1 → chython-3.1}/chython/core/_query_container.pxi +0 -0
  132. {chython-3.0.1 → chython-3.1}/chython/core/_query_seal.pxi +0 -0
  133. {chython-3.0.1 → chython-3.1}/chython/core/_reaction_passes.py +0 -0
  134. {chython-3.0.1 → chython-3.1}/chython/core/_rings.pxi +0 -0
  135. {chython-3.0.1 → chython-3.1}/chython/core/_smarts_read.pxi +0 -0
  136. {chython-3.0.1 → chython-3.1}/chython/core/_smirks_patch.pxi +0 -0
  137. {chython-3.0.1 → chython-3.1}/chython/core/_smirks_read.pxi +0 -0
  138. {chython-3.0.1 → chython-3.1}/chython/core/_sssr.pxi +0 -0
  139. {chython-3.0.1 → chython-3.1}/chython/core/_stereo.pxi +0 -0
  140. {chython-3.0.1 → chython-3.1}/chython/core/_thiele.pxi +0 -0
  141. {chython-3.0.1 → chython-3.1}/chython/core/_valence.pxi +0 -0
  142. {chython-3.0.1 → chython-3.1}/chython/core/elements.tsv +0 -0
  143. {chython-3.0.1 → chython-3.1}/chython/core/isotopes.tsv +0 -0
  144. {chython-3.0.1 → chython-3.1}/chython/core/test/__init__.py +0 -0
  145. {chython-3.0.1 → chython-3.1}/chython/core/test/arena_v4_corpus.bin.gz +0 -0
  146. {chython-3.0.1 → chython-3.1}/chython/core/test/bench_ml.py +0 -0
  147. {chython-3.0.1 → chython-3.1}/chython/core/test/chytorch_oracle.py +0 -0
  148. {chython-3.0.1 → chython-3.1}/chython/core/test/gen_element_tables.py +0 -0
  149. {chython-3.0.1 → chython-3.1}/chython/core/test/gen_modeling_view_corpus.py +0 -0
  150. {chython-3.0.1 → chython-3.1}/chython/core/test/gen_pach3_corpus.py +0 -0
  151. {chython-3.0.1 → chython-3.1}/chython/core/test/gen_reaction_pach_corpus.py +0 -0
  152. {chython-3.0.1 → chython-3.1}/chython/core/test/gen_v3_fixtures.py +0 -0
  153. {chython-3.0.1 → chython-3.1}/chython/core/test/gen_v4_fixtures.py +0 -0
  154. {chython-3.0.1 → chython-3.1}/chython/core/test/gen_valence_rules.py +0 -0
  155. {chython-3.0.1 → chython-3.1}/chython/core/test/modeling_view_corpus.json.gz +0 -0
  156. {chython-3.0.1 → chython-3.1}/chython/core/test/oracle.py +0 -0
  157. {chython-3.0.1 → chython-3.1}/chython/core/test/pach3_corpus.py +0 -0
  158. {chython-3.0.1 → chython-3.1}/chython/core/test/pach_corpus.py +0 -0
  159. {chython-3.0.1 → chython-3.1}/chython/core/test/pach_v0_corpus.bin.gz +0 -0
  160. {chython-3.0.1 → chython-3.1}/chython/core/test/pach_v0_native_corpus.bin.gz +0 -0
  161. {chython-3.0.1 → chython-3.1}/chython/core/test/pach_v2_corpus.bin.gz +0 -0
  162. {chython-3.0.1 → chython-3.1}/chython/core/test/pach_v3_corpus.bin.gz +0 -0
  163. {chython-3.0.1 → chython-3.1}/chython/core/test/pach_v4_corpus.bin.gz +0 -0
  164. {chython-3.0.1 → chython-3.1}/chython/core/test/reaction_pach_corpus.py +0 -0
  165. {chython-3.0.1 → chython-3.1}/chython/core/test/reaction_pach_v2_corpus.bin.gz +0 -0
  166. {chython-3.0.1 → chython-3.1}/chython/core/test/test_aggregates.py +0 -0
  167. {chython-3.0.1 → chython-3.1}/chython/core/test/test_alternative_spellings.py +0 -0
  168. {chython-3.0.1 → chython-3.1}/chython/core/test/test_apply_scratch_probe.py +0 -0
  169. {chython-3.0.1 → chython-3.1}/chython/core/test/test_arena_f60.py +0 -0
  170. {chython-3.0.1 → chython-3.1}/chython/core/test/test_arena_identity.py +0 -0
  171. {chython-3.0.1 → chython-3.1}/chython/core/test/test_arena_v3_compat.py +0 -0
  172. {chython-3.0.1 → chython-3.1}/chython/core/test/test_arena_v4_compat.py +0 -0
  173. {chython-3.0.1 → chython-3.1}/chython/core/test/test_aromatic_storage.py +0 -0
  174. {chython-3.0.1 → chython-3.1}/chython/core/test/test_canonical.py +0 -0
  175. {chython-3.0.1 → chython-3.1}/chython/core/test/test_canonical_mirror.py +0 -0
  176. {chython-3.0.1 → chython-3.1}/chython/core/test/test_cip_storage.py +0 -0
  177. {chython-3.0.1 → chython-3.1}/chython/core/test/test_clean_isotopes_and_coordinate_bonds.py +0 -0
  178. {chython-3.0.1 → chython-3.1}/chython/core/test/test_clean_stereo.py +0 -0
  179. {chython-3.0.1 → chython-3.1}/chython/core/test/test_conformers.py +0 -0
  180. {chython-3.0.1 → chython-3.1}/chython/core/test/test_container_log.py +0 -0
  181. {chython-3.0.1 → chython-3.1}/chython/core/test/test_copy_caches.py +0 -0
  182. {chython-3.0.1 → chython-3.1}/chython/core/test/test_derive.py +0 -0
  183. {chython-3.0.1 → chython-3.1}/chython/core/test/test_descriptors.py +0 -0
  184. {chython-3.0.1 → chython-3.1}/chython/core/test/test_element_tables.py +0 -0
  185. {chython-3.0.1 → chython-3.1}/chython/core/test/test_facade.py +0 -0
  186. {chython-3.0.1 → chython-3.1}/chython/core/test/test_features.py +0 -0
  187. {chython-3.0.1 → chython-3.1}/chython/core/test/test_featurizer_injection.py +0 -0
  188. {chython-3.0.1 → chython-3.1}/chython/core/test/test_fingerprints.py +0 -0
  189. {chython-3.0.1 → chython-3.1}/chython/core/test/test_geometry.py +0 -0
  190. {chython-3.0.1 → chython-3.1}/chython/core/test/test_h_unknown.py +0 -0
  191. {chython-3.0.1 → chython-3.1}/chython/core/test/test_hydrogens.py +0 -0
  192. {chython-3.0.1 → chython-3.1}/chython/core/test/test_inchi.py +0 -0
  193. {chython-3.0.1 → chython-3.1}/chython/core/test/test_interop_injection.py +0 -0
  194. {chython-3.0.1 → chython-3.1}/chython/core/test/test_isomorphism.py +0 -0
  195. {chython-3.0.1 → chython-3.1}/chython/core/test/test_kekule.py +0 -0
  196. {chython-3.0.1 → chython-3.1}/chython/core/test/test_log.py +0 -0
  197. {chython-3.0.1 → chython-3.1}/chython/core/test/test_magic.py +0 -0
  198. {chython-3.0.1 → chython-3.1}/chython/core/test/test_meta.py +0 -0
  199. {chython-3.0.1 → chython-3.1}/chython/core/test/test_ml_encoding.py +0 -0
  200. {chython-3.0.1 → chython-3.1}/chython/core/test/test_ml_state_view.py +0 -0
  201. {chython-3.0.1 → chython-3.1}/chython/core/test/test_ml_transition_view.py +0 -0
  202. {chython-3.0.1 → chython-3.1}/chython/core/test/test_ml_unpack_differential.py +0 -0
  203. {chython-3.0.1 → chython-3.1}/chython/core/test/test_molecule.py +0 -0
  204. {chython-3.0.1 → chython-3.1}/chython/core/test/test_morgan.py +0 -0
  205. {chython-3.0.1 → chython-3.1}/chython/core/test/test_no_chython_two_imports.py +0 -0
  206. {chython-3.0.1 → chython-3.1}/chython/core/test/test_oracle.py +0 -0
  207. {chython-3.0.1 → chython-3.1}/chython/core/test/test_pach.py +0 -0
  208. {chython-3.0.1 → chython-3.1}/chython/core/test/test_pach3.py +0 -0
  209. {chython-3.0.1 → chython-3.1}/chython/core/test/test_pack.py +0 -0
  210. {chython-3.0.1 → chython-3.1}/chython/core/test/test_query.py +0 -0
  211. {chython-3.0.1 → chython-3.1}/chython/core/test/test_r_edit.py +0 -0
  212. {chython-3.0.1 → chython-3.1}/chython/core/test/test_r_query.py +0 -0
  213. {chython-3.0.1 → chython-3.1}/chython/core/test/test_r_semantics.py +0 -0
  214. {chython-3.0.1 → chython-3.1}/chython/core/test/test_r_serialisation.py +0 -0
  215. {chython-3.0.1 → chython-3.1}/chython/core/test/test_r_smirks.py +0 -0
  216. {chython-3.0.1 → chython-3.1}/chython/core/test/test_r_storage.py +0 -0
  217. {chython-3.0.1 → chython-3.1}/chython/core/test/test_reaction_identity.py +0 -0
  218. {chython-3.0.1 → chython-3.1}/chython/core/test/test_reaction_pach.py +0 -0
  219. {chython-3.0.1 → chython-3.1}/chython/core/test/test_reaction_passes.py +0 -0
  220. {chython-3.0.1 → chython-3.1}/chython/core/test/test_reaction_patch_stereo.py +0 -0
  221. {chython-3.0.1 → chython-3.1}/chython/core/test/test_reaction_smiles.py +0 -0
  222. {chython-3.0.1 → chython-3.1}/chython/core/test/test_rings.py +0 -0
  223. {chython-3.0.1 → chython-3.1}/chython/core/test/test_rings_c60.py +0 -0
  224. {chython-3.0.1 → chython-3.1}/chython/core/test/test_set_element.py +0 -0
  225. {chython-3.0.1 → chython-3.1}/chython/core/test/test_sgroups.py +0 -0
  226. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smarts_read.py +0 -0
  227. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_r.py +0 -0
  228. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_roundtrip.py +0 -0
  229. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write_aromatic.py +0 -0
  230. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write_cis_trans.py +0 -0
  231. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write_detached.py +0 -0
  232. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write_differential.py +0 -0
  233. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write_h_unknown.py +0 -0
  234. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write_stereo.py +0 -0
  235. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smiles_write_sticky.py +0 -0
  236. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smirks_filter.py +0 -0
  237. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smirks_patch.py +0 -0
  238. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smirks_read.py +0 -0
  239. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smirks_report.py +0 -0
  240. {chython-3.0.1 → chython-3.1}/chython/core/test/test_smirks_stereo.py +0 -0
  241. {chython-3.0.1 → chython-3.1}/chython/core/test/test_stereo_acceptance.py +0 -0
  242. {chython-3.0.1 → chython-3.1}/chython/core/test/test_stereo_parity.py +0 -0
  243. {chython-3.0.1 → chython-3.1}/chython/core/test/test_stereo_perception.py +0 -0
  244. {chython-3.0.1 → chython-3.1}/chython/core/test/test_stereo_query.py +0 -0
  245. {chython-3.0.1 → chython-3.1}/chython/core/test/test_stereo_units.py +0 -0
  246. {chython-3.0.1 → chython-3.1}/chython/core/test/test_stereo_v2_differential.py +0 -0
  247. {chython-3.0.1 → chython-3.1}/chython/core/test/test_structure.py +0 -0
  248. {chython-3.0.1 → chython-3.1}/chython/core/test/test_thiele.py +0 -0
  249. {chython-3.0.1 → chython-3.1}/chython/core/test/test_title.py +0 -0
  250. {chython-3.0.1 → chython-3.1}/chython/core/test/test_topology.py +0 -0
  251. {chython-3.0.1 → chython-3.1}/chython/core/test/test_union_stereo.py +0 -0
  252. {chython-3.0.1 → chython-3.1}/chython/core/test/test_valence.py +0 -0
  253. {chython-3.0.1 → chython-3.1}/chython/core/test/test_view_surface.py +0 -0
  254. {chython-3.0.1 → chython-3.1}/chython/core/test/v3_fixtures.py +0 -0
  255. {chython-3.0.1 → chython-3.1}/chython/core/test/v4_fixtures.py +0 -0
  256. {chython-3.0.1 → chython-3.1}/chython/core/valence_rules.tsv +0 -0
  257. {chython-3.0.1 → chython-3.1}/chython/core/wedge.py +0 -0
  258. {chython-3.0.1 → chython-3.1}/chython/depict/__init__.py +0 -0
  259. {chython-3.0.1 → chython-3.1}/chython/depict/_config.py +0 -0
  260. {chython-3.0.1 → chython-3.1}/chython/depict/_hooks.py +0 -0
  261. {chython-3.0.1 → chython-3.1}/chython/depict/bonds.py +0 -0
  262. {chython-3.0.1 → chython-3.1}/chython/depict/colorbar.py +0 -0
  263. {chython-3.0.1 → chython-3.1}/chython/depict/colormap.py +0 -0
  264. {chython-3.0.1 → chython-3.1}/chython/depict/field.py +0 -0
  265. {chython-3.0.1 → chython-3.1}/chython/depict/figure.py +0 -0
  266. {chython-3.0.1 → chython-3.1}/chython/depict/label.py +0 -0
  267. {chython-3.0.1 → chython-3.1}/chython/depict/layout/__init__.py +0 -0
  268. {chython-3.0.1 → chython-3.1}/chython/depict/layout/clean2d.js +0 -0
  269. {chython-3.0.1 → chython-3.1}/chython/depict/layout/molecule.py +0 -0
  270. {chython-3.0.1 → chython-3.1}/chython/depict/layout/reaction.py +0 -0
  271. {chython-3.0.1 → chython-3.1}/chython/depict/metrics/__init__.py +0 -0
  272. {chython-3.0.1 → chython-3.1}/chython/depict/metrics/helvetica.tsv +0 -0
  273. {chython-3.0.1 → chython-3.1}/chython/depict/metrics/times.tsv +0 -0
  274. {chython-3.0.1 → chython-3.1}/chython/depict/overlay.py +0 -0
  275. {chython-3.0.1 → chython-3.1}/chython/depict/render/__init__.py +0 -0
  276. {chython-3.0.1 → chython-3.1}/chython/depict/render/svg.py +0 -0
  277. {chython-3.0.1 → chython-3.1}/chython/depict/scene.py +0 -0
  278. {chython-3.0.1 → chython-3.1}/chython/depict/style.py +0 -0
  279. {chython-3.0.1 → chython-3.1}/chython/depict/test/__init__.py +0 -0
  280. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_bonds.py +0 -0
  281. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_clean2d.py +0 -0
  282. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_colorbar.py +0 -0
  283. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_colormap.py +0 -0
  284. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_field.py +0 -0
  285. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_figure.py +0 -0
  286. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_label.py +0 -0
  287. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_metrics.py +0 -0
  288. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_overlay.py +0 -0
  289. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_package.py +0 -0
  290. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_r_atom.py +0 -0
  291. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_scene.py +0 -0
  292. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_style.py +0 -0
  293. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_svg.py +0 -0
  294. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_wedge_draw.py +0 -0
  295. {chython-3.0.1 → chython-3.1}/chython/depict/test/test_x3dom.py +0 -0
  296. {chython-3.0.1 → chython-3.1}/chython/depict/wedge.py +0 -0
  297. {chython-3.0.1 → chython-3.1}/chython/depict/x3dom.py +0 -0
  298. {chython-3.0.1 → chython-3.1}/chython/exceptions.py +0 -0
  299. {chython-3.0.1 → chython-3.1}/chython/formats/__init__.py +0 -0
  300. {chython-3.0.1 → chython-3.1}/chython/formats/_text.py +0 -0
  301. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/CTFILE.md +0 -0
  302. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/__init__.py +0 -0
  303. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_ctab.py +0 -0
  304. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_errors.py +0 -0
  305. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_facade.py +0 -0
  306. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_hydrogens.py +0 -0
  307. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_rdf.py +0 -0
  308. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_rxn.py +0 -0
  309. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_sdf.py +0 -0
  310. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_sgroup.py +0 -0
  311. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_stream.py +0 -0
  312. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_tokens.py +0 -0
  313. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_v2000.py +0 -0
  314. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/_v3000.py +0 -0
  315. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/__init__.py +0 -0
  316. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/conftest.py +0 -0
  317. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_aromatic.py +0 -0
  318. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_container_log.py +0 -0
  319. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_data_labels.py +0 -0
  320. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_facade.py +0 -0
  321. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_fidelity.py +0 -0
  322. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_hydrogens.py +0 -0
  323. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_r_atom.py +0 -0
  324. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_rdf.py +0 -0
  325. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_rxn.py +0 -0
  326. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_sdf.py +0 -0
  327. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_sgroup.py +0 -0
  328. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_stream.py +0 -0
  329. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_tokens.py +0 -0
  330. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_v2000.py +0 -0
  331. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_v3000.py +0 -0
  332. {chython-3.0.1 → chython-3.1}/chython/formats/ctfile/test/test_wedge.py +0 -0
  333. {chython-3.0.1 → chython-3.1}/chython/formats/mol2.py +0 -0
  334. {chython-3.0.1 → chython-3.1}/chython/formats/pdb/__init__.py +0 -0
  335. {chython-3.0.1 → chython-3.1}/chython/formats/pdb/_builder.py +0 -0
  336. {chython-3.0.1 → chython-3.1}/chython/formats/pdb/_legacy.py +0 -0
  337. {chython-3.0.1 → chython-3.1}/chython/formats/pdb/_mmcif.py +0 -0
  338. {chython-3.0.1 → chython-3.1}/chython/formats/pdb/_records.py +0 -0
  339. {chython-3.0.1 → chython-3.1}/chython/formats/pdb/_star.py +0 -0
  340. {chython-3.0.1 → chython-3.1}/chython/formats/test/__init__.py +0 -0
  341. {chython-3.0.1 → chython-3.1}/chython/formats/test/conftest.py +0 -0
  342. {chython-3.0.1 → chython-3.1}/chython/formats/test/oracles.py +0 -0
  343. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_conformance.py +0 -0
  344. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_done_when.py +0 -0
  345. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_isolation.py +0 -0
  346. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_log_prefix.py +0 -0
  347. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_mmcif.py +0 -0
  348. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_mol2.py +0 -0
  349. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_no_review_bookkeeping.py +0 -0
  350. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_oracles.py +0 -0
  351. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_pdb.py +0 -0
  352. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_pdb_builder.py +0 -0
  353. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_perceive_agreement.py +0 -0
  354. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_read_only_facades.py +0 -0
  355. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_xyz.py +0 -0
  356. {chython-3.0.1 → chython-3.1}/chython/formats/test/test_xyz_builder.py +0 -0
  357. {chython-3.0.1 → chython-3.1}/chython/formats/xml/__init__.py +0 -0
  358. {chython-3.0.1 → chython-3.1}/chython/formats/xml/_cml.py +0 -0
  359. {chython-3.0.1 → chython-3.1}/chython/formats/xml/_dialect.py +0 -0
  360. {chython-3.0.1 → chython-3.1}/chython/formats/xml/_errors.py +0 -0
  361. {chython-3.0.1 → chython-3.1}/chython/formats/xml/_facade.py +0 -0
  362. {chython-3.0.1 → chython-3.1}/chython/formats/xml/_mrv.py +0 -0
  363. {chython-3.0.1 → chython-3.1}/chython/formats/xml/_tree.py +0 -0
  364. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/__init__.py +0 -0
  365. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/conftest.py +0 -0
  366. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_cml.py +0 -0
  367. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_container_log.py +0 -0
  368. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_dialect.py +0 -0
  369. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_equivalence.py +0 -0
  370. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_facade.py +0 -0
  371. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_mrv.py +0 -0
  372. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_mrv_census.py +0 -0
  373. {chython-3.0.1 → chython-3.1}/chython/formats/xml/test/test_tree.py +0 -0
  374. {chython-3.0.1 → chython-3.1}/chython/formats/xyz.py +0 -0
  375. {chython-3.0.1 → chython-3.1}/chython/interop/__init__.py +0 -0
  376. {chython-3.0.1 → chython-3.1}/chython/interop/_cdk.py +0 -0
  377. {chython-3.0.1 → chython-3.1}/chython/interop/_cdpkit.py +0 -0
  378. {chython-3.0.1 → chython-3.1}/chython/interop/_indigo.py +0 -0
  379. {chython-3.0.1 → chython-3.1}/chython/interop/_iupac.py +0 -0
  380. {chython-3.0.1 → chython-3.1}/chython/interop/_java.py +0 -0
  381. {chython-3.0.1 → chython-3.1}/chython/interop/_openbabel.py +0 -0
  382. {chython-3.0.1 → chython-3.1}/chython/interop/_pandas.py +0 -0
  383. {chython-3.0.1 → chython-3.1}/chython/interop/_rdkit.py +0 -0
  384. {chython-3.0.1 → chython-3.1}/chython/interop/_records.py +0 -0
  385. {chython-3.0.1 → chython-3.1}/chython/interop/_stereo.py +0 -0
  386. {chython-3.0.1 → chython-3.1}/chython/interop/config.py +0 -0
  387. {chython-3.0.1 → chython-3.1}/chython/interop/conformers.py +0 -0
  388. {chython-3.0.1 → chython-3.1}/chython/interop/test/__init__.py +0 -0
  389. {chython-3.0.1 → chython-3.1}/chython/interop/test/conftest.py +0 -0
  390. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_cdk.py +0 -0
  391. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_cdpkit.py +0 -0
  392. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_config.py +0 -0
  393. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_conformers.py +0 -0
  394. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_coordinate_honesty.py +0 -0
  395. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_dispatch.py +0 -0
  396. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_indigo.py +0 -0
  397. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_iupac.py +0 -0
  398. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_log_delivery.py +0 -0
  399. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_openbabel.py +0 -0
  400. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_pandas.py +0 -0
  401. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_rdkit.py +0 -0
  402. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_stereo.py +0 -0
  403. {chython-3.0.1 → chython-3.1}/chython/interop/test/test_v2_oracle.py +0 -0
  404. {chython-3.0.1 → chython-3.1}/chython/reactions/__init__.py +0 -0
  405. {chython-3.0.1 → chython-3.1}/chython/reactions/_enumerate.py +0 -0
  406. {chython-3.0.1 → chython-3.1}/chython/reactions/_numbering.py +0 -0
  407. {chython-3.0.1 → chython-3.1}/chython/reactions/_reconstruct.py +0 -0
  408. {chython-3.0.1 → chython-3.1}/chython/reactions/_stickers.py +0 -0
  409. {chython-3.0.1 → chython-3.1}/chython/reactions/_tables.py +0 -0
  410. {chython-3.0.1 → chython-3.1}/chython/reactions/attention/__init__.py +0 -0
  411. {chython-3.0.1 → chython-3.1}/chython/reactions/attention/_assign.py +0 -0
  412. {chython-3.0.1 → chython-3.1}/chython/reactions/attention/_encode.py +0 -0
  413. {chython-3.0.1 → chython-3.1}/chython/reactions/attention/_session.py +0 -0
  414. {chython-3.0.1 → chython-3.1}/chython/reactions/tables/protective.tsv +0 -0
  415. {chython-3.0.1 → chython-3.1}/chython/reactions/tables/reactions.tsv +0 -0
  416. {chython-3.0.1 → chython-3.1}/chython/reactions/tables/roles.tsv +0 -0
  417. {chython-3.0.1 → chython-3.1}/chython/reactions/test/__init__.py +0 -0
  418. {chython-3.0.1 → chython-3.1}/chython/reactions/test/gen_corpus_glossary.py +0 -0
  419. {chython-3.0.1 → chython-3.1}/chython/reactions/test/golden_subset.smi +0 -0
  420. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_attention.py +0 -0
  421. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_attention_assign.py +0 -0
  422. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_attention_encode.py +0 -0
  423. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_attention_isolation.py +0 -0
  424. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_corpus_glossary.py +0 -0
  425. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_dependency_direction.py +0 -0
  426. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_functional.py +0 -0
  427. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_id_stability.py +0 -0
  428. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_numbering.py +0 -0
  429. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_probes.py +0 -0
  430. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_protective.py +0 -0
  431. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_reconstruct.py +0 -0
  432. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_roles.py +0 -0
  433. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_stickers.py +0 -0
  434. {chython-3.0.1 → chython-3.1}/chython/reactions/test/test_tables.py +0 -0
  435. {chython-3.0.1 → chython-3.1}/chython/test/__init__.py +0 -0
  436. {chython-3.0.1 → chython-3.1}/chython/test/test_code_hygiene.py +0 -0
  437. {chython-3.0.1 → chython-3.1}/chython/test/test_container_methods.py +0 -0
  438. {chython-3.0.1 → chython-3.1}/chython/test/test_doc_figures.py +0 -0
  439. {chython-3.0.1 → chython-3.1}/chython/test/test_doc_references.py +0 -0
  440. {chython-3.0.1 → chython-3.1}/chython/test/test_doc_samples.py +0 -0
  441. {chython-3.0.1 → chython-3.1}/chython/test/test_hydrogen_parity.py +0 -0
  442. {chython-3.0.1 → chython-3.1}/chython/test/test_libinchi_staging.py +0 -0
  443. {chython-3.0.1 → chython-3.1}/chython/test/test_log_records.py +0 -0
  444. {chython-3.0.1 → chython-3.1}/chython/test/test_optional_numpy.py +0 -0
  445. {chython-3.0.1 → chython-3.1}/chython/test/test_packaging.py +0 -0
  446. {chython-3.0.1 → chython-3.1}/chython/test/test_performance.py +0 -0
  447. {chython-3.0.1 → chython-3.1}/chython/test/test_r_atom_integration.py +0 -0
  448. {chython-3.0.1 → chython-3.1}/chython/test/test_release_build.py +0 -0
  449. {chython-3.0.1 → chython-3.1}/chython/test/test_stereo_bluebook.py +0 -0
  450. {chython-3.0.1 → chython-3.1}/chython/test/test_v2_boundary.py +0 -0
  451. {chython-3.0.1 → chython-3.1}/chython.egg-info/dependency_links.txt +0 -0
  452. {chython-3.0.1 → chython-3.1}/chython.egg-info/requires.txt +0 -0
  453. {chython-3.0.1 → chython-3.1}/chython.egg-info/top_level.txt +0 -0
  454. {chython-3.0.1 → chython-3.1}/setup.cfg +0 -0
  455. {chython-3.0.1 → chython-3.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chython
3
- Version: 3.0.1
3
+ Version: 3.1
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
@@ -62,9 +62,13 @@ def canonicalize(molecule: MoleculeContainer, *, fix_tautomers: bool = True,
62
62
  needing the aromatic form.
63
63
 
64
64
  CHARGES ARE PAIRED OFF, not preserved atom by atom: glycine's zwitterion and its neutral drawing
65
- share a key, because step 4 runs `neutralize()`. The NET charge is untouched, so sodium acetate
66
- stays sodium acetate -- there is no proton in it to move -- while ammonium acetate becomes acetic
67
- acid and ammonia, both drawings of one salt.
65
+ share a key, because step 4 runs `neutralize()`. `neutralize()` leaves the NET charge untouched, so
66
+ sodium acetate stays sodium acetate -- there is no proton in it to move -- while ammonium acetate
67
+ becomes acetic acid and ammonia, both drawings of one salt.
68
+
69
+ One stage does move the net charge, `standardize()`'s organometallic completion: a zinc or magnesium
70
+ holding one carbon and no halide is charged rather than left as a neutral one-coordinate metal. It
71
+ is the only place in the pipeline where the total changes, and it says so in the log.
68
72
  """
69
73
  # A refusal at the answer boundary, which is the only place one belongs. `smiles()` returns
70
74
  # whichever container its string describes, so a `>>` in a structure column arrives here as a
@@ -0,0 +1,133 @@
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
+ """An organozinc or Grignard drawn apart from its halide: `CC[Zn+].[Cl-]` is one `CC[Zn]Cl`.
20
+
21
+ A ONE-COORDINATE ZINC OR MAGNESIUM HOLDING A CARBON IS AN INCOMPLETE DRAWING, and this stage completes
22
+ it two ways. A free halide in the record is the halide that belongs on the metal, so it is bonded there.
23
+ A metal left without one is charged `+`: the halide is missing from the drawing rather than from the
24
+ compound, and a neutral one-coordinate metal is not a species anybody meant. Both readings apply to
25
+ zinc and magnesium alike.
26
+
27
+ A `standardize()` stage rather than a `standardize_metals.tsv` row, and for one reason: a rule table
28
+ applies whichever match the isomorphism search returned first, so a drawing offering more than one
29
+ candidate would be settled by its atom order. Pairing is a question about all the candidates at once.
30
+
31
+ Two orderings answer it, and neither can see the input's spelling:
32
+
33
+ * **halides** by `Cl > Br > I > F`, a fact about the reagents rather than about the graph;
34
+ * **metals** by canonical rank, which is `_isomers._ranks` without the stripping step -- the bond being
35
+ placed is the one that is absent, so the molecule as it arrived already is the placement-free frame.
36
+
37
+ Ties are left tied. Two candidates of equal rank are automorphic, so which one is taken is not
38
+ observable in the result, and the sort being stable makes the arbitrary half of the choice cheap.
39
+ Charging needs no order at all, being one atom's charge and nothing else's.
40
+
41
+ NET CHARGE MOVES HERE, which no other `standardize()` stage does, and every spelling that moves it is
42
+ accepted: a lone `[Zn+]` beside a neutral halogen sits at `+1`, a neutral metal beside `[Cl-]` at `-1`,
43
+ and a charged metal with no halide leaves `0` for `+1`. A dropped sign is the premise of the stage, so
44
+ the record states which way the total went rather than the pass declining to move it.
45
+ """
46
+ from collections.abc import MutableSequence
47
+ from ..core import LogRecord, MoleculeContainer
48
+
49
+
50
+ __all__ = ['unite_organometallics']
51
+
52
+ #: Table-qualified, as every rule id must be -- never a bare index. Two and not one: bonding a halide
53
+ #: the record HAS and charging a metal whose halide the record LACKS are different claims about the
54
+ #: drawing, and a consumer filtering the log can decline the second while keeping the first.
55
+ _RULE = 'organometallics:unite'
56
+ _RULE_CHARGE = 'organometallics:charge'
57
+
58
+ #: `Cl > Br > I > F`, low wins. Chloride and bromide are the reagent halides; fluoride is last because
59
+ #: a free `[F-]` beside a magnesium is more often a separate salt than a bond somebody forgot to draw.
60
+ _PREFERENCE = {17: 0, 35: 1, 53: 2, 9: 3}
61
+
62
+ #: Zinc and magnesium only. Widening this is a chemical claim per element, not a constant to grow.
63
+ _METALS = frozenset({12, 30})
64
+
65
+
66
+ def _candidates(molecule: MoleculeContainer) -> tuple[list[int], list[int]]:
67
+ """The one-bonded metals holding a carbon, and the free halides, in arena order.
68
+
69
+ A metal is a candidate at charge `0` or `+1` and at exactly one single bond to carbon: two bonds
70
+ means it is already satisfied, and an alkoxide or amide is a different compound rather than a
71
+ reagent drawn apart. A halide is a candidate at charge `0` or `-1` with no bonds at all -- bonded,
72
+ it belongs to whatever it is bonded to. Radicals are nobody's dropped sign.
73
+ """
74
+ metals: list[int] = []
75
+ halides: list[int] = []
76
+ for atom in molecule.atoms():
77
+ n = atom.n
78
+ if atom.is_radical:
79
+ continue
80
+ if atom.element in _METALS:
81
+ if atom.degree == 1 and atom.charge in (0, 1):
82
+ m = next(iter(molecule.neighbors_of(n)))
83
+ if molecule.atom(m).element == 6 and molecule.order_of(n, m) == 1:
84
+ metals.append(n)
85
+ elif atom.element in _PREFERENCE and not atom.degree and atom.charge in (0, -1):
86
+ halides.append(n)
87
+ return metals, halides
88
+
89
+
90
+ def unite_organometallics(molecule: MoleculeContainer, log: MutableSequence) -> set[int]:
91
+ """Bond each candidate metal to one candidate halide, and charge whichever went without. Written ids.
92
+
93
+ Spare halides are left as drawn -- a second chloride beside a satisfied metal is a counterion -- so
94
+ only the metal side is completed either way.
95
+ """
96
+ metals, halides = _candidates(molecule)
97
+ if not metals:
98
+ return set()
99
+
100
+ pairs: list[tuple[int, int, int]] = []
101
+ if halides:
102
+ # before the edit scope, which answers reads from the pre-scope arena. `atoms_order` is the
103
+ # expensive word here, so it is asked for only when there is a pairing to decide.
104
+ ranks = molecule.atoms_order
105
+ metals.sort(key=lambda n: ranks[n])
106
+ halides.sort(key=lambda n: (_PREFERENCE[molecule.atom(n).element], molecule.atom(n).isotope,
107
+ ranks[n]))
108
+ pairs = [(n, m, molecule.charge_of(n) + molecule.charge_of(m)) for n, m in zip(metals, halides)]
109
+
110
+ # a metal that took a halide is neutral by the join; one that did not is charged, unless the drawing
111
+ # already said `+`. Ranking cannot matter to this half -- every leftover is treated alike.
112
+ bonded = {n for n, _, _ in pairs}
113
+ stranded = [n for n in metals if n not in bonded and not molecule.charge_of(n)]
114
+ if not pairs and not stranded:
115
+ return set()
116
+
117
+ with molecule.edit():
118
+ for n, m, _ in pairs:
119
+ molecule.add_bond(n, m, 1)
120
+ molecule.set_charge(n, 0)
121
+ molecule.set_charge(m, 0)
122
+ for n in stranded:
123
+ molecule.set_charge(n, 1)
124
+
125
+ for n, m, before in pairs:
126
+ moved = f', and the net charge of the pair moved {before:+d} -> 0' if before else ''
127
+ log.append(LogRecord(_RULE, (n, m), f'atoms {n} and {m} are one organometallic reagent drawn '
128
+ f'apart; joined by a single bond{moved}'))
129
+ for n in stranded:
130
+ log.append(LogRecord(_RULE_CHARGE, (n,), f'atom {n} holds one carbon and no halide; charged +1, '
131
+ f'the halide being absent from the drawing rather than '
132
+ f'from the compound, so the net charge moves 0 -> +1'))
133
+ return bonded | {m for _, m, _ in pairs} | set(stranded)
@@ -22,9 +22,15 @@ A repair pass the caller asks for -- no reader or writer runs it. The patch lan
22
22
  charge delta and an optional absolute radical flag per matched atom, plus a new order in {1,2,3,8}
23
23
  per matched bond; no rule touches the atom or bond set, an isotope, an aromatic order or a hydrogen
24
24
  count. A patch is validated in full before any of it is written.
25
+
26
+ `standardize()` closes with one stage that is not a table row, `_organometallics`: completing a
27
+ one-coordinate zinc or magnesium adds a bond, and which halide joins which metal is a question about
28
+ every candidate at once, neither of which a rule table can state. It is also the one stage that moves
29
+ net charge, a metal nobody drew a halide for being charged instead.
25
30
  """
26
31
  from collections.abc import MutableSequence
27
32
  from ._implicit import calc_implicit
33
+ from ._organometallics import unite_organometallics
28
34
  from ._tables import Rule, groups_rules, metals_rules
29
35
  from ..core import LogRecord, MoleculeContainer, recording
30
36
 
@@ -128,7 +134,9 @@ def standardize(molecule: MoleculeContainer, *, fix_hydrogens: bool = True,
128
134
  fix_tautomers: bool = True) -> bool:
129
135
  """Repair mis-drawn functional groups and metal-organic bonding in place. Did anything change?
130
136
 
131
- Runs the functional-group rules and then the metal-organic ones, then recomputes the implicit
137
+ Runs the functional-group rules, then the metal-organic ones, then completes any organozinc or
138
+ Grignard that arrived one-coordinate -- bonding a free halide to it, or charging it when the drawing
139
+ offers none -- then recomputes the implicit
132
140
  hydrogen count of every atom a patch wrote -- charge, radical state and bond order all change what
133
141
  the valence collection gives an atom. `fix_hydrogens=False` skips that recompute, for a caller about
134
142
  to kekulise anyway. `molecule.log` takes a record per patch applied and per patch refused.
@@ -141,6 +149,9 @@ def standardize(molecule: MoleculeContainer, *, fix_hydrogens: bool = True,
141
149
  with recording(molecule, stage='standardize') as lg:
142
150
  written = _pass(molecule, groups_rules(), lg, fix_tautomers)
143
151
  written |= _pass(molecule, metals_rules(), lg, fix_tautomers)
152
+ # last, and the order is not observable: no `metals:` row matches a sigma metal-carbon bond, so
153
+ # none of them can see either the ion pair this reads or the covalent form it writes.
154
+ written |= unite_organometallics(molecule, lg)
144
155
  if not written:
145
156
  return False
146
157
  if fix_hydrogens:
@@ -87,6 +87,7 @@ salts:benzoic counterion OC(=O)c1ccccc1 - benzoic
87
87
  salts:salicylic counterion OC(=O)c1ccccc1O - salicylic
88
88
  salts:gentisic counterion OC(=O)c1cc(O)ccc1O - gentisic
89
89
  salts:xinafoic counterion OC(=O)c1c(O)ccc2ccccc12 - 1-hydroxy-2-naphthoic (xinafoic)
90
+ salts:oxynaphthoic counterion OC(=O)c1cc2ccccc2cc1O - 3-hydroxy-2-naphthoic (beta-oxynaphthoic, BON). Same formula as `salts:xinafoic` and a different constitution, so the key mechanism keeps them apart and both are needed
90
91
  salts:pamoic counterion OC(=O)c1c(O)c(Cc2c(O)c(C(O)=O)c3ccccc3c2)cc4ccccc14 - pamoic (embonic)
91
92
  salts:hippuric counterion OC(=O)CNC(=O)c1ccccc1 - hippuric
92
93
  salts:nicotinic counterion OC(=O)c1cccnc1 - nicotinic
@@ -18,6 +18,13 @@
18
18
  #
19
19
  # The examples are one metal with one ligand each. Nine of the 19 rules are also exercised by real drawings,
20
20
  # in `test/_corpus.py`.
21
+ #
22
+ # NO ROW HERE COMPLETES A ONE-COORDINATE ORGANOZINC OR GRIGNARD, and none can: `bonds_fix` changes the order
23
+ # of a bond that exists, and which of several free halides joins which metal is a question about all the
24
+ # candidates at once rather than about one match. `_organometallics.py` is that stage, run by `standardize()`
25
+ # after this table -- it bonds a free halide to the metal, or charges the metal when the drawing offers none.
26
+ # Its direction is the opposite of these 19 rows and does not compete with them: they break a DATIVE contact
27
+ # from a lone pair, it makes a polar covalent sigma metal-carbon bond.
21
28
 
22
29
  id smarts atom_fix bonds_fix tautomer after examples why
23
30
  metals:00 [M;*;^,!^:1]=[C:2]-1-[N;D3;x0;z1:3]-[C;z2:5]-,=[C;z2:6]-[N;D3;x0;z1:4]-1 2:-1:-;3:1:- 1:2:8;2:3:2 0 - C=1N(C)C(=[Cu]I)N(C)C=1>>[C-]=1(N(C)C=C[N+]=1C)~[Cu]I An N-heterocyclic carbene drawn with a covalent M=C double bond; the carbene carbon becomes the [C-] that donates to the metal and the imidazolium charge is restored on a ring nitrogen
@@ -0,0 +1,219 @@
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
+ """Completing a one-coordinate organozinc or Grignard: `[Zn+].[Cl-]` joins, and a halide-less one charges.
20
+
21
+ The invariant under test is that THE RESULT DOES NOT DEPEND ON INPUT ATOM ORDER. Two orderings settle
22
+ it -- halides by `Cl > Br > I > F`, metals by canonical rank -- and every test that could see an order
23
+ effect permutes its input and demands one answer. Charging needs no order, so what is tested there is
24
+ that net charge moves, that it moves once, and that it is a record of its own.
25
+ """
26
+ # `__all__` and not the package object: `from ... import chemistry` would execute the facade, which
27
+ # `test_dependency_direction.py` ratchets against.
28
+ from itertools import permutations
29
+ from .. import canonicalize, standardize
30
+ from ...core import read_smiles as smiles
31
+
32
+
33
+ def _std(s):
34
+ """`standardize()` then a canonical string, so two spellings of one answer compare equal."""
35
+ m = smiles(s)
36
+ standardize(m)
37
+ m.canonicalize()
38
+ return format(m, '')
39
+
40
+
41
+ def _one(spellings):
42
+ """The single canonical answer every spelling gives, or an assertion naming the ones that differ."""
43
+ got = {s: _std(s) for s in spellings}
44
+ assert len(set(got.values())) == 1, got
45
+ return next(iter(got.values()))
46
+
47
+
48
+ # what gets joined
49
+
50
+
51
+ def test_every_charge_spelling_of_a_drawn_apart_reagent_reaches_the_covalent_form():
52
+ """All four spellings are the same reagent: ethylzinc chloride, and phenylmagnesium bromide.
53
+
54
+ Two of the four do not conserve net charge -- a lone `[Zn+]` beside a neutral halogen is at `+1`, a
55
+ neutral metal beside `[Cl-]` at `-1` -- and both are joined anyway, the premise being that the
56
+ drawing dropped a sign rather than that a cation and a radical were meant.
57
+ """
58
+ assert _one(['CC[Zn+].[Cl-]', 'CC[Zn+].[Cl]', 'CC[Zn].[Cl-]', 'CC[Zn].[Cl]']) == _std('CC[Zn]Cl')
59
+ assert _one(['c1ccccc1[Mg+].[Br-]', 'c1ccccc1[Mg].[Br-]']) == _std('c1ccccc1[Mg]Br')
60
+
61
+
62
+ def test_all_four_halogens_are_joined():
63
+ for x in ('F', 'Cl', 'Br', 'I'):
64
+ assert _std(f'CC[Zn+].[{x}-]') == _std(f'CC[Zn]{x}'), x
65
+
66
+
67
+ def test_the_joined_form_is_a_fixed_point_and_a_plain_molecule_is_untouched():
68
+ assert _std('CC[Zn]Cl') == _std(_std('CC[Zn]Cl'))
69
+ m = smiles('c1ccccc1O')
70
+ assert not standardize(m)
71
+
72
+
73
+ # what is left alone
74
+
75
+
76
+ def test_only_a_one_bonded_metal_holding_a_carbon_takes_a_halide():
77
+ for s in ('C[Zn](C)C.[Cl-]', # already two-coordinate: no room
78
+ 'CO[Zn].[Cl-]', # bonded to oxygen, not to carbon: an alkoxide, not a reagent
79
+ 'CC[Zn]Cl.[Cl-]', # the metal is satisfied; the second chloride is a counterion
80
+ 'CC[Zn+].C[Cl]', # the halogen is bonded, so it is not a free halide
81
+ 'CC[Cu+].[Cl-]'): # not Zn or Mg
82
+ m = smiles(s)
83
+ before = format(m, '')
84
+ standardize(m)
85
+ assert format(m, '') == before, s
86
+
87
+
88
+ def test_a_metal_with_no_halide_anywhere_is_charged_instead():
89
+ """A neutral one-coordinate metal is not a species: the halide is missing from the DRAWING.
90
+
91
+ Zinc and magnesium alike, and it is the one thing `standardize()` does that moves net charge.
92
+ """
93
+ for s, expect in [('CC[Zn]', 'CC[Zn+]'), ('CC[Mg]', 'CC[Mg+]'),
94
+ ('c1ccccc1[Mg]', 'c1ccccc1[Mg+]')]:
95
+ m = smiles(s)
96
+ assert standardize(m), s
97
+ assert _std(s) == _std(expect), s
98
+ assert sum(m.charge_of(n) for n in m) == 1, s
99
+ assert not m.check_valence(), s
100
+
101
+
102
+ def test_a_metal_the_drawing_already_charged_is_not_charged_twice():
103
+ m = smiles('CC[Zn+]')
104
+ assert not standardize(m)
105
+ assert sum(m.charge_of(n) for n in m) == 1
106
+
107
+
108
+ def test_a_metal_left_over_when_the_halides_run_short_is_charged():
109
+ """One chloride between two reagents: rank decides who takes it, and the other is charged."""
110
+ assert _one(['C[Zn+].CC[Zn].[Cl-]', '[Cl-].CC[Zn].C[Zn+]', 'CC[Zn].[Cl-].C[Zn+]'])
111
+
112
+
113
+ # the two orderings, each proved by permuting the input
114
+
115
+
116
+ def test_a_mixed_halide_set_is_decided_by_element_and_not_by_input_order():
117
+ """`Cl > Br > I > F`: the metal takes the preferred halogen and the rest stay as counterions."""
118
+ assert _one(['CC[Zn+].[Cl-].[Br-]', 'CC[Zn+].[Br-].[Cl-]',
119
+ '[Br-].[Cl-].CC[Zn+]']) == _std('CC[Zn]Cl.[Br-]')
120
+ assert _one(['CC[Zn+].[Br-].[I-]', '[I-].CC[Zn+].[Br-]']) == _std('CC[Zn]Br.[I-]')
121
+ assert _one(['CC[Zn+].[I-].[F-]', '[F-].[I-].CC[Zn+]']) == _std('CC[Zn]I.[F-]')
122
+
123
+
124
+ def test_interchangeable_halides_give_one_answer_whichever_is_picked():
125
+ """Two identical chlorides are the same choice twice, so the tie needs no breaking."""
126
+ assert _one(['CC[Zn+].[Cl-].[Cl-]', '[Cl-].CC[Zn+].[Cl-]',
127
+ '[Cl-].[Cl-].CC[Zn+]']) == _std('CC[Zn]Cl.[Cl-]')
128
+
129
+
130
+ def test_which_metal_takes_the_single_halide_is_decided_by_canonical_rank():
131
+ """Two different reagents drawn with one chloride between them: rank picks, and picks the same way.
132
+
133
+ This is the case that cannot be a rule-table row -- the table applies whichever match the
134
+ isomorphism search returned first, which is what the input order decides.
135
+ """
136
+ assert _one(['C[Zn+].CC[Zn+].[Cl-]', 'CC[Zn+].C[Zn+].[Cl-]', '[Cl-].CC[Zn+].C[Zn+]',
137
+ 'C[Zn+].[Cl-].CC[Zn+]'])
138
+
139
+
140
+ def test_equivalent_metals_are_automorphic_so_the_tie_is_not_observable():
141
+ assert _one(['C[Zn+].C[Zn+].[Cl-]', '[Cl-].C[Zn+].C[Zn+]', 'C[Zn+].[Cl-].C[Zn+]'])
142
+
143
+
144
+ def test_several_metals_and_several_halides_are_paired_one_each():
145
+ """Two reagents, two chlorides: each metal takes one, and no permutation changes the pairing."""
146
+ assert _one([f'{a}.{b}.{c}.{d}' for a, b, c, d in
147
+ permutations(['C[Zn+]', 'CC[Zn+]', '[Cl-]', '[Cl-]'])]) == _std('C[Zn]Cl.CC[Zn]Cl')
148
+
149
+
150
+ # what the join is worth downstream
151
+
152
+
153
+ def test_the_joined_form_is_the_one_the_reaction_corpus_names():
154
+ """The ion pair answers with the generic carbanion; joined, each reagent names itself."""
155
+ for s, group in [('CC[Zn+].[Cl-]', 'alkyl_zinc'), ('c1ccccc1[Mg+].[Br-]', 'aryl_grignard')]:
156
+ m = smiles(s)
157
+ assert 'metalate_carbanion' in m.functional_groups()
158
+ standardize(m)
159
+ assert group in m.functional_groups(), s
160
+
161
+
162
+ def test_the_join_leaves_a_clean_valence_and_recomputes_the_hydrogen_count():
163
+ for s in ('CC[Zn+].[Cl-]', 'CC[Zn].[Cl]', 'c1ccccc1[Mg+].[Br-]'):
164
+ m = smiles(s)
165
+ standardize(m)
166
+ assert not m.check_valence(), s
167
+
168
+
169
+ # the record
170
+
171
+
172
+ def test_the_join_is_recorded_against_a_table_qualified_id():
173
+ m = smiles('CC[Zn+].[Cl-]')
174
+ standardize(m)
175
+ records = [r for r in m.log if r.rule.startswith('organometallics:')]
176
+ assert len(records) == 1, [r.rule for r in m.log]
177
+ assert records[0].rule == 'organometallics:unite'
178
+ assert m.log.by_stage('standardize')
179
+
180
+
181
+ def test_charging_a_stranded_metal_is_a_separate_record_from_joining_one():
182
+ """A consumer can decline the weaker claim -- the halide nobody drew -- and keep the join."""
183
+ m = smiles('CC[Zn]')
184
+ standardize(m)
185
+ records = [r for r in m.log if r.rule.startswith('organometallics:')]
186
+ assert [r.rule for r in records] == ['organometallics:charge']
187
+ assert 'no halide' in records[0].message
188
+
189
+ both = smiles('C[Zn].CC[Zn].[Cl-]')
190
+ standardize(both)
191
+ assert {r.rule for r in both.log if r.rule.startswith('organometallics:')} == \
192
+ {'organometallics:unite', 'organometallics:charge'}
193
+
194
+ # and the two halves compose to one answer: whether the leftover metal arrived neutral or already
195
+ # `+`, the halide goes to the same reagent and the other ends up charged either way.
196
+ assert _std('C[Zn].CC[Zn].[Cl-]') == _std('C[Zn+].CC[Zn].[Cl-]') == _std('CC[Zn]Cl.C[Zn+]')
197
+
198
+
199
+ def test_a_join_that_moves_net_charge_says_so_and_one_that_does_not_stays_quiet_about_it():
200
+ def charge(m):
201
+ return sum(m.charge_of(n) for n in m)
202
+
203
+ conserving, moving = smiles('CC[Zn+].[Cl-]'), smiles('CC[Zn+].[Cl]')
204
+ assert charge(conserving) == 0 and charge(moving) == 1
205
+ for m in (conserving, moving):
206
+ standardize(m)
207
+ assert charge(conserving) == 0 and charge(moving) == 0
208
+ said = [r for r in moving.log if r.rule == 'organometallics:unite'][0].message
209
+ assert 'charge' in said
210
+ assert 'charge' not in [r for r in conserving.log if r.rule == 'organometallics:unite'][0].message
211
+
212
+
213
+ # the pipeline
214
+
215
+
216
+ def test_canonicalize_joins_it_too():
217
+ m = smiles('CC[Zn+].[Cl-]')
218
+ canonicalize(m)
219
+ assert format(m, '') == _std('CC[Zn]Cl')