synkit 1.4.2b1__tar.gz → 1.5.0__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 (326) hide show
  1. {synkit-1.4.2b1 → synkit-1.5.0}/.gitignore +0 -3
  2. {synkit-1.4.2b1 → synkit-1.5.0}/PKG-INFO +2 -1
  3. {synkit-1.4.2b1 → synkit-1.5.0}/pyproject.toml +2 -1
  4. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Fingerprint/transformation_fp.py +8 -4
  5. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/__init__.py +2 -1
  6. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/tautomerize.py +1 -4
  7. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/its_expand.py +115 -46
  8. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/subgraph_matcher.py +0 -4
  9. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Mech/conversion.py +162 -0
  10. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Mech/electron_accounting.py +25 -138
  11. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Wildcard/its_merge.py +6 -6
  12. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/__init__.py +1 -0
  13. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/chem_converter.py +17 -22
  14. synkit-1.5.0/synkit/IO/conversion.py +42 -0
  15. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/rbl_engine.py +258 -859
  16. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/syn_reactor.py +4 -23
  17. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/render.py +29 -14
  18. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/visualizer.py +24 -12
  19. synkit-1.5.0/synkit/Vis/its_drawer.py +615 -0
  20. synkit-1.5.0/synkit/Vis/molecule_drawer.py +565 -0
  21. synkit-1.5.0/synkit/Vis/mtg_drawer.py +769 -0
  22. synkit-1.5.0/synkit/Vis/reaction_drawer.py +285 -0
  23. synkit-1.5.0/synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
  24. synkit-1.5.0/synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
  25. synkit-1.5.0/synkit/Vis/vis_synedu/rxn_vis.py +382 -0
  26. synkit-1.5.0/synkit/Vis/vis_synedu/vis.py +501 -0
  27. {synkit-1.4.2b1 → synkit-1.5.0}/LICENSE +0 -0
  28. {synkit-1.4.2b1 → synkit-1.5.0}/README.md +0 -0
  29. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/DAG/__init__.py +0 -0
  30. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/DAG/crn.py +0 -0
  31. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/DAG/mod_crn.py +0 -0
  32. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/DAG/syncrn.py +0 -0
  33. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/__init__.py +0 -0
  34. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/abstract.py +0 -0
  35. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/arity.py +0 -0
  36. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/builder.py +0 -0
  37. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/derivation.py +0 -0
  38. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/flattener.py +0 -0
  39. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/keys.py +0 -0
  40. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/mixtures.py +0 -0
  41. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/smiles.py +0 -0
  42. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/state.py +0 -0
  43. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/strategy.py +0 -0
  44. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Construct/worker.py +0 -0
  45. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Pathway/__init__.py +0 -0
  46. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Pathway/_adapter.py +0 -0
  47. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Pathway/pathfinder.py +0 -0
  48. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Pathway/reachability.py +0 -0
  49. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Pathway/realizability.py +0 -0
  50. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Petrinet/__init__.py +0 -0
  51. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Petrinet/analyzer.py +0 -0
  52. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Petrinet/net.py +0 -0
  53. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Petrinet/persistence.py +0 -0
  54. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Petrinet/semiflows.py +0 -0
  55. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Petrinet/structure.py +0 -0
  56. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Props/__init__.py +0 -0
  57. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Props/dynamics.py +0 -0
  58. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Props/helper.py +0 -0
  59. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Props/stoich.py +0 -0
  60. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Props/thermo.py +0 -0
  61. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Query/__init__.py +0 -0
  62. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Query/kegg_api.py +0 -0
  63. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Query/kegg_extract.py +0 -0
  64. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Query/kegg_impute.py +0 -0
  65. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Query/kegg_parse.py +0 -0
  66. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Structure/__init__.py +0 -0
  67. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Structure/reaction.py +0 -0
  68. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Structure/rule.py +0 -0
  69. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Structure/species.py +0 -0
  70. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Structure/syncrn.py +0 -0
  71. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/__init__.py +0 -0
  72. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/_common.py +0 -0
  73. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/_ir.py +0 -0
  74. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/automorphism.py +0 -0
  75. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/canon.py +0 -0
  76. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/isomorphism.py +0 -0
  77. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/symmetry.py +0 -0
  78. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Symmetry/wl_canon.py +0 -0
  79. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Visualize/__init__.py +0 -0
  80. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Visualize/crn_vis.py +0 -0
  81. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Visualize/labels.py +0 -0
  82. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Visualize/layout.py +0 -0
  83. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Visualize/palette.py +0 -0
  84. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Visualize/validation.py +0 -0
  85. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/Visualize/vis.py +0 -0
  86. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/__init__.py +0 -0
  87. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Structure/__init__.py +0 -0
  88. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Structure/backend.py +0 -0
  89. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Structure/conversion.py +0 -0
  90. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Structure/hyperedge.py +0 -0
  91. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Structure/hypergraph.py +0 -0
  92. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Structure/rxn.py +0 -0
  93. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Symmetry/__init__.py +0 -0
  94. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Symmetry/automorphism.py +0 -0
  95. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Symmetry/canon.py +0 -0
  96. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/Symmetry/wl_canon.py +0 -0
  97. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/__init__.py +0 -0
  98. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/configs/__init__.py +0 -0
  99. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/configs/loader.py +0 -0
  100. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/configs/models.py +0 -0
  101. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/constants.py +0 -0
  102. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/crn_formula.py +0 -0
  103. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/deficiency.py +0 -0
  104. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/enumerator.py +0 -0
  105. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/exceptions.py +0 -0
  106. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/explorer.py +0 -0
  107. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/helpers.py +0 -0
  108. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/injectivity.py +0 -0
  109. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/motif.py +0 -0
  110. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/network.py +0 -0
  111. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/pathway.py +0 -0
  112. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/properties.py +0 -0
  113. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/reaction.py +0 -0
  114. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/utils.py +0 -0
  115. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/CRN/dev_crn/viz.py +0 -0
  116. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Cluster/__init__.py +0 -0
  117. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Cluster/butina.py +0 -0
  118. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Fingerprint/__init__.py +0 -0
  119. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
  120. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
  121. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Molecule/__init__.py +0 -0
  122. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Molecule/atom_features.py +0 -0
  123. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Molecule/descriptors.py +0 -0
  124. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Molecule/formula.py +0 -0
  125. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Molecule/graph_annotator.py +0 -0
  126. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Molecule/standardize.py +0 -0
  127. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Molecule/valence.py +0 -0
  128. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/__init__.py +0 -0
  129. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/aam_validator.py +0 -0
  130. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/chem/__init__.py +0 -0
  131. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/chem/aam.py +0 -0
  132. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/chem/its.py +0 -0
  133. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/chem/smiles.py +0 -0
  134. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/exact/__init__.py +0 -0
  135. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/exact/branching.py +0 -0
  136. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/exact/certificate.py +0 -0
  137. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/exact/enumerate.py +0 -0
  138. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/exact/exhaustive.py +0 -0
  139. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/exact/kernel.py +0 -0
  140. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/exact/milp.py +0 -0
  141. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/graph/__init__.py +0 -0
  142. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/graph/automorphism.py +0 -0
  143. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/graph/blockcut.py +0 -0
  144. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/graph/labeled_graph.py +0 -0
  145. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/graph/refine.py +0 -0
  146. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/graph/synkit_adapter.py +0 -0
  147. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/io/__init__.py +0 -0
  148. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/io/idxmapstr.py +0 -0
  149. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/slap/__init__.py +0 -0
  150. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/slap/lap.py +0 -0
  151. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/Mapper/slap/sequential.py +0 -0
  152. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/aam_validator.py +0 -0
  153. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/balance_check.py +0 -0
  154. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
  155. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/cleaning.py +0 -0
  156. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/deionize.py +0 -0
  157. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/fix_aam.py +0 -0
  158. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/neutralize.py +0 -0
  159. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/radical_wildcard.py +0 -0
  160. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/Reaction/standardize.py +0 -0
  161. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/__init__.py +0 -0
  162. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Chem/utils.py +0 -0
  163. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Data/__init__.py +0 -0
  164. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Data/gen_partial_aam.py +0 -0
  165. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Canon/__init__.py +0 -0
  166. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Canon/canon_algs.py +0 -0
  167. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Canon/canon_graph.py +0 -0
  168. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Canon/nauty.py +0 -0
  169. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Context/__init__.py +0 -0
  170. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Context/hier_context.py +0 -0
  171. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Context/radius_expand.py +0 -0
  172. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/FG/__init__.py +0 -0
  173. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/FG/api.py +0 -0
  174. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/FG/audit.py +0 -0
  175. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/FG/catalog.py +0 -0
  176. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/FG/detector.py +0 -0
  177. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/FG/model.py +0 -0
  178. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/FG/ring_system.py +0 -0
  179. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/Descriptors/topology.py +0 -0
  180. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
  181. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +0 -0
  182. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/__init__.py +0 -0
  183. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/graph_descriptors.py +0 -0
  184. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/graph_fps.py +0 -0
  185. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/graph_signature.py +0 -0
  186. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/hash_fps.py +0 -0
  187. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/morgan_fps.py +0 -0
  188. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/path_fps.py +0 -0
  189. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Feature/wl_hash.py +0 -0
  190. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Hyrogen/__init__.py +0 -0
  191. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Hyrogen/_misc.py +0 -0
  192. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
  193. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Hyrogen/hextend.py +0 -0
  194. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/__init__.py +0 -0
  195. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/its_builder.py +0 -0
  196. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/its_construction.py +0 -0
  197. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/its_decompose.py +0 -0
  198. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/its_destruction.py +0 -0
  199. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/its_relabel.py +0 -0
  200. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/its_reverter.py +0 -0
  201. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/normalize_aam.py +0 -0
  202. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/partial_its.py +0 -0
  203. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/ITS/rc_extractor.py +0 -0
  204. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/MTG/__init__.py +0 -0
  205. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/MTG/mcs_matcher.py +0 -0
  206. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/MTG/mtg.py +0 -0
  207. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/MTG/mtg_explore.py +0 -0
  208. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/MTG/utils.py +0 -0
  209. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/__init__.py +0 -0
  210. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/approx_mcs.py +0 -0
  211. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/auto_est.py +0 -0
  212. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/automorphism.py +0 -0
  213. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/batch_cluster.py +0 -0
  214. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/dedup_matches.py +0 -0
  215. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/graph_cluster.py +0 -0
  216. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/graph_matcher.py +0 -0
  217. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/graph_morphism.py +0 -0
  218. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/mcs_matcher.py +0 -0
  219. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/multi_turbo_iso.py +0 -0
  220. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/orbit.py +0 -0
  221. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/partial_matcher.py +0 -0
  222. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/sing.py +0 -0
  223. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/turbo_iso.py +0 -0
  224. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Matcher/wl_sel.py +0 -0
  225. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Mech/__init__.py +0 -0
  226. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Mech/lwg_editor.py +0 -0
  227. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Mech/lwg_ops.py +0 -0
  228. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Wildcard/__init__.py +0 -0
  229. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Wildcard/fuse_graph.py +0 -0
  230. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Wildcard/graph_wc.py +0 -0
  231. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Wildcard/radwc.py +0 -0
  232. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Wildcard/wc_matcher.py +0 -0
  233. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/Wildcard/wildcard.py +0 -0
  234. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/__init__.py +0 -0
  235. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/canon_graph.py +0 -0
  236. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/syn_graph.py +0 -0
  237. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Graph/utils.py +0 -0
  238. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/combinatorial/__init__.py +0 -0
  239. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/combinatorial/gml_to_graph.py +0 -0
  240. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/combinatorial/graph_to_gml.py +0 -0
  241. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/combinatorial/graph_to_smarts.py +0 -0
  242. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/combinatorial/smarts_expander.py +0 -0
  243. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/combinatorial/smarts_generalizer.py +0 -0
  244. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/combinatorial/smarts_to_graph.py +0 -0
  245. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/data_io.py +0 -0
  246. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/data_process.py +0 -0
  247. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/debug.py +0 -0
  248. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/dg_to_gml.py +0 -0
  249. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/gml_to_nx.py +0 -0
  250. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/graph_to_mol.py +0 -0
  251. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/mol_to_graph.py +0 -0
  252. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/nx_to_gml.py +0 -0
  253. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/IO/smiles_to_id.py +0 -0
  254. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Apply/__init__.py +0 -0
  255. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Apply/reactor_rule.py +0 -0
  256. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Apply/retro_reactor.py +0 -0
  257. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Apply/rule_matcher.py +0 -0
  258. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Apply/rule_rbl.py +0 -0
  259. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Compose/__init__.py +0 -0
  260. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Compose/compose_rule.py +0 -0
  261. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Compose/rule_compose.py +0 -0
  262. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Compose/rule_mapping.py +0 -0
  263. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Compose/seq_comp.py +0 -0
  264. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Compose/valence_constrain.py +0 -0
  265. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Modify/__init__.py +0 -0
  266. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Modify/implict_rule.py +0 -0
  267. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Modify/longest_path.py +0 -0
  268. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Modify/molecule_rule.py +0 -0
  269. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Modify/prune_templates.py +0 -0
  270. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Modify/rule_utils.py +0 -0
  271. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/Modify/strip_rule.py +0 -0
  272. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/__init__.py +0 -0
  273. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Rule/syn_rule.py +0 -0
  274. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/MSR/__init__.py +0 -0
  275. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/MSR/multi_steps.py +0 -0
  276. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/MSR/path_finder.py +0 -0
  277. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Metrics/__init__.py +0 -0
  278. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Metrics/_base.py +0 -0
  279. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Metrics/_plot.py +0 -0
  280. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Metrics/_ranking.py +0 -0
  281. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/__init__.py +0 -0
  282. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/batch_reactor.py +0 -0
  283. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/benchmark.py +0 -0
  284. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/imba_engine.py +0 -0
  285. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
  286. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
  287. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/partial_engine.py +0 -0
  288. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/post_syn.py +0 -0
  289. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/rule_filter.py +0 -0
  290. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
  291. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/Reactor/strategy.py +0 -0
  292. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/__init__.py +0 -0
  293. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Synthesis/reactor_utils.py +0 -0
  294. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Utils/__init__.py +0 -0
  295. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Utils/utils.py +0 -0
  296. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/__init__.py +0 -0
  297. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/crn/__init__.py +0 -0
  298. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/crn/visualizer.py +0 -0
  299. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/__init__.py +0 -0
  300. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/arrows.py +0 -0
  301. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/chem.py +0 -0
  302. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/constants.py +0 -0
  303. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/layout.py +0 -0
  304. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/mapping.py +0 -0
  305. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/models.py +0 -0
  306. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/epd/utils.py +0 -0
  307. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/graph_visualizer.py +0 -0
  308. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/its/__init__.py +0 -0
  309. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/its/drawer.py +0 -0
  310. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/molecule/__init__.py +0 -0
  311. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/molecule/drawer.py +0 -0
  312. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/molecule/reaction.py +0 -0
  313. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/mtg/__init__.py +0 -0
  314. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/mtg/drawer.py +0 -0
  315. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/pdf_writer.py +0 -0
  316. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/reaction/__init__.py +0 -0
  317. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/reaction/rule.py +0 -0
  318. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/reaction/rxn.py +0 -0
  319. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/space/__init__.py +0 -0
  320. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/space/chemical.py +0 -0
  321. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/space/embedding.py +0 -0
  322. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/visual_drawer.py +0 -0
  323. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/Vis/visual_model.py +0 -0
  324. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/__init__.py +0 -0
  325. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/examples.py +0 -0
  326. {synkit-1.4.2b1 → synkit-1.5.0}/synkit/version.py +0 -0
@@ -37,6 +37,3 @@ test_syn.py
37
37
  .gitignore
38
38
  measure_candidate_stages.py
39
39
  run_valid_bug_cases.py
40
- debug/*
41
- diag_postproc.py
42
- diag_nofused.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synkit
3
- Version: 1.4.2b1
3
+ Version: 1.5.0
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
@@ -19,6 +19,7 @@ Requires-Dist: rdkit>=2025.3.1
19
19
  Requires-Dist: regex>=2024.11.6
20
20
  Requires-Dist: requests>=2.32.3
21
21
  Requires-Dist: scikit-learn>=1.4.0
22
+ Requires-Dist: scipy>=1.14.0
22
23
  Requires-Dist: seaborn>=0.13.2
23
24
  Provides-Extra: all
24
25
  Requires-Dist: numpy>=2.2.0; extra == 'all'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "synkit"
7
- version = "1.4.2b1"
7
+ version = "1.5.0"
8
8
  description = "Utility for reaction modeling using graph grammar"
9
9
  readme = "README.md"
10
10
  long-description = { file = "CHANGELOG.md" }
@@ -25,6 +25,7 @@ dependencies = [
25
25
  "pandas>=1.5.3",
26
26
  "rdkit>=2025.3.1",
27
27
  "networkx>=3.3",
28
+ "scipy>=1.14.0",
28
29
  "seaborn>=0.13.2",
29
30
  "requests>=2.32.3",
30
31
  "regex>=2024.11.6",
@@ -36,14 +36,18 @@ class TransformationFP:
36
36
 
37
37
  @staticmethod
38
38
  def convert_arr2vec(arr: np.ndarray) -> cDataStructs.ExplicitBitVect:
39
- """Convert a NumPy array of bits into an RDKit ExplicitBitVect.
39
+ """Convert a non-negative NumPy array into an RDKit bit vector.
40
40
 
41
- :param arr: Array of 0/1 values representing a fingerprint.
41
+ :param arr: Array of non-negative fingerprint values.
42
42
  :type arr: np.ndarray
43
- :returns: RDKit bit vector constructed from the bit string.
43
+ :returns: RDKit bit vector with positive values encoded as 1.
44
44
  :rtype: cDataStructs.ExplicitBitVect
45
+ :raises ValueError: If the array contains negative or non-finite values.
45
46
  """
46
- bitstr = "".join(str(int(x)) for x in arr.flatten())
47
+ flat = np.asarray(arr).flatten()
48
+ if not np.isfinite(flat).all() or (flat < 0).any():
49
+ raise ValueError("Bit-vector output requires non-negative finite values.")
50
+ bitstr = "".join("1" if x else "0" for x in flat)
47
51
  return cDataStructs.CreateFromBitString(bitstr)
48
52
 
49
53
  def fit(
@@ -1,6 +1,7 @@
1
1
  from .aam_validator import AAMValidator
2
+ from synkit.Chem.utils import remove_explicit_H_from_rsmi
2
3
 
3
4
  # from .standardize import Standardize
4
5
  # from .canon_rsmi import CanonRSMI
5
6
 
6
- __all__ = ["AAMValidator"]
7
+ __all__ = ["AAMValidator", "remove_explicit_H_from_rsmi"]
@@ -121,10 +121,7 @@ class Tautomerize:
121
121
  mol = Chem.MolFromSmiles(smiles)
122
122
  if mol is None:
123
123
  return []
124
- try:
125
- graph, groups = smiles_to_graph_and_functional_groups(smiles)
126
- except ValueError:
127
- return []
124
+ graph, groups = smiles_to_graph_and_functional_groups(smiles)
128
125
  node_to_idx = {
129
126
  (
130
127
  atom.GetAtomMapNum() if atom.GetAtomMapNum() else atom.GetIdx() + 1
@@ -100,13 +100,36 @@ class ITSExpand:
100
100
  )
101
101
 
102
102
  @staticmethod
103
- def _validate_positive_atom_maps(atom_maps: list[int]) -> None:
104
- """Validate that atom-map numbers can be used as node IDs.
103
+ def _validate_atom_maps_within_range(
104
+ atom_maps: list[int],
105
+ n_nodes: int,
106
+ ) -> None:
107
+ """Validate that atom-map numbers can be used as contiguous node IDs.
108
+
109
+ In the side graph, we want final node IDs to remain exactly ``1..N``.
110
+ Therefore, a mapped atom can only be moved to its atom-map number if
111
+ that number is within ``1..N``.
105
112
 
106
113
  :param atom_maps: Nonzero atom-map numbers.
107
114
  :type atom_maps: list[int]
108
- :raises ValueError: If any atom-map number is not positive.
115
+ :param n_nodes: Number of nodes in the side graph.
116
+ :type n_nodes: int
117
+ :raises ValueError: If any atom-map number is outside ``1..N``.
109
118
  """
119
+ bad_targets = [
120
+ atom_map for atom_map in atom_maps if atom_map < 1 or atom_map > n_nodes
121
+ ]
122
+
123
+ if bad_targets:
124
+ raise ValueError(
125
+ "Cannot keep side graph node ids contiguous from 1..N while "
126
+ f"also using atom_map as node id. The following atom maps are "
127
+ f"outside 1..{n_nodes}: {bad_targets}"
128
+ )
129
+
130
+ @staticmethod
131
+ def _validate_positive_atom_maps(atom_maps: list[int]) -> None:
132
+ """Validate that atom-map numbers are valid positive node IDs."""
110
133
  bad_targets = [atom_map for atom_map in atom_maps if atom_map < 1]
111
134
 
112
135
  if bad_targets:
@@ -141,33 +164,17 @@ class ITSExpand:
141
164
 
142
165
  return mapping, used_ids
143
166
 
144
- @staticmethod
145
- def _next_free_positive_id(used_ids: set[int], start: int = 1) -> int:
146
- """Return the smallest positive integer not present in ``used_ids``.
147
-
148
- :param used_ids: Node IDs already occupied by mapped atoms.
149
- :type used_ids: set[int]
150
- :param start: First candidate ID.
151
- :type start: int
152
- :returns: Available positive node ID.
153
- :rtype: int
154
- """
155
- candidate = max(1, start)
156
- while candidate in used_ids:
157
- candidate += 1
158
- return candidate
159
-
160
167
  @staticmethod
161
168
  def _assign_unmapped_nodes(
162
169
  graph,
163
170
  mapping: dict,
164
171
  used_ids: set[int],
165
172
  ) -> dict:
166
- """Assign unmapped atoms while avoiding mapped atom-map IDs.
173
+ """Assign unmapped atoms while preserving contiguous node IDs.
167
174
 
168
175
  Unmapped atoms keep their original node ID when possible. If an
169
- unmapped atom's node ID conflicts with a mapped atom-map target, it is
170
- moved into the smallest unused positive ID.
176
+ unmapped atom's node ID conflicts with a mapped atom's target ID, it is
177
+ moved into one of the remaining free IDs inside ``1..N``.
171
178
 
172
179
  :param graph: Molecular side graph.
173
180
  :type graph: networkx.Graph
@@ -178,7 +185,9 @@ class ITSExpand:
178
185
  :returns: Complete old-node to new-node mapping.
179
186
  :rtype: dict
180
187
  """
181
- next_candidate = 1
188
+ n_nodes = graph.number_of_nodes()
189
+ free_ids = set(range(1, n_nodes + 1)) - used_ids
190
+ pending_unmapped = []
182
191
 
183
192
  for node, data in graph.nodes(data=True):
184
193
  atom_map = ITSExpand._atom_map(data)
@@ -186,29 +195,68 @@ class ITSExpand:
186
195
  if atom_map != 0:
187
196
  continue
188
197
 
198
+ if isinstance(node, int) and node in free_ids:
199
+ mapping[node] = node
200
+ free_ids.remove(node)
201
+ else:
202
+ pending_unmapped.append(node)
203
+
204
+ for old_node, new_node in zip(pending_unmapped, sorted(free_ids)):
205
+ mapping[old_node] = new_node
206
+
207
+ return mapping
208
+
209
+ @staticmethod
210
+ def _assign_unmapped_nodes_sparse(
211
+ graph,
212
+ mapping: dict,
213
+ used_ids: set[int],
214
+ ) -> dict:
215
+ """Assign unmapped atoms without changing preserved sparse map IDs."""
216
+ next_candidate = 1
217
+
218
+ for node, data in graph.nodes(data=True):
219
+ if ITSExpand._atom_map(data) != 0:
220
+ continue
221
+
189
222
  if isinstance(node, int) and node > 0 and node not in used_ids:
190
223
  mapping[node] = node
191
224
  used_ids.add(node)
192
225
  next_candidate = max(next_candidate, node + 1)
193
226
  continue
194
227
 
195
- new_node = ITSExpand._next_free_positive_id(used_ids, next_candidate)
196
- mapping[node] = new_node
197
- used_ids.add(new_node)
198
- next_candidate = new_node + 1
228
+ while next_candidate in used_ids:
229
+ next_candidate += 1
230
+ mapping[node] = next_candidate
231
+ used_ids.add(next_candidate)
232
+ next_candidate += 1
199
233
 
200
234
  return mapping
201
235
 
202
236
  @staticmethod
203
- def _validate_complete_unique_mapping(mapping: dict, n_nodes: int) -> None:
204
- """Validate that every source node maps to a unique target node.
237
+ def _validate_contiguous_mapping(mapping: dict, n_nodes: int) -> None:
238
+ """Validate that a mapping produces exactly node IDs ``1..N``.
205
239
 
206
240
  :param mapping: Old-node to new-node mapping.
207
241
  :type mapping: dict
208
242
  :param n_nodes: Number of nodes in the graph.
209
243
  :type n_nodes: int
210
- :raises RuntimeError: If the mapping is incomplete or has collisions.
244
+ :raises RuntimeError: If the mapped node IDs are not exactly ``1..N``.
211
245
  """
246
+ expected_ids = set(range(1, n_nodes + 1))
247
+ actual_ids = set(mapping.values())
248
+
249
+ if actual_ids != expected_ids:
250
+ missing = sorted(expected_ids - actual_ids)
251
+ extra = sorted(actual_ids - expected_ids)
252
+ raise RuntimeError(
253
+ f"Reindexing failed. Missing node ids: {missing}; "
254
+ f"extra node ids: {extra}"
255
+ )
256
+
257
+ @staticmethod
258
+ def _validate_complete_unique_mapping(mapping: dict, n_nodes: int) -> None:
259
+ """Validate that every source node maps to one unique target node."""
212
260
  if len(mapping) != n_nodes:
213
261
  raise RuntimeError(
214
262
  f"Reindexing failed. Expected {n_nodes} mapped nodes; "
@@ -222,32 +270,48 @@ class ITSExpand:
222
270
  )
223
271
 
224
272
  @staticmethod
225
- def _build_side_graph_reindex_mapping(graph) -> dict:
273
+ def _build_side_graph_reindex_mapping(
274
+ graph,
275
+ contiguous: bool = True,
276
+ ) -> dict:
226
277
  """Build an old-node to new-node mapping for a side graph.
227
278
 
228
279
  The mapping satisfies two conditions:
229
280
 
230
281
  1. Every atom with ``atom_map != 0`` is assigned to node ID
231
282
  ``atom_map``.
232
- 2. Every unmapped atom is assigned a unique positive node ID that does
233
- not collide with preserved atom-map IDs.
283
+ 2. The final node IDs are exactly contiguous from ``1..N`` when
284
+ ``contiguous=True``. With ``contiguous=False``, existing positive,
285
+ sparse atom-map IDs are retained and unmapped atoms receive unused
286
+ positive IDs.
234
287
 
235
288
  :param graph: Molecular side graph.
236
289
  :type graph: networkx.Graph
237
290
  :returns: Old-node to new-node mapping.
238
291
  :rtype: dict
239
- :raises ValueError: If atom-map values are duplicated or non-positive.
292
+ :raises ValueError: If atom-map values are duplicated or incompatible
293
+ with contiguous node IDs.
240
294
  """
241
295
  n_nodes = graph.number_of_nodes()
242
296
  atom_maps = ITSExpand._nonzero_atom_maps(graph)
243
297
 
244
298
  ITSExpand._validate_unique_atom_maps(atom_maps)
245
- ITSExpand._validate_positive_atom_maps(atom_maps)
299
+ if contiguous:
300
+ ITSExpand._validate_atom_maps_within_range(atom_maps, n_nodes)
301
+ else:
302
+ ITSExpand._validate_positive_atom_maps(atom_maps)
246
303
 
247
304
  mapping, used_ids = ITSExpand._assign_mapped_nodes(graph)
248
- mapping = ITSExpand._assign_unmapped_nodes(graph, mapping, used_ids)
249
-
250
- ITSExpand._validate_complete_unique_mapping(mapping, n_nodes)
305
+ if contiguous:
306
+ mapping = ITSExpand._assign_unmapped_nodes(graph, mapping, used_ids)
307
+ ITSExpand._validate_contiguous_mapping(mapping, n_nodes)
308
+ else:
309
+ mapping = ITSExpand._assign_unmapped_nodes_sparse(
310
+ graph,
311
+ mapping,
312
+ used_ids,
313
+ )
314
+ ITSExpand._validate_complete_unique_mapping(mapping, n_nodes)
251
315
 
252
316
  return mapping
253
317
 
@@ -319,12 +383,12 @@ class ITSExpand:
319
383
  return new_graph
320
384
 
321
385
  @staticmethod
322
- def reindex_side_graph_by_atom_map(graph):
386
+ def reindex_side_graph_by_atom_map(graph, contiguous: bool = True):
323
387
  """Reindex a side graph so mapped atoms use ``atom_map`` as node ID.
324
388
 
325
- The returned graph keeps mapped atom node IDs equal to their atom-map
326
- values. Unmapped atom node IDs remain compact where possible, but may
327
- become sparse to avoid collisions with preserved atom maps.
389
+ By default, the returned graph keeps node IDs contiguous from ``1..N``.
390
+ Set ``contiguous=False`` to preserve valid sparse map IDs such as
391
+ ``:10`` and ``:21``; this is needed by the EF-SMIRKS expansion path.
328
392
 
329
393
  This is useful because the reaction-center graph produced by
330
394
  ``ITSConstruction().ITSGraph(...)`` uses atom-map numbers as node IDs,
@@ -351,12 +415,14 @@ class ITSExpand:
351
415
 
352
416
  :param graph: Molecular side graph.
353
417
  :type graph: networkx.Graph
354
- :returns: Reindexed side graph with preserved mapped node IDs.
418
+ :param contiguous: Whether the returned node IDs must be ``1..N``.
419
+ :type contiguous: bool
420
+ :returns: Reindexed side graph.
355
421
  :rtype: networkx.Graph
356
422
  :raises ValueError: If atom-map numbers cannot be safely used as node
357
- IDs.
423
+ IDs while preserving ``1..N`` indexing.
358
424
  """
359
- mapping = ITSExpand._build_side_graph_reindex_mapping(graph)
425
+ mapping = ITSExpand._build_side_graph_reindex_mapping(graph, contiguous)
360
426
  return ITSExpand._rebuild_graph_with_mapping(graph, mapping)
361
427
 
362
428
  @staticmethod
@@ -431,7 +497,10 @@ class ITSExpand:
431
497
 
432
498
  # Node IDs remain contiguous from 1..N.
433
499
  if preserve_older_map:
434
- side_graph = ITSExpand.reindex_side_graph_by_atom_map(side_graph)
500
+ side_graph = ITSExpand.reindex_side_graph_by_atom_map(
501
+ side_graph,
502
+ contiguous=False,
503
+ )
435
504
 
436
505
  # Reconstruct the full ITS graph.
437
506
  its_graph = ITSBuilder().ITSGraph(side_graph, rc_graph)
@@ -128,10 +128,6 @@ def electron_aware_node_match(
128
128
  attr, 0 if attr in {"hcount", "lone_pairs"} else None
129
129
  )
130
130
  if attr in {"hcount", "lone_pairs"}:
131
- if host_value is None:
132
- host_value = 0
133
- if pattern_value is None:
134
- pattern_value = 0
135
131
  if host_value < pattern_value:
136
132
  return False
137
133
  continue
@@ -73,6 +73,69 @@ def split_arrow_code(arrow_code: str) -> list[str]:
73
73
  return [s.strip() for s in arrow_code.split(";") if s.strip()]
74
74
 
75
75
 
76
+ def ef_arrow_code_to_arrow_code(ef_arrow_code: str) -> str:
77
+ """Normalize an EF-SMIRKS hyphen-form flow code for SynKit conversion.
78
+
79
+ EF-SMIRKS stores one electron-flow step as ``source-target``; SynKit's
80
+ internal arrow-code helpers use ``source=target``. For example,
81
+ ``"10,20-20,21"`` becomes ``"10,20=20,21"``.
82
+
83
+ :param ef_arrow_code: Semicolon-separated hyphen-form electron-flow code.
84
+ :type ef_arrow_code: str
85
+ :returns: Equivalent equals-form SynKit arrow code.
86
+ :rtype: str
87
+ :raises ValueError: If a step does not contain exactly one ``"-"``.
88
+ """
89
+ normalized_steps = []
90
+ for step in split_arrow_code(ef_arrow_code):
91
+ if step.count("-") != 1:
92
+ raise ValueError(
93
+ "An EF-SMIRKS electron-flow step must contain exactly one '-': "
94
+ f"{step!r}"
95
+ )
96
+ lhs, rhs = step.split("-", 1)
97
+ normalized_steps.append(f"{lhs.strip()}={rhs.strip()}")
98
+ if not normalized_steps:
99
+ raise ValueError("EF-SMIRKS electron-flow code is empty.")
100
+ return ";".join(normalized_steps)
101
+
102
+
103
+ def arrow_code_to_ef_arrow_code(arrow_code: str) -> str:
104
+ """Convert internal equals-form arrow code to EF-SMIRKS hyphen form.
105
+
106
+ :param arrow_code: Semicolon-separated SynKit arrow code.
107
+ :type arrow_code: str
108
+ :returns: Equivalent hyphen-form electron-flow code.
109
+ :rtype: str
110
+ """
111
+ return ";".join(step.replace("=", "-", 1) for step in split_arrow_code(arrow_code))
112
+
113
+
114
+ def split_ef_smirks(ef_smirks: str) -> tuple[str, str]:
115
+ """Split an EF-SMIRKS string into reaction SMILES and flow code.
116
+
117
+ An EF-SMIRKS is an atom-mapped reaction SMILES followed by whitespace and
118
+ a hyphen-form electron-flow code, e.g. ``"[CH3:1][Cl:2]>>[CH3:1].[Cl-:2]
119
+ 1-1,2"``.
120
+
121
+ :param ef_smirks: RSMI followed by whitespace and electron-flow code.
122
+ :type ef_smirks: str
123
+ :returns: Reaction SMILES and hyphen-form electron-flow code.
124
+ :rtype: tuple[str, str]
125
+ :raises ValueError: If the input lacks an RSMI arrow or flow code.
126
+ """
127
+ try:
128
+ reaction_smiles, ef_arrow_code = ef_smirks.strip().rsplit(None, 1)
129
+ except ValueError as exc:
130
+ raise ValueError(
131
+ "EF-SMIRKS must contain reaction SMILES followed by whitespace and "
132
+ "an electron-flow code."
133
+ ) from exc
134
+ if ">>" not in reaction_smiles:
135
+ raise ValueError("EF-SMIRKS reaction component must contain '>>'.")
136
+ return reaction_smiles, ef_arrow_code
137
+
138
+
76
139
  def arrow_atom_maps(arrow_code: str) -> set[int]:
77
140
  """Return all atom maps used in an arrow code.
78
141
 
@@ -946,6 +1009,105 @@ def convert_reaction_arrow(
946
1009
  return result
947
1010
 
948
1011
 
1012
+ def ef_smirks_to_epd(
1013
+ ef_smirks: str,
1014
+ orbital_class: Optional[str] = None,
1015
+ strict_bond_lookup: bool = True,
1016
+ ) -> dict[str, Any]:
1017
+ """Complete AAM and convert an EF-SMIRKS string into EPD data.
1018
+
1019
+ The supplied atom maps used by the electron-flow code are preserved. All
1020
+ remaining atoms receive maps through :class:`ITSExpand`. The result
1021
+ contains both generic ``epd`` and Lewis-wavefunction-aware ``epd_lw``
1022
+ records; the latter uses local ITS bond orders to distinguish sigma and pi
1023
+ edits.
1024
+
1025
+ :param ef_smirks: RSMI followed by a hyphen-form electron-flow code.
1026
+ :type ef_smirks: str
1027
+ :param orbital_class: Optional source-dataset metadata.
1028
+ :type orbital_class: Optional[str]
1029
+ :param strict_bond_lookup: Whether missing ITS bond lookups should raise.
1030
+ :type strict_bond_lookup: bool
1031
+ :returns: Completed AAM, generic EPD, typed ``epd_lw``, and diagnostics.
1032
+ :rtype: dict[str, Any]
1033
+ """
1034
+ reaction_smiles, ef_arrow_code = split_ef_smirks(ef_smirks)
1035
+ arrow_code = ef_arrow_code_to_arrow_code(ef_arrow_code)
1036
+ result = convert_reaction_arrow(
1037
+ reaction_smiles=reaction_smiles,
1038
+ arrow_code=arrow_code,
1039
+ orbital_class=orbital_class,
1040
+ expand_aam=True,
1041
+ remove_non_arrow_maps=True,
1042
+ strict_bond_lookup=strict_bond_lookup,
1043
+ )
1044
+ result.update(
1045
+ {
1046
+ "ef_smirks": ef_smirks,
1047
+ "ef_arrow_code": ef_arrow_code,
1048
+ "complete_aam": result["expanded_rsmi"],
1049
+ "epd": result["converted"],
1050
+ "epd_lw": result["typed_converted"],
1051
+ }
1052
+ )
1053
+ return result
1054
+
1055
+
1056
+ def epd_to_ef_smirks(complete_aam: str, epd: list[list[Any]]) -> str:
1057
+ """Reconstruct an EF-SMIRKS string from complete AAM and EPD records.
1058
+
1059
+ ``epd`` may contain generic action names (``"B-/B+"``) or typed Lewis
1060
+ names (``"Sigma-/Pi+"``). Only action direction and source/target map
1061
+ lists are needed to reconstruct the electron-flow code.
1062
+
1063
+ :param complete_aam: Fully atom-mapped reaction SMILES.
1064
+ :type complete_aam: str
1065
+ :param epd: Generic or typed EPD records of ``[action, source, target]``.
1066
+ :type epd: list[list[Any]]
1067
+ :returns: EF-SMIRKS text with a hyphen-form electron-flow code.
1068
+ :rtype: str
1069
+ :raises ValueError: If AAM or EPD records are malformed or unsupported.
1070
+ """
1071
+ if complete_aam.count(">>") != 1:
1072
+ raise ValueError("complete_aam must contain exactly one '>>'.")
1073
+ if not epd:
1074
+ raise ValueError("epd must contain at least one electron-pushing step.")
1075
+
1076
+ def format_maps(atom_maps: list[int]) -> str:
1077
+ """Format an atom-map sequence for one EF-SMIRKS endpoint."""
1078
+ return ",".join(str(atom_map) for atom_map in atom_maps)
1079
+
1080
+ steps = []
1081
+ for record in epd:
1082
+ if len(record) < 3:
1083
+ raise ValueError(f"Invalid EPD record: {record!r}")
1084
+ action, source, target = record[:3]
1085
+ action_parts = str(action).split("/", 1)
1086
+ if len(action_parts) != 2:
1087
+ raise ValueError(f"Invalid EPD action: {action!r}")
1088
+ consumed, produced = action_parts
1089
+ source = [int(atom_map) for atom_map in source]
1090
+ target = [int(atom_map) for atom_map in target]
1091
+
1092
+ if consumed == "LP-" and len(source) == 1 and len(target) in {1, 2}:
1093
+ lhs, rhs = source, target
1094
+ elif produced == "LP+" and len(source) == 2 and len(target) == 1:
1095
+ lhs, rhs = source, target
1096
+ elif (
1097
+ consumed.endswith("-")
1098
+ and produced.endswith("+")
1099
+ and len(source) == 2
1100
+ and len(target) == 2
1101
+ ):
1102
+ lhs, rhs = source, target
1103
+ else:
1104
+ raise ValueError(f"Unsupported EPD record for EF-SMIRKS: {record!r}")
1105
+
1106
+ steps.append(f"{format_maps(lhs)}-{format_maps(rhs)}")
1107
+
1108
+ return f"{complete_aam} {';'.join(steps)}"
1109
+
1110
+
949
1111
  def convert_record(
950
1112
  record: dict[str, Any],
951
1113
  reaction_key: str = "SMIRKS",