synkit 1.3.1a0__tar.gz → 1.3.2b1__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 (286) hide show
  1. {synkit-1.3.1a0 → synkit-1.3.2b1}/.gitignore +10 -1
  2. {synkit-1.3.1a0 → synkit-1.3.2b1}/PKG-INFO +1 -1
  3. {synkit-1.3.1a0 → synkit-1.3.2b1}/pyproject.toml +1 -1
  4. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_parse.py +1 -1
  5. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/atom_features.py +0 -1
  6. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/standardize.py +56 -8
  7. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/tautomerize.py +50 -7
  8. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/canon_graph.py +2 -2
  9. synkit-1.3.2b1/synkit/Graph/FG/__init__.py +19 -0
  10. synkit-1.3.2b1/synkit/Graph/FG/api.py +39 -0
  11. synkit-1.3.2b1/synkit/Graph/FG/audit.py +155 -0
  12. synkit-1.3.2b1/synkit/Graph/FG/catalog.py +1168 -0
  13. synkit-1.3.2b1/synkit/Graph/FG/detector.py +288 -0
  14. synkit-1.3.2b1/synkit/Graph/FG/model.py +97 -0
  15. synkit-1.3.2b1/synkit/Graph/FG/ring_system.py +154 -0
  16. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/_misc.py +78 -7
  17. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_construction.py +14 -1
  18. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_destruction.py +25 -3
  19. synkit-1.3.2b1/synkit/Graph/ITS/its_expand.py +444 -0
  20. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_reverter.py +7 -0
  21. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/rc_extractor.py +24 -2
  22. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/mtg.py +377 -39
  23. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/graph_matcher.py +34 -15
  24. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/subgraph_matcher.py +127 -11
  25. synkit-1.3.2b1/synkit/Graph/Mech/conversion.py +1305 -0
  26. synkit-1.3.2b1/synkit/Graph/Mech/electron_accounting.py +64 -0
  27. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/canon_graph.py +12 -3
  28. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/utils.py +31 -1
  29. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/chem_converter.py +85 -10
  30. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/graph_to_mol.py +2 -0
  31. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/mol_to_graph.py +88 -38
  32. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/rule_matcher.py +26 -3
  33. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/syn_rule.py +135 -28
  34. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/imba_engine.py +10 -0
  35. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/partial_engine.py +15 -1
  36. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/rbl_engine.py +22 -1
  37. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/rule_filter.py +11 -1
  38. synkit-1.3.2b1/synkit/Synthesis/Reactor/syn_reactor.py +1429 -0
  39. synkit-1.3.2b1/synkit/Utils/__init__.py +0 -0
  40. synkit-1.3.2b1/synkit/Vis/__init__.py +47 -0
  41. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/graph_visualizer.py +1 -1
  42. synkit-1.3.2b1/synkit/Vis/its_drawer.py +615 -0
  43. synkit-1.3.2b1/synkit/Vis/molecule_drawer.py +565 -0
  44. synkit-1.3.2b1/synkit/Vis/reaction_drawer.py +285 -0
  45. synkit-1.3.2b1/synkit/Vis/rxn_vis.py +532 -0
  46. synkit-1.3.2b1/synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
  47. synkit-1.3.2b1/synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
  48. synkit-1.3.2b1/synkit/Vis/vis_synedu/rxn_vis.py +382 -0
  49. synkit-1.3.2b1/synkit/Vis/vis_synedu/vis.py +501 -0
  50. synkit-1.3.2b1/synkit/Vis/visual_drawer.py +215 -0
  51. synkit-1.3.2b1/synkit/Vis/visual_model.py +520 -0
  52. synkit-1.3.1a0/synkit/Graph/ITS/its_expand.py +0 -86
  53. synkit-1.3.1a0/synkit/Graph/MTG/group_comp.py +0 -157
  54. synkit-1.3.1a0/synkit/Graph/MTG/groupoid.py +0 -358
  55. synkit-1.3.1a0/synkit/Synthesis/Reactor/syn_reactor.py +0 -638
  56. synkit-1.3.1a0/synkit/Vis/__init__.py +0 -5
  57. synkit-1.3.1a0/synkit/Vis/rxn_vis.py +0 -159
  58. {synkit-1.3.1a0 → synkit-1.3.2b1}/LICENSE +0 -0
  59. {synkit-1.3.1a0 → synkit-1.3.2b1}/README.md +0 -0
  60. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/__init__.py +0 -0
  61. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/crn.py +0 -0
  62. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/mod_crn.py +0 -0
  63. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/syncrn.py +0 -0
  64. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/__init__.py +0 -0
  65. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/abstract.py +0 -0
  66. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/arity.py +0 -0
  67. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/builder.py +0 -0
  68. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/derivation.py +0 -0
  69. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/flattener.py +0 -0
  70. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/keys.py +0 -0
  71. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/mixtures.py +0 -0
  72. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/smiles.py +0 -0
  73. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/state.py +0 -0
  74. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/strategy.py +0 -0
  75. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/worker.py +0 -0
  76. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/__init__.py +0 -0
  77. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/_adapter.py +0 -0
  78. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/pathfinder.py +0 -0
  79. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/reachability.py +0 -0
  80. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/realizability.py +0 -0
  81. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/__init__.py +0 -0
  82. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/analyzer.py +0 -0
  83. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/net.py +0 -0
  84. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/persistence.py +0 -0
  85. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/semiflows.py +0 -0
  86. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/structure.py +0 -0
  87. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/__init__.py +0 -0
  88. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/dynamics.py +0 -0
  89. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/helper.py +0 -0
  90. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/stoich.py +0 -0
  91. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/thermo.py +0 -0
  92. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/__init__.py +0 -0
  93. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_api.py +0 -0
  94. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_extract.py +0 -0
  95. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_impute.py +0 -0
  96. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/__init__.py +0 -0
  97. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/reaction.py +0 -0
  98. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/rule.py +0 -0
  99. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/species.py +0 -0
  100. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/syncrn.py +0 -0
  101. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/__init__.py +0 -0
  102. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/_common.py +0 -0
  103. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/_ir.py +0 -0
  104. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/automorphism.py +0 -0
  105. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/canon.py +0 -0
  106. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/isomorphism.py +0 -0
  107. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/symmetry.py +0 -0
  108. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/wl_canon.py +0 -0
  109. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/__init__.py +0 -0
  110. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/crn_vis.py +0 -0
  111. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/labels.py +0 -0
  112. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/layout.py +0 -0
  113. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/palette.py +0 -0
  114. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/validation.py +0 -0
  115. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/vis.py +0 -0
  116. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/__init__.py +0 -0
  117. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/__init__.py +0 -0
  118. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/backend.py +0 -0
  119. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/conversion.py +0 -0
  120. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/hyperedge.py +0 -0
  121. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/hypergraph.py +0 -0
  122. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/rxn.py +0 -0
  123. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/__init__.py +0 -0
  124. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/automorphism.py +0 -0
  125. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/canon.py +0 -0
  126. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/wl_canon.py +0 -0
  127. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/__init__.py +0 -0
  128. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/configs/__init__.py +0 -0
  129. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/configs/loader.py +0 -0
  130. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/configs/models.py +0 -0
  131. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/constants.py +0 -0
  132. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/crn_formula.py +0 -0
  133. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/deficiency.py +0 -0
  134. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/enumerator.py +0 -0
  135. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/exceptions.py +0 -0
  136. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/explorer.py +0 -0
  137. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/helpers.py +0 -0
  138. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/injectivity.py +0 -0
  139. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/motif.py +0 -0
  140. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/network.py +0 -0
  141. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/pathway.py +0 -0
  142. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/properties.py +0 -0
  143. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/reaction.py +0 -0
  144. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/utils.py +0 -0
  145. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/viz.py +0 -0
  146. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Cluster/__init__.py +0 -0
  147. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Cluster/butina.py +0 -0
  148. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/__init__.py +0 -0
  149. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
  150. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
  151. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/transformation_fp.py +0 -0
  152. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/__init__.py +0 -0
  153. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/descriptors.py +0 -0
  154. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/formula.py +0 -0
  155. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/graph_annotator.py +0 -0
  156. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/standardize.py +0 -0
  157. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/valence.py +0 -0
  158. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/Mapper/__init__.py +0 -0
  159. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/Mapper/wl_mapper.py +0 -0
  160. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/__init__.py +0 -0
  161. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/aam_validator.py +0 -0
  162. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/balance_check.py +0 -0
  163. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
  164. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/cleaning.py +0 -0
  165. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/deionize.py +0 -0
  166. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/fix_aam.py +0 -0
  167. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/neutralize.py +0 -0
  168. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/radical_wildcard.py +0 -0
  169. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/__init__.py +0 -0
  170. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/utils.py +0 -0
  171. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Data/__init__.py +0 -0
  172. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Data/gen_partial_aam.py +0 -0
  173. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/__init__.py +0 -0
  174. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/canon_algs.py +0 -0
  175. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/nauty.py +0 -0
  176. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Context/__init__.py +0 -0
  177. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Context/hier_context.py +0 -0
  178. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Context/radius_expand.py +0 -0
  179. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/Descriptors/topology.py +0 -0
  180. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
  181. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +0 -0
  182. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/__init__.py +0 -0
  183. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/graph_descriptors.py +0 -0
  184. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/graph_fps.py +0 -0
  185. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/graph_signature.py +0 -0
  186. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/hash_fps.py +0 -0
  187. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/morgan_fps.py +0 -0
  188. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/path_fps.py +0 -0
  189. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/wl_hash.py +0 -0
  190. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/__init__.py +0 -0
  191. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
  192. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/hextend.py +0 -0
  193. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/__init__.py +0 -0
  194. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_builder.py +0 -0
  195. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_decompose.py +0 -0
  196. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_relabel.py +0 -0
  197. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/normalize_aam.py +0 -0
  198. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/partial_its.py +0 -0
  199. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/__init__.py +0 -0
  200. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/mcs_matcher.py +0 -0
  201. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/mtg_explore.py +0 -0
  202. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/utils.py +0 -0
  203. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/__init__.py +0 -0
  204. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/approx_mcs.py +0 -0
  205. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/auto_est.py +0 -0
  206. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/automorphism.py +0 -0
  207. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/batch_cluster.py +0 -0
  208. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/dedup_matches.py +0 -0
  209. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/graph_cluster.py +0 -0
  210. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/graph_morphism.py +0 -0
  211. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/mcs_matcher.py +0 -0
  212. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/multi_turbo_iso.py +0 -0
  213. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/orbit.py +0 -0
  214. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/partial_matcher.py +0 -0
  215. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/sing.py +0 -0
  216. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/turbo_iso.py +0 -0
  217. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/wl_sel.py +0 -0
  218. {synkit-1.3.1a0/synkit/Graph/Wildcard → synkit-1.3.2b1/synkit/Graph/Mech}/__init__.py +0 -0
  219. {synkit-1.3.1a0/synkit/Rule/Apply → synkit-1.3.2b1/synkit/Graph/Wildcard}/__init__.py +0 -0
  220. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/fuse_graph.py +0 -0
  221. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/graph_wc.py +0 -0
  222. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/its_merge.py +0 -0
  223. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/radwc.py +0 -0
  224. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/wc_matcher.py +0 -0
  225. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/wildcard.py +0 -0
  226. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/__init__.py +0 -0
  227. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/syn_graph.py +0 -0
  228. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/__init__.py +0 -0
  229. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/__init__.py +0 -0
  230. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/gml_to_graph.py +0 -0
  231. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/graph_to_gml.py +0 -0
  232. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/graph_to_smarts.py +0 -0
  233. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/smarts_expander.py +0 -0
  234. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/smarts_generalizer.py +0 -0
  235. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/smarts_to_graph.py +0 -0
  236. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/data_io.py +0 -0
  237. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/data_process.py +0 -0
  238. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/debug.py +0 -0
  239. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/dg_to_gml.py +0 -0
  240. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/gml_to_nx.py +0 -0
  241. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/nx_to_gml.py +0 -0
  242. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/smiles_to_id.py +0 -0
  243. {synkit-1.3.1a0/synkit/Rule/Compose → synkit-1.3.2b1/synkit/Rule/Apply}/__init__.py +0 -0
  244. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/reactor_rule.py +0 -0
  245. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/retro_reactor.py +0 -0
  246. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/rule_rbl.py +0 -0
  247. {synkit-1.3.1a0/synkit/Rule/Modify → synkit-1.3.2b1/synkit/Rule/Compose}/__init__.py +0 -0
  248. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/compose_rule.py +0 -0
  249. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/rule_compose.py +0 -0
  250. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/rule_mapping.py +0 -0
  251. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/seq_comp.py +0 -0
  252. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/valence_constrain.py +0 -0
  253. {synkit-1.3.1a0/synkit/Synthesis/MSR → synkit-1.3.2b1/synkit/Rule/Modify}/__init__.py +0 -0
  254. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/implict_rule.py +0 -0
  255. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/longest_path.py +0 -0
  256. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/molecule_rule.py +0 -0
  257. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/prune_templates.py +0 -0
  258. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/rule_utils.py +0 -0
  259. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/strip_rule.py +0 -0
  260. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/__init__.py +0 -0
  261. {synkit-1.3.1a0/synkit/Synthesis/Metrics → synkit-1.3.2b1/synkit/Synthesis/MSR}/__init__.py +0 -0
  262. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/MSR/multi_steps.py +0 -0
  263. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/MSR/path_finder.py +0 -0
  264. {synkit-1.3.1a0/synkit/Synthesis/Reactor → synkit-1.3.2b1/synkit/Synthesis/Metrics}/__init__.py +0 -0
  265. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Metrics/_base.py +0 -0
  266. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Metrics/_plot.py +0 -0
  267. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Metrics/_ranking.py +0 -0
  268. {synkit-1.3.1a0/synkit/Synthesis → synkit-1.3.2b1/synkit/Synthesis/Reactor}/__init__.py +0 -0
  269. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/batch_reactor.py +0 -0
  270. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/benchmark.py +0 -0
  271. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
  272. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
  273. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/post_syn.py +0 -0
  274. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
  275. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/strategy.py +0 -0
  276. {synkit-1.3.1a0/synkit/Utils → synkit-1.3.2b1/synkit/Synthesis}/__init__.py +0 -0
  277. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/reactor_utils.py +0 -0
  278. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Utils/utils.py +0 -0
  279. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/chemical_space.py +0 -0
  280. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/crn_vis.py +0 -0
  281. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/embedding.py +0 -0
  282. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/pdf_writer.py +0 -0
  283. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/rule_vis.py +0 -0
  284. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/__init__.py +0 -0
  285. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/examples.py +0 -0
  286. {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/version.py +0 -0
@@ -27,4 +27,13 @@ crn/*
27
27
  test_run.py
28
28
  Data/Study/CRN/case_formose/*
29
29
 
30
- synkit/Graph/dev/*
30
+ synkit/Graph/dev/*
31
+ *.ipynb
32
+ *.json
33
+ *.txt
34
+ *.log
35
+ sprint/
36
+ test_syn.py
37
+ .gitignore
38
+ measure_candidate_stages.py
39
+ run_valid_bug_cases.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synkit
3
- Version: 1.3.1a0
3
+ Version: 1.3.2b1
4
4
  Summary: Utility for reaction modeling using graph grammar
5
5
  Project-URL: homepage, https://github.com/TieuLongPhan/SynKit
6
6
  Project-URL: source, https://github.com/TieuLongPhan/SynKit
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "synkit"
7
- version = "1.3.1a"
7
+ version = "1.3.2b1"
8
8
  description = "Utility for reaction modeling using graph grammar"
9
9
  readme = "README.md"
10
10
  long-description = { file = "CHANGELOG.md" }
@@ -426,7 +426,7 @@ def parse_module_reaction_directions(
426
426
  in_reaction = False
427
427
  for line in text.splitlines():
428
428
  if line.startswith("REACTION"):
429
- payload = line[len("REACTION"):].strip()
429
+ payload = line[len("REACTION") :].strip() # noqa
430
430
  in_reaction = True
431
431
  elif in_reaction and (line.startswith(" ") or line.startswith("\t")):
432
432
  payload = line.strip()
@@ -192,7 +192,6 @@ class AtomFeatureExtractor:
192
192
  "partial_charge": round(float(gcharge), 3),
193
193
  "hybridization": str(atom.GetHybridization()),
194
194
  "in_ring": bool(atom.IsInRing()),
195
- "implicit_hcount": int(atom.GetNumImplicitHs()),
196
195
  "neighbors": neighbor_symbols,
197
196
  "atom_map": atom_map,
198
197
  }
@@ -72,7 +72,34 @@ class Standardize:
72
72
  return valid
73
73
 
74
74
  @staticmethod
75
- def standardize_rsmi(rsmi: str, stereo: bool = False) -> Optional[str]:
75
+ def _parse_molecule_fragments(
76
+ smiles_list: List[str],
77
+ ) -> Tuple[List[Chem.Mol], bool]:
78
+ """Parse and sanitize SMILES fragments.
79
+
80
+ :param smiles_list: List of SMILES strings to validate.
81
+ :type smiles_list: List[str]
82
+ :returns: Tuple of valid molecules and whether any fragment was invalid.
83
+ :rtype: Tuple[List[rdkit.Chem.Mol], bool]
84
+ """
85
+ valid: List[Chem.Mol] = []
86
+ had_invalid = False
87
+ for smi in smiles_list:
88
+ mol = Chem.MolFromSmiles(smi, sanitize=False)
89
+ if mol:
90
+ try:
91
+ Chem.SanitizeMol(mol)
92
+ valid.append(mol)
93
+ except Exception:
94
+ had_invalid = True
95
+ else:
96
+ had_invalid = True
97
+ return valid, had_invalid
98
+
99
+ @staticmethod
100
+ def standardize_rsmi(
101
+ rsmi: str, stereo: bool = False, remove_invalid: bool = True
102
+ ) -> Optional[str]:
76
103
  """
77
104
  Normalize a reaction SMILES: validate molecules, sort fragments, optionally keep stereo.
78
105
 
@@ -80,6 +107,10 @@ class Standardize:
80
107
  :type rsmi: str
81
108
  :param stereo: If True, include stereochemistry in the output. Defaults to False.
82
109
  :type stereo: bool
110
+ :param remove_invalid: If True, drop invalid fragments and standardize
111
+ remaining molecules. If False, return None when any invalid fragment
112
+ exists. Defaults to True.
113
+ :type remove_invalid: bool
83
114
  :returns: Standardized reaction SMILES or None if no valid molecules remain.
84
115
  :rtype: Optional[str]
85
116
  :raises ValueError: If the input format is invalid.
@@ -91,8 +122,15 @@ class Standardize:
91
122
  "Invalid reaction SMILES format. Expected 'reactants>>products'."
92
123
  )
93
124
 
94
- react_mols = Standardize.filter_valid_molecules(react_str.split("."))
95
- prod_mols = Standardize.filter_valid_molecules(prod_str.split("."))
125
+ react_mols, react_invalid = Standardize._parse_molecule_fragments(
126
+ react_str.split(".")
127
+ )
128
+ prod_mols, prod_invalid = Standardize._parse_molecule_fragments(
129
+ prod_str.split(".")
130
+ )
131
+
132
+ if not remove_invalid and (react_invalid or prod_invalid):
133
+ return None
96
134
 
97
135
  if not react_mols or not prod_mols:
98
136
  return None
@@ -107,7 +145,11 @@ class Standardize:
107
145
  return f"{sorted_react}>>{sorted_prod}"
108
146
 
109
147
  def fit(
110
- self, rsmi: str, remove_aam: bool = True, ignore_stereo: bool = True
148
+ self,
149
+ rsmi: str,
150
+ remove_aam: bool = True,
151
+ ignore_stereo: bool = True,
152
+ remove_invalid: bool = True,
111
153
  ) -> Optional[str]:
112
154
  """
113
155
  Full standardization pipeline: strip atom‑mapping, normalize SMILES, fix hydrogen notation.
@@ -118,16 +160,22 @@ class Standardize:
118
160
  :type remove_aam: bool
119
161
  :param ignore_stereo: If True, drop stereochemistry. Defaults to True.
120
162
  :type ignore_stereo: bool
163
+ :param remove_invalid: If True, drop invalid fragments and standardize
164
+ remaining molecules. If False, return None when any invalid fragment
165
+ exists. Defaults to True.
166
+ :type remove_invalid: bool
121
167
  :returns: The standardized reaction SMILES, or None if standardization fails.
122
168
  :rtype: Optional[str]
123
169
  """
124
- if remove_aam:
125
- rsmi = self.remove_atom_mapping(rsmi)
126
-
127
- std = self.standardize_rsmi(rsmi, stereo=not ignore_stereo)
170
+ std = self.standardize_rsmi(
171
+ rsmi, stereo=not ignore_stereo, remove_invalid=remove_invalid
172
+ )
128
173
  if std is None:
129
174
  return None
130
175
 
176
+ if remove_aam:
177
+ std = self.remove_atom_mapping(std)
178
+
131
179
  # Format any double‑hydrogen notation
132
180
  return std.replace("[HH]", "[H][H]")
133
181
 
@@ -1,8 +1,9 @@
1
1
  from typing import List, Dict, Optional
2
2
  from rdkit import Chem
3
- from fgutils import FGQuery
4
3
  from joblib import Parallel, delayed
5
4
 
5
+ from synkit.Graph.FG import smiles_to_graph_and_functional_groups
6
+
6
7
 
7
8
  class Tautomerize:
8
9
  """Standardize molecules by converting enol and hemiketal tautomers into
@@ -103,18 +104,60 @@ class Tautomerize:
103
104
  :returns: Canonical SMILES of the standardized molecule.
104
105
  :rtype: str
105
106
  """
106
- query = FGQuery()
107
- fg = query.get(smiles)
108
- for item in fg:
109
- label, indices = item
107
+ while True:
108
+ targets = Tautomerize._tautomer_targets(smiles)
109
+ if not targets:
110
+ break
111
+ label, indices = targets[0]
110
112
  if label == "hemiketal":
111
113
  smiles = Tautomerize.standardize_hemiketal(smiles, indices)
112
- fg = query.get(smiles)
113
114
  elif label == "enol":
114
115
  smiles = Tautomerize.standardize_enol(smiles, indices)
115
- fg = query.get(smiles)
116
116
  return Chem.CanonSmiles(smiles)
117
117
 
118
+ @staticmethod
119
+ def _tautomer_targets(smiles: str) -> list[tuple[str, List[int]]]:
120
+ """Return RDKit-index targets used by the tautomer repair helpers."""
121
+ mol = Chem.MolFromSmiles(smiles)
122
+ if mol is None:
123
+ return []
124
+ graph, groups = smiles_to_graph_and_functional_groups(smiles)
125
+ node_to_idx = {
126
+ (
127
+ atom.GetAtomMapNum() if atom.GetAtomMapNum() else atom.GetIdx() + 1
128
+ ): atom.GetIdx()
129
+ for atom in mol.GetAtoms()
130
+ }
131
+
132
+ targets = [
133
+ (label, [node_to_idx[node] for node in nodes])
134
+ for label, nodes in groups
135
+ if label in {"hemiketal", "enol"}
136
+ ]
137
+ targets.extend(
138
+ ("hemiketal", [node_to_idx[node] for node in nodes])
139
+ for nodes in Tautomerize._geminal_diol_nodes(graph)
140
+ )
141
+ return targets
142
+
143
+ @staticmethod
144
+ def _geminal_diol_nodes(graph) -> list[tuple[int, ...]]:
145
+ """Legacy tautomerization compatibility for hydrated carbonyls."""
146
+ targets: list[tuple[int, ...]] = []
147
+ for carbon, data in graph.nodes(data=True):
148
+ if data.get("element") != "C":
149
+ continue
150
+ hydroxyls = [
151
+ neighbor
152
+ for neighbor in graph.neighbors(carbon)
153
+ if graph.nodes[neighbor].get("element") == "O"
154
+ and graph.nodes[neighbor].get("hcount", 0) >= 1
155
+ and graph.edges[carbon, neighbor].get("order") == 1.0
156
+ ]
157
+ if len(hydroxyls) >= 2:
158
+ targets.append((carbon, hydroxyls[0], hydroxyls[1]))
159
+ return targets
160
+
118
161
  @staticmethod
119
162
  def fix_dict(data: Dict[str, str], reaction_column: str) -> Dict[str, str]:
120
163
  """Standardize the reactant and product SMILES in a reaction
@@ -316,8 +316,8 @@ class GraphCanonicaliser:
316
316
  nodes = sorted(g.nodes(data=True), key=lambda x: self._node_key(*x))
317
317
  edges = sorted(g.edges(data=True), key=lambda x: self._edge_key(*x))
318
318
 
319
- node_str = ";".join(f"{n}:{self._node_key(n,d)}" for n, d in nodes)
320
- edge_str = ";".join(f"{(u,v)}:{self._edge_key(u,v,d)}" for u, v, d in edges)
319
+ node_str = ";".join(f"{n}:{self._node_key(n, d)}" for n, d in nodes)
320
+ edge_str = ";".join(f"{(u, v)}:{self._edge_key(u, v, d)}" for u, v, d in edges)
321
321
  return f"N[{node_str}]|E[{edge_str}]"
322
322
 
323
323
  # ------------------------------------------------------------------ #
@@ -0,0 +1,19 @@
1
+ """Functional-group detection on SynKit molecular graphs."""
2
+
3
+ from .catalog import default_registry
4
+ from .audit import FunctionalGroupAudit, audit_reaction_smiles
5
+ from .api import FunctionalGroupLabels, smiles_to_graph_and_functional_groups
6
+ from .detector import FunctionalGroupDetector
7
+ from .model import FunctionalGroupMatch, FunctionalGroupPattern, FunctionalGroupRegistry
8
+
9
+ __all__ = [
10
+ "FunctionalGroupDetector",
11
+ "FunctionalGroupLabels",
12
+ "FunctionalGroupAudit",
13
+ "FunctionalGroupMatch",
14
+ "FunctionalGroupPattern",
15
+ "FunctionalGroupRegistry",
16
+ "default_registry",
17
+ "audit_reaction_smiles",
18
+ "smiles_to_graph_and_functional_groups",
19
+ ]
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import networkx as nx
4
+
5
+ from synkit.IO.chem_converter import smiles_to_graph
6
+
7
+ from .detector import FunctionalGroupDetector
8
+
9
+ FunctionalGroupLabels = list[tuple[str, tuple[int, ...]]]
10
+
11
+
12
+ def smiles_to_graph_and_functional_groups(
13
+ smiles: str,
14
+ *,
15
+ sanitize: bool = True,
16
+ ) -> tuple[nx.Graph, FunctionalGroupLabels]:
17
+ """Convert SMILES to a molecular graph and detect functional groups.
18
+
19
+ Atom-mapped SMILES keep their non-zero atom-map numbers as graph node IDs.
20
+ Unmapped atoms use their 1-based atom order as node IDs, so both mapped and
21
+ unmapped SMILES can be passed to the same API.
22
+
23
+ :param smiles: Input SMILES, with or without atom-map labels.
24
+ :type smiles: str
25
+ :param sanitize: If ``True``, sanitize the RDKit molecule during conversion.
26
+ :type sanitize: bool
27
+ :return: Molecular graph and detected ``(name, node_ids)`` FG labels.
28
+ :rtype: tuple[nx.Graph, list[tuple[str, tuple[int, ...]]]]
29
+ :raises ValueError: If the SMILES cannot be converted to a molecular graph.
30
+ """
31
+ graph = smiles_to_graph(
32
+ smiles,
33
+ drop_non_aam=False,
34
+ sanitize=sanitize,
35
+ use_index_as_atom_map=True,
36
+ )
37
+ if graph is None:
38
+ raise ValueError(f"Could not convert SMILES to molecular graph: {smiles!r}")
39
+ return graph, FunctionalGroupDetector().detect(graph)
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from dataclasses import dataclass
5
+ from time import perf_counter
6
+ from typing import Iterable
7
+
8
+ import networkx as nx
9
+
10
+ from synkit.Chem.Reaction.standardize import Standardize
11
+ from synkit.IO.chem_converter import smiles_to_graph
12
+
13
+ from .detector import FunctionalGroupDetector
14
+ from .ring_system import AromaticRingSystemDetector
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class FunctionalGroupAudit:
19
+ """Aggregated detector coverage over a reaction-SMILES corpus."""
20
+
21
+ reactions: int
22
+ molecules: int
23
+ parse_failures: int
24
+ elapsed_seconds: float
25
+ label_counts: Counter[str]
26
+ heteroaromatic_systems: int
27
+ named_heteroaromatic_systems: int
28
+ unnamed_heteroaromatic_systems: Counter[tuple]
29
+ uncovered_atom_signatures: Counter[tuple]
30
+ uncovered_edge_signatures: Counter[tuple]
31
+
32
+ @property
33
+ def unnamed_heteroaromatic_count(self) -> int:
34
+ return self.heteroaromatic_systems - self.named_heteroaromatic_systems
35
+
36
+
37
+ def audit_reaction_smiles(
38
+ reactions: Iterable[str],
39
+ *,
40
+ standardizer: Standardize | None = None,
41
+ ) -> FunctionalGroupAudit:
42
+ """Audit FG coverage for an iterable of reaction SMILES strings."""
43
+ std = Standardize() if standardizer is None else standardizer
44
+ detector = FunctionalGroupDetector()
45
+
46
+ reaction_count = 0
47
+ molecule_count = 0
48
+ parse_failures = 0
49
+ heteroaromatic_systems = 0
50
+ named_heteroaromatic_systems = 0
51
+ label_counts: Counter[str] = Counter()
52
+ unnamed_systems: Counter[tuple] = Counter()
53
+ uncovered_atoms: Counter[tuple] = Counter()
54
+ uncovered_edges: Counter[tuple] = Counter()
55
+
56
+ started = perf_counter()
57
+ for reaction in reactions:
58
+ reaction_count += 1
59
+ standardized = std.fit(reaction, remove_aam=True)
60
+ for side in standardized.split(">>"):
61
+ for smiles in side.split("."):
62
+ graph = smiles_to_graph(
63
+ smiles,
64
+ drop_non_aam=False,
65
+ use_index_as_atom_map=True,
66
+ )
67
+ if graph is None:
68
+ parse_failures += 1
69
+ continue
70
+ molecule_count += 1
71
+ matches = detector.matches(graph)
72
+ label_counts.update(match.name for match in matches)
73
+ covered = {node for match in matches for node in match.group_nodes}
74
+ _count_uncovered_signatures(
75
+ graph,
76
+ covered,
77
+ uncovered_atoms,
78
+ uncovered_edges,
79
+ )
80
+
81
+ named_ring_nodes = {
82
+ match.group_nodes
83
+ for match in matches
84
+ if match.name != "heteroaromatic_ring"
85
+ and match.pattern.priority == 70
86
+ }
87
+ for system in AromaticRingSystemDetector.detect(graph):
88
+ if not system.hetero_nodes:
89
+ continue
90
+ heteroaromatic_systems += 1
91
+ has_named_subring = any(
92
+ set(nodes).issubset(system.nodes) for nodes in named_ring_nodes
93
+ )
94
+ if has_named_subring:
95
+ named_heteroaromatic_systems += 1
96
+ continue
97
+ unnamed_systems[
98
+ (
99
+ system.hetero_pattern,
100
+ system.is_fused,
101
+ system.ring_sizes,
102
+ tuple(sorted(system.element_counts.items())),
103
+ )
104
+ ] += 1
105
+
106
+ return FunctionalGroupAudit(
107
+ reactions=reaction_count,
108
+ molecules=molecule_count,
109
+ parse_failures=parse_failures,
110
+ elapsed_seconds=perf_counter() - started,
111
+ label_counts=label_counts,
112
+ heteroaromatic_systems=heteroaromatic_systems,
113
+ named_heteroaromatic_systems=named_heteroaromatic_systems,
114
+ unnamed_heteroaromatic_systems=unnamed_systems,
115
+ uncovered_atom_signatures=uncovered_atoms,
116
+ uncovered_edge_signatures=uncovered_edges,
117
+ )
118
+
119
+
120
+ def _count_uncovered_signatures(
121
+ graph: nx.Graph,
122
+ covered: set[int],
123
+ atom_counts: Counter[tuple],
124
+ edge_counts: Counter[tuple],
125
+ ) -> None:
126
+ for node, data in graph.nodes(data=True):
127
+ if data.get("element") == "H" or node in covered:
128
+ continue
129
+ neighbors = tuple(
130
+ sorted(
131
+ graph.nodes[neighbor].get("element")
132
+ for neighbor in graph.neighbors(node)
133
+ if graph.nodes[neighbor].get("element") != "H"
134
+ )
135
+ )
136
+ atom_counts[
137
+ (
138
+ data.get("element"),
139
+ data.get("aromatic", False),
140
+ data.get("hcount", 0),
141
+ neighbors,
142
+ )
143
+ ] += 1
144
+
145
+ for left, right, data in graph.edges(data=True):
146
+ if left in covered or right in covered:
147
+ continue
148
+ left_element = graph.nodes[left].get("element")
149
+ right_element = graph.nodes[right].get("element")
150
+ if "H" in {left_element, right_element}:
151
+ continue
152
+ edge_counts[
153
+ tuple(sorted((left_element, right_element)))
154
+ + (data.get("order"), data.get("aromatic", False))
155
+ ] += 1