synkit 0.0.8__tar.gz → 0.0.9__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 (253) hide show
  1. {synkit-0.0.8 → synkit-0.0.9}/.gitignore +2 -1
  2. synkit-0.0.9/.readthedocs.yml +24 -0
  3. {synkit-0.0.8 → synkit-0.0.9}/PKG-INFO +5 -1
  4. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_canon_rsmi.py +5 -1
  5. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Modify/test_molecule_rule.py +1 -1
  6. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Modify/test_rule_utils.py +2 -1
  7. {synkit-0.0.8 → synkit-0.0.9}/doc/conf.py +14 -11
  8. {synkit-0.0.8 → synkit-0.0.9}/doc/index.rst +1 -0
  9. synkit-0.0.9/doc/requirements.txt +3 -0
  10. {synkit-0.0.8 → synkit-0.0.9}/pyproject.toml +6 -2
  11. synkit-0.0.9/synkit/Graph/Canon/__init__.py +3 -0
  12. {synkit-0.0.8/synkit/Graph → synkit-0.0.9/synkit/Graph/Canon}/canon_graph.py +11 -2
  13. synkit-0.0.9/synkit/Graph/Canon/nauty.py +292 -0
  14. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/_misc.py +19 -2
  15. synkit-0.0.9/synkit/Graph/Matcher/__init__.py +10 -0
  16. synkit-0.0.9/synkit/Graph/Matcher/mcs_matcher.py +202 -0
  17. synkit-0.0.9/synkit/Graph/Matcher/multi_turbo_iso.py +178 -0
  18. synkit-0.0.9/synkit/Graph/Matcher/partial_matcher.py +214 -0
  19. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/turbo_iso.py +8 -5
  20. synkit-0.0.9/synkit/Graph/__init__.py +16 -0
  21. synkit-0.0.9/synkit/Graph/canon_graph.py +538 -0
  22. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/chem_converter.py +23 -17
  23. synkit-0.0.9/synkit/Synthesis/Reactor/batch_reactor.py +231 -0
  24. synkit-0.0.9/synkit/Synthesis/Reactor/rule_filter.py +201 -0
  25. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/strategy.py +2 -0
  26. synkit-0.0.8/synkit/Graph/Matcher/__init__.py +0 -10
  27. synkit-0.0.8/synkit/Graph/Matcher/mcs_matcher.py +0 -202
  28. synkit-0.0.8/synkit/Graph/__init__.py +0 -6
  29. synkit-0.0.8/synkit/Synthesis/Reactor/old_syn_reactor.py +0 -443
  30. {synkit-0.0.8 → synkit-0.0.9}/.github/workflows/build-doc.yml +0 -0
  31. {synkit-0.0.8 → synkit-0.0.9}/.github/workflows/publish-package.yml +0 -0
  32. {synkit-0.0.8 → synkit-0.0.9}/.github/workflows/test-and-lint.yml +0 -0
  33. {synkit-0.0.8 → synkit-0.0.9}/Data/Figure/synkit.png +0 -0
  34. {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/ComposeRule/data.txt +0 -0
  35. {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/SingleRule/R0/0.gml +0 -0
  36. {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/SingleRule/R0/1.gml +0 -0
  37. {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/SingleRule/R0/2.gml +0 -0
  38. {synkit-0.0.8 → synkit-0.0.9}/LICENSE +0 -0
  39. {synkit-0.0.8 → synkit-0.0.9}/Makefile +0 -0
  40. {synkit-0.0.8 → synkit-0.0.9}/README.md +0 -0
  41. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/__init__.py +0 -0
  42. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/test_fp_calculator.py +0 -0
  43. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/test_smiles_featurizer.py +0 -0
  44. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/test_transformation_fp.py +0 -0
  45. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Molecule/__init__.py +0 -0
  46. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Molecule/test_standardize.py +0 -0
  47. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/__init__.py +0 -0
  48. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_aam_utils.py +0 -0
  49. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_aam_validator.py +0 -0
  50. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_balance_checker.py +0 -0
  51. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_cleanning.py +0 -0
  52. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_deionize.py +0 -0
  53. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_fix_aam.py +0 -0
  54. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_neutralize.py +0 -0
  55. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_rsmi_utils.py +0 -0
  56. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_standardize.py +0 -0
  57. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_tautomerize.py +0 -0
  58. {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/__init__.py +0 -0
  59. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Context/__init__.py +0 -0
  60. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Context/test_hier_context.py +0 -0
  61. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Context/test_radius_expand.py +0 -0
  62. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/__init__.py +0 -0
  63. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_graph_descriptors.py +0 -0
  64. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_graph_fps.py +0 -0
  65. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_graph_signature.py +0 -0
  66. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_hash_fps.py +0 -0
  67. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_morgan_fps.py +0 -0
  68. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_path_fps.py +0 -0
  69. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/__init__.py +0 -0
  70. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/test_graph_hydrogen.py +0 -0
  71. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/test_hcomplete.py +0 -0
  72. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/test_misc.py +0 -0
  73. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/__init__.py +0 -0
  74. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/test_its_construction.py +0 -0
  75. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/test_its_expand.py +0 -0
  76. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/test_normalize_aam.py +0 -0
  77. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/__init__.py +0 -0
  78. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/test_group_comp.py +0 -0
  79. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/test_groupoid.py +0 -0
  80. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/test_mtg.py +0 -0
  81. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/__init__.py +0 -0
  82. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_batch_cluster.py +0 -0
  83. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_graph_cluster.py +0 -0
  84. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_graph_matcher.py +0 -0
  85. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_graph_morphism.py +0 -0
  86. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_subgraph_matcher.py +0 -0
  87. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/__init__.py +0 -0
  88. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/test_canon_graph.py +0 -0
  89. {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/test_syn_graph.py +0 -0
  90. {synkit-0.0.8 → synkit-0.0.9}/Test/IO/__init__.py +0 -0
  91. {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_chemical_converter.py +0 -0
  92. {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_dg_to_gml.py +0 -0
  93. {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_gml_to_nx.py +0 -0
  94. {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_graph_to_mol.py +0 -0
  95. {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_mol_to_graph.py +0 -0
  96. {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_nx_to_gml.py +0 -0
  97. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/__init__.py +0 -0
  98. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/test_reactor_rule.py +0 -0
  99. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/test_retro_reactor.py +0 -0
  100. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/test_rule_rbl.py +0 -0
  101. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Compose/__init__.py +0 -0
  102. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Compose/test_rule_compose.py +0 -0
  103. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Compose/test_valance_constrain.py +0 -0
  104. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Modify/__init__.py +0 -0
  105. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/__init__.py +0 -0
  106. {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/test_syn_rule.py +0 -0
  107. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/CRN/__init__.py +0 -0
  108. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/CRN/test_crn.py +0 -0
  109. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/CRN/test_mod_crn.py +0 -0
  110. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/MSR/__init__.py +0 -0
  111. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/MSR/test_multi_steps.py +0 -0
  112. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/MSR/test_path_finder.py +0 -0
  113. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/__init__.py +0 -0
  114. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_core_engine.py +0 -0
  115. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_mod_aam.py +0 -0
  116. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_mod_reactor.py +0 -0
  117. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_strategy.py +0 -0
  118. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/__init__.py +0 -0
  119. {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/test_reactor_utils.py +0 -0
  120. {synkit-0.0.8 → synkit-0.0.9}/Test/Vis/__init__.py +0 -0
  121. {synkit-0.0.8 → synkit-0.0.9}/Test/Vis/test_embedding.py +0 -0
  122. {synkit-0.0.8 → synkit-0.0.9}/Test/__init__.py +0 -0
  123. {synkit-0.0.8 → synkit-0.0.9}/build-doc.sh +0 -0
  124. {synkit-0.0.8 → synkit-0.0.9}/doc/api.rst +0 -0
  125. {synkit-0.0.8 → synkit-0.0.9}/doc/changelog.rst +0 -0
  126. {synkit-0.0.8 → synkit-0.0.9}/doc/chem.rst +0 -0
  127. {synkit-0.0.8 → synkit-0.0.9}/doc/figures/aldol.png +0 -0
  128. {synkit-0.0.8 → synkit-0.0.9}/doc/figures/aldol_its.png +0 -0
  129. {synkit-0.0.8 → synkit-0.0.9}/doc/figures/mtg.png +0 -0
  130. {synkit-0.0.8 → synkit-0.0.9}/doc/getting_started.rst +0 -0
  131. {synkit-0.0.8 → synkit-0.0.9}/doc/graph.rst +0 -0
  132. {synkit-0.0.8 → synkit-0.0.9}/doc/io.rst +0 -0
  133. {synkit-0.0.8 → synkit-0.0.9}/doc/reference.rst +0 -0
  134. {synkit-0.0.8 → synkit-0.0.9}/doc/refs.bib +0 -0
  135. {synkit-0.0.8 → synkit-0.0.9}/doc/rule.rst +0 -0
  136. {synkit-0.0.8 → synkit-0.0.9}/doc/synthesis.rst +0 -0
  137. {synkit-0.0.8 → synkit-0.0.9}/environment.yml +0 -0
  138. {synkit-0.0.8 → synkit-0.0.9}/lint.sh +0 -0
  139. {synkit-0.0.8 → synkit-0.0.9}/make.bat +0 -0
  140. {synkit-0.0.8 → synkit-0.0.9}/pytest.sh +0 -0
  141. {synkit-0.0.8 → synkit-0.0.9}/requirements.txt +0 -0
  142. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/__init__.py +0 -0
  143. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
  144. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
  145. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/transformation_fp.py +0 -0
  146. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Molecule/__init__.py +0 -0
  147. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Molecule/standardize.py +0 -0
  148. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/__init__.py +0 -0
  149. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/aam_utils.py +0 -0
  150. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/aam_validator.py +0 -0
  151. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/balance_check.py +0 -0
  152. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
  153. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/cleanning.py +0 -0
  154. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/deionize.py +0 -0
  155. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/fix_aam.py +0 -0
  156. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/neutralize.py +0 -0
  157. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/rsmi_utils.py +0 -0
  158. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/standardize.py +0 -0
  159. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/tautomerize.py +0 -0
  160. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/__init__.py +0 -0
  161. {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/utils.py +0 -0
  162. {synkit-0.0.8/synkit/Graph → synkit-0.0.9/synkit/Graph/Canon}/canon_algs.py +0 -0
  163. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Context/__init__.py +0 -0
  164. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Context/hier_context.py +0 -0
  165. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Context/radius_expand.py +0 -0
  166. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/__init__.py +0 -0
  167. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/graph_descriptors.py +0 -0
  168. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/graph_fps.py +0 -0
  169. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/graph_signature.py +0 -0
  170. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/hash_fps.py +0 -0
  171. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/morgan_fps.py +0 -0
  172. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/path_fps.py +0 -0
  173. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/wl_hash.py +0 -0
  174. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/__init__.py +0 -0
  175. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
  176. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/hextend.py +0 -0
  177. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/__init__.py +0 -0
  178. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_builder.py +0 -0
  179. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_construction.py +0 -0
  180. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_decompose.py +0 -0
  181. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_expand.py +0 -0
  182. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/normalize_aam.py +0 -0
  183. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/__init__.py +0 -0
  184. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/group_comp.py +0 -0
  185. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/groupoid.py +0 -0
  186. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/mcs_matcher.py +0 -0
  187. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/mtg.py +0 -0
  188. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/batch_cluster.py +0 -0
  189. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/graph_cluster.py +0 -0
  190. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/graph_matcher.py +0 -0
  191. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/graph_morphism.py +0 -0
  192. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/sing.py +0 -0
  193. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/subgraph_matcher.py +0 -0
  194. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/syn_graph.py +0 -0
  195. {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/utils.py +0 -0
  196. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/__init__.py +0 -0
  197. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/data_io.py +0 -0
  198. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/data_process.py +0 -0
  199. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/debug.py +0 -0
  200. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/dg_to_gml.py +0 -0
  201. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/gml_to_nx.py +0 -0
  202. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/graph_to_mol.py +0 -0
  203. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/mol_to_graph.py +0 -0
  204. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/nx_to_gml.py +0 -0
  205. {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/smiles_to_id.py +0 -0
  206. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/__init__.py +0 -0
  207. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/reactor_rule.py +0 -0
  208. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/retro_reactor.py +0 -0
  209. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/rule_rbl.py +0 -0
  210. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/__init__.py +0 -0
  211. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/compose_rule.py +0 -0
  212. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/rule_compose.py +0 -0
  213. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/rule_mapping.py +0 -0
  214. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/seq_comp.py +0 -0
  215. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/valence_constrain.py +0 -0
  216. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/__init__.py +0 -0
  217. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/implict_rule.py +0 -0
  218. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/longest_path.py +0 -0
  219. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/molecule_rule.py +0 -0
  220. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/prune_templates.py +0 -0
  221. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/rule_utils.py +0 -0
  222. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/strip_rule.py +0 -0
  223. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/__init__.py +0 -0
  224. {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/syn_rule.py +0 -0
  225. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/__init__.py +0 -0
  226. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/crn.py +0 -0
  227. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/dcrn.py +0 -0
  228. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/mod_crn.py +0 -0
  229. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/MSR/__init__.py +0 -0
  230. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/MSR/multi_steps.py +0 -0
  231. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/MSR/path_finder.py +0 -0
  232. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/__init__.py +0 -0
  233. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/_base.py +0 -0
  234. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/_plot.py +0 -0
  235. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/_ranking.py +0 -0
  236. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/__init__.py +0 -0
  237. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/core_engine.py +0 -0
  238. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
  239. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
  240. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
  241. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/syn_reactor.py +0 -0
  242. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/__init__.py +0 -0
  243. {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/reactor_utils.py +0 -0
  244. {synkit-0.0.8 → synkit-0.0.9}/synkit/Utils/__init__.py +0 -0
  245. {synkit-0.0.8 → synkit-0.0.9}/synkit/Utils/utils.py +0 -0
  246. {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/__init__.py +0 -0
  247. {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/chemical_space.py +0 -0
  248. {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/embedding.py +0 -0
  249. {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/graph_visualizer.py +0 -0
  250. {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/pdf_writer.py +0 -0
  251. {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/rule_vis.py +0 -0
  252. {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/rxn_vis.py +0 -0
  253. {synkit-0.0.8 → synkit-0.0.9}/synkit/__init__.py +0 -0
@@ -19,4 +19,5 @@ Data/Benchmark/*
19
19
  # *.png
20
20
  *.json.gz
21
21
  run.sh
22
- docs/*
22
+ docs/*
23
+ run_rdcanon.py
@@ -0,0 +1,24 @@
1
+ # Read the Docs configuration file
2
+ # https://docs.readthedocs.io/en/stable/config-file/v2.html
3
+
4
+ # 1) Tell RTD this is v2 of the schema
5
+ version: 2
6
+
7
+ # 2) Pick your build image and interpreter
8
+ build:
9
+ os: ubuntu-22.04
10
+ tools:
11
+ python: "3.11"
12
+
13
+ # 3) Install your package + any doc-only extras
14
+ python:
15
+ install:
16
+ - method: pip
17
+ path: .
18
+ extra_requirements:
19
+ - docs
20
+
21
+ # 4) Point Sphinx at your conf.py and choose HTML
22
+ sphinx:
23
+ configuration: doc/conf.py
24
+ builder: html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synkit
3
- Version: 0.0.8
3
+ Version: 0.0.9
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
@@ -22,6 +22,10 @@ Requires-Dist: seaborn>=0.13.2
22
22
  Provides-Extra: all
23
23
  Requires-Dist: numpy>=2.2.0; extra == 'all'
24
24
  Requires-Dist: torch>=2.2.0; extra == 'all'
25
+ Provides-Extra: docs
26
+ Requires-Dist: sphinx-rtd-theme; extra == 'docs'
27
+ Requires-Dist: sphinx>=6.0; extra == 'docs'
28
+ Requires-Dist: sphinxcontrib-bibtex; extra == 'docs'
25
29
  Description-Content-Type: text/markdown
26
30
 
27
31
  # SynKit
@@ -7,7 +7,11 @@ from synkit.IO.chem_converter import rsmi_to_graph
7
7
  class TestCanonRSMI(unittest.TestCase):
8
8
  def setUp(self):
9
9
  # Use generic backend for deterministic behavior
10
- self.canon = CanonRSMI(backend="wl", wl_iterations=5)
10
+ self.canon = CanonRSMI(
11
+ backend="wl",
12
+ wl_iterations=5,
13
+ node_attrs=["element", "aromatic", "charge", "hcount", "neighbors"],
14
+ )
11
15
  # Example reaction SMILES with atom-map labels
12
16
  self.input_rsmi = "[CH3:3][CH2:5][OH:10]>>[CH2:3]=[CH2:5].[OH2:10]"
13
17
  # After expand_aam and canonicalisation, since backend is generic,
@@ -1,6 +1,6 @@
1
1
  import unittest
2
2
  from synkit.Rule.Modify.molecule_rule import MoleculeRule
3
- from synkit.Graph.Matcher import GraphMatcherEngine
3
+ from synkit.Graph.Matcher.graph_matcher import GraphMatcherEngine
4
4
  import importlib
5
5
 
6
6
  MOD_AVAILABLE = importlib.util.find_spec("mod") is not None
@@ -7,7 +7,8 @@ from synkit.Rule.Modify.rule_utils import (
7
7
  strip_context,
8
8
  _increment_gml_ids,
9
9
  )
10
- from synkit.Graph.Matcher import GraphMatcherEngine, SubgraphMatch
10
+ from synkit.Graph.Matcher.graph_matcher import GraphMatcherEngine
11
+ from synkit.Graph.Matcher.subgraph_matcher import SubgraphMatch
11
12
  import importlib
12
13
 
13
14
  MOD_AVAILABLE = importlib.util.find_spec("mod") is not None
@@ -1,31 +1,34 @@
1
- # Configuration file for the Sphinx documentation builder.
2
- #
3
- # For the full list of built-in configuration values, see the documentation:
4
- # https://www.sphinx-doc.org/en/master/usage/configuration.html
1
+ import os
2
+ import sys
5
3
 
6
- # -- Project information -----------------------------------------------------
7
- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
4
+ sys.path.insert(0, os.path.abspath(".."))
8
5
 
6
+ # -- Project information -----------------------------------------------------
9
7
  project = "synkit"
10
- copyright = "2025, Tieu-Long Phan"
11
8
  author = "Tieu-Long Phan"
9
+ release = "0.0.8"
10
+ version = release
12
11
 
13
12
  # -- General configuration ---------------------------------------------------
14
- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
15
-
16
13
  extensions = [
17
14
  "sphinx.ext.autodoc",
18
15
  "sphinx.ext.autosectionlabel",
19
16
  "sphinx.ext.githubpages",
20
17
  "sphinxcontrib.bibtex",
18
+ # "sphinx.ext.napoleon", # un-comment if using Google/NumPy docstrings
21
19
  ]
20
+
22
21
  bibtex_bibfiles = ["refs.bib"]
23
22
  templates_path = ["_templates"]
24
23
  exclude_patterns = []
25
24
  autosectionlabel_prefix_document = True
26
25
 
27
- # -- Options for HTML output -------------------------------------------------
28
- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
26
+ autodoc_default_options = {
27
+ "members": True,
28
+ "undoc-members": True,
29
+ "show-inheritance": True,
30
+ }
29
31
 
32
+ # -- Options for HTML output -------------------------------------------------
30
33
  html_theme = "sphinx_rtd_theme"
31
34
  html_static_path = ["_static"]
@@ -1,5 +1,6 @@
1
1
  Welcome to synkit documentation!
2
2
  ================================
3
+ This is documentation for synkit version |version| (release |release|).
3
4
 
4
5
  .. toctree::
5
6
  :caption: Contents
@@ -0,0 +1,3 @@
1
+ sphinx
2
+ sphinx-rtd-theme
3
+ sphinxcontrib-bibtex
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "synkit"
7
- version = "0.0.8"
7
+ version = "0.0.9"
8
8
  authors = [
9
9
  {name="Tieu Long Phan", email="tieu@bioinf.uni-leipzig.de"}
10
10
  ]
@@ -30,7 +30,11 @@ dependencies = [
30
30
 
31
31
  [project.optional-dependencies]
32
32
  all = ["numpy>=2.2.0", "torch>=2.2.0"]
33
-
33
+ docs = [
34
+ "sphinx>=6.0",
35
+ "sphinx-rtd-theme",
36
+ "sphinxcontrib-bibtex",
37
+ ]
34
38
  [project.urls]
35
39
  homepage = "https://github.com/TieuLongPhan/SynKit"
36
40
  source = "https://github.com/TieuLongPhan/SynKit"
@@ -0,0 +1,3 @@
1
+ from .canon_graph import GraphCanonicaliser, CanonicalGraph
2
+
3
+ __all__ = ["GraphCanonicaliser", "CanonicalGraph"]
@@ -63,6 +63,7 @@ from networkx.algorithms.graph_hashing import (
63
63
  weisfeiler_lehman_subgraph_hashes as _wl_hashes,
64
64
  )
65
65
  from .canon_algs import canon_morgan
66
+ from .nauty import NautyCanonicalizer
66
67
 
67
68
  __all__: list[str] = ["CanonicalGraph", "GraphCanonicaliser", "CanonicalRule"]
68
69
 
@@ -145,14 +146,14 @@ class GraphCanonicaliser:
145
146
  def __init__(
146
147
  self,
147
148
  *,
148
- backend: Literal["generic", "wl", "morgan"] = "generic",
149
+ backend: Literal["generic", "wl", "morgan", "nauty"] = "generic",
149
150
  wl_iterations: int = 3,
150
151
  morgan_radius: int = 3,
151
152
  node_attrs: List[str] = ["element", "aromatic", "charge", "hcount"],
152
153
  node_sort_key: T_NodeSortKey = _default_node_key,
153
154
  edge_sort_key: T_EdgeSortKey = _default_edge_key,
154
155
  ) -> None:
155
- if backend not in {"generic", "wl", "morgan"}:
156
+ if backend not in {"generic", "wl", "morgan", "nauty"}:
156
157
  raise ValueError("backend must be 'generic' or 'wl' or 'morgan' ")
157
158
  self.backend: Literal["generic", "wl", "morgan"] = backend
158
159
  self._wl_k: int = wl_iterations
@@ -161,6 +162,8 @@ class GraphCanonicaliser:
161
162
  self._edge_key: T_EdgeSortKey = edge_sort_key
162
163
  self._wl_node_attrs: Tuple[str, ...] = tuple(node_attrs)
163
164
  self._mg_node_attrs: List[str] = node_attrs
165
+ if self.backend == "nauty":
166
+ self.nauty = NautyCanonicalizer(node_attrs=node_attrs, edge_attrs=["order"])
164
167
 
165
168
  # ------------------------------------------------------------------ #
166
169
  # High‑level helpers #
@@ -220,6 +223,8 @@ class GraphCanonicaliser:
220
223
  return self._canon_generic(g)
221
224
  elif self.backend == "wl":
222
225
  return self._canon_wl(g)
226
+ elif self.backend == "nauty":
227
+ return self._canon_nauty(g)
223
228
  else:
224
229
  return canon_morgan(
225
230
  g, morgan_radius=self._mg_k, node_attributes=self._mg_node_attrs
@@ -228,6 +233,9 @@ class GraphCanonicaliser:
228
233
  # self._canon_generic(g) if self.backend == "generic" else self._canon_wl(g)
229
234
  # )
230
235
 
236
+ def _canon_nauty(self, g: nx.Graph) -> nx.Graph:
237
+ return self.nauty.canonical_form(g)
238
+
231
239
  def _canon_generic(self, g: nx.Graph) -> nx.Graph:
232
240
  """Pure attribute‑sort strategy – O(|V| log |V| + |E| log |E|)."""
233
241
  nodes_sorted = sorted(g.nodes(data=True), key=lambda x: self._node_key(*x))
@@ -274,6 +282,7 @@ class GraphCanonicaliser:
274
282
  wl_hash = _wl_hashes(
275
283
  g2,
276
284
  node_attr=node_attr,
285
+ edge_attr="order",
277
286
  iterations=self._wl_k,
278
287
  )
279
288
 
@@ -0,0 +1,292 @@
1
+ import networkx as nx
2
+ import hashlib
3
+ from synkit.IO import setup_logging
4
+
5
+ logger = setup_logging()
6
+
7
+
8
+ class NautyCanonicalizer:
9
+ __slots__ = ("node_attrs", "edge_attrs")
10
+
11
+ def __init__(self, node_attrs=None, edge_attrs=None):
12
+ self.node_attrs = list(node_attrs) if node_attrs else []
13
+ self.edge_attrs = list(edge_attrs) if edge_attrs else []
14
+
15
+ @staticmethod
16
+ def _freeze(x):
17
+ if isinstance(x, list):
18
+ return tuple(NautyCanonicalizer._freeze(v) for v in x)
19
+ if isinstance(x, dict):
20
+ return frozenset(
21
+ (k, NautyCanonicalizer._freeze(v)) for k, v in sorted(x.items())
22
+ )
23
+ return x
24
+
25
+ def canonical_form(
26
+ self,
27
+ G,
28
+ return_aut=False,
29
+ remap_aut=False,
30
+ return_orbits=False,
31
+ return_perm=False,
32
+ max_depth=None,
33
+ ):
34
+ """
35
+ Compute canonical form of graph G with optional automorphisms, orbits, and early stopping.
36
+
37
+ :param G: NetworkX graph to canonicalize.
38
+ :param return_aut: bool, whether to return list of automorphism permutations.
39
+ Default: False.
40
+ :param remap_aut: bool, whether to remap automorphisms to canonical labels
41
+ (only valid if return_aut=True). Default: False.
42
+ :param return_orbits: bool, whether to return node orbits (symmetry groups). Default: False.
43
+ :param return_perm: bool, whether to return canonical permutation (ordering of nodes). Default: False.
44
+ :param max_depth: int or None, max recursion depth for backtracking search (early stopping).
45
+ Default: None (unlimited).
46
+ :return: tuple containing requested results and a boolean early_stop flag indicating if search terminated early.
47
+ The order of outputs is (G_canon, perm?, automorphisms?, orbits?, early_stop).
48
+ """
49
+ logger.debug(
50
+ f"Starting canonical_form: max_depth={max_depth},"
51
+ + f" return_aut={return_aut}, remap_aut={remap_aut},"
52
+ + f" return_orbits={return_orbits}, return_perm={return_perm}"
53
+ )
54
+
55
+ best = {"label": None, "perm": None}
56
+ aut_perms = []
57
+
58
+ initial_partition = self._initial_partition(G)
59
+ logger.debug(f"Initial partition: {initial_partition}")
60
+
61
+ early_stop_occurred = self._search(
62
+ G, initial_partition, [], best, aut_perms, depth=0, max_depth=max_depth
63
+ )
64
+
65
+ perm = best["perm"]
66
+ if perm is None:
67
+ logger.error(
68
+ f"Canonical form not found: search stopped early (max_depth={max_depth} too small)."
69
+ )
70
+ raise RuntimeError(
71
+ f"Canonical form not found: search stopped early (max_depth={max_depth} too small)."
72
+ )
73
+
74
+ mapping = {v: i + 1 for i, v in enumerate(perm)}
75
+ G_can = nx.relabel_nodes(G, mapping, copy=True)
76
+ self._update_atom_map(G_can)
77
+
78
+ results = [G_can]
79
+ if return_perm:
80
+ results.append(perm)
81
+
82
+ if return_aut:
83
+ if remap_aut:
84
+ remapped = [[mapping[v] for v in p] for p in aut_perms]
85
+ results.append(remapped)
86
+ else:
87
+ results.append(aut_perms)
88
+
89
+ if return_orbits:
90
+ orbits = self.compute_orbits(aut_perms)
91
+ if remap_aut and return_aut:
92
+ orbits = [set(mapping[v] for v in orbit) for orbit in orbits]
93
+ results.append(orbits)
94
+
95
+ results.append(early_stop_occurred)
96
+
97
+ logger.debug(
98
+ f"canonical_form completed, early_stop_occurred={early_stop_occurred}"
99
+ )
100
+ return tuple(results) if len(results) > 2 else results[0]
101
+
102
+ def _update_atom_map(self, G):
103
+ for n in G.nodes():
104
+ G.nodes[n]["atom_map"] = n
105
+
106
+ def _initial_partition(self, G):
107
+ if not self.node_attrs:
108
+ return [sorted(G.nodes())]
109
+ buckets = {}
110
+ for v in G.nodes():
111
+ key = tuple(
112
+ self._freeze(G.nodes[v].get(attr, None)) for attr in self.node_attrs
113
+ )
114
+ buckets.setdefault(key, []).append(v)
115
+ return [sorted(nodes) for _, nodes in sorted(buckets.items())]
116
+
117
+ def _node_signature(self, G, v, partition):
118
+ node_attrs = tuple(
119
+ self._freeze(G.nodes[v].get(a, None)) for a in self.node_attrs
120
+ )
121
+ degree = G.degree[v]
122
+
123
+ nbr_part_counts = []
124
+ for cell in partition:
125
+ count = sum(1 for nbr in G.neighbors(v) if nbr in cell)
126
+ nbr_part_counts.append(count)
127
+ nbr_part_counts = tuple(nbr_part_counts)
128
+
129
+ edge_attr_multiset = []
130
+ for nbr in G.neighbors(v):
131
+ attrs = G[v][nbr]
132
+ edge_attrs = []
133
+ for a in self.edge_attrs:
134
+ val = attrs.get(a, None)
135
+ if a == "order" and isinstance(val, tuple):
136
+ val = tuple(sorted(round(float(x), 3) for x in val))
137
+ edge_attrs.append(self._freeze(val))
138
+ edge_attr_multiset.append(tuple(edge_attrs))
139
+ edge_attr_multiset = tuple(sorted(edge_attr_multiset))
140
+
141
+ return (node_attrs, degree, nbr_part_counts, edge_attr_multiset)
142
+
143
+ def _refine(self, G, partition):
144
+ changed = True
145
+ while changed:
146
+ changed = False
147
+ new_partition = []
148
+ sig_cache = {}
149
+ for cell in partition:
150
+ if len(cell) <= 1:
151
+ new_partition.append(cell)
152
+ continue
153
+ sigs = {}
154
+ for v in cell:
155
+ if v not in sig_cache:
156
+ sig_cache[v] = self._node_signature(G, v, partition)
157
+ sig = sig_cache[v]
158
+ sigs.setdefault(sig, []).append(v)
159
+ if len(sigs) > 1:
160
+ changed = True
161
+ for sig in sorted(sigs):
162
+ new_partition.append(sorted(sigs[sig]))
163
+ else:
164
+ new_partition.append(cell)
165
+ partition = new_partition
166
+ return partition
167
+
168
+ def _search(self, G, partition, prefix, best, aut_perms, depth=0, max_depth=None):
169
+ if max_depth is not None and depth > max_depth:
170
+ logger.debug(
171
+ f"Early stopping at depth {depth} due to max_depth={max_depth}"
172
+ )
173
+ return True # early stop triggered
174
+
175
+ partition = self._refine(G, partition)
176
+ if all(len(c) == 1 for c in partition):
177
+ perm = prefix + [v for c in partition for v in c]
178
+ label = self._build_label(G, perm)
179
+ if best["label"] is None or label < best["label"]:
180
+ best["label"], best["perm"] = label, perm
181
+ aut_perms.clear()
182
+ aut_perms.append(perm)
183
+ logger.debug(f"New best label found at depth {depth}")
184
+ elif label == best["label"]:
185
+ aut_perms.append(perm)
186
+ logger.debug(f"Equivalent label found at depth {depth}")
187
+ return False
188
+
189
+ idx = next(i for i, c in enumerate(partition) if len(c) > 1)
190
+ cell = partition[idx]
191
+ sorted_cell = sorted(cell, key=lambda n: G.nodes[n].get("atom_map", n))
192
+
193
+ for v in sorted_cell:
194
+ rest = [w for w in cell if w != v]
195
+ # fmt: off
196
+ new_partition = (
197
+ partition[:idx]
198
+ + [[v]]
199
+ + ([sorted(rest)] if rest else [])
200
+ + partition[idx + 1:]
201
+ )
202
+ # fmt: on
203
+ candidate_prefix = prefix + [v]
204
+
205
+ partial_label = self._build_partial_label(G, candidate_prefix)
206
+
207
+ if best["label"] is not None and partial_label > best["label"]:
208
+ logger.debug(f"Pruning branch at depth {depth} due to partial label")
209
+ continue # prune branch early
210
+
211
+ if self._search(
212
+ G,
213
+ new_partition,
214
+ candidate_prefix,
215
+ best,
216
+ aut_perms,
217
+ depth=depth + 1,
218
+ max_depth=max_depth,
219
+ ):
220
+ return True # propagate early stop upward
221
+
222
+ return False
223
+
224
+ def _build_label(self, G, perm):
225
+ node_segment = "|".join(
226
+ ":".join(
227
+ str(self._freeze(G.nodes[v].get(attr, ""))) for attr in self.node_attrs
228
+ )
229
+ for v in perm
230
+ )
231
+ n = len(perm)
232
+ edge_bits = []
233
+ for i in range(n):
234
+ vi = perm[i]
235
+ for j in range(i + 1, n):
236
+ vj = perm[j]
237
+ if G.has_edge(vi, vj):
238
+ attrs = G[vi][vj]
239
+ frozen_attrs = tuple(
240
+ self._freeze(attrs.get(a, "")) for a in self.edge_attrs
241
+ )
242
+ edge_bits.append("1:" + ":".join(str(x) for x in frozen_attrs))
243
+ else:
244
+ edge_bits.append("0:" + ":".join("" for _ in self.edge_attrs))
245
+ edge_segment = "|".join(edge_bits)
246
+ return node_segment + "||" + edge_segment
247
+
248
+ def _build_partial_label(self, G, prefix):
249
+ node_segment = "|".join(
250
+ ":".join(
251
+ str(self._freeze(G.nodes[v].get(attr, ""))) for attr in self.node_attrs
252
+ )
253
+ for v in prefix
254
+ )
255
+ suffix = "{" * 1000 # lexicographically larger than any label char
256
+ return node_segment + suffix
257
+
258
+ def compute_orbits(self, aut_perms):
259
+ if not aut_perms:
260
+ return []
261
+
262
+ orbit_map = {}
263
+ orbits = []
264
+
265
+ def union_orbits(i, j):
266
+ if i == j:
267
+ return
268
+ o1 = orbits[i]
269
+ o2 = orbits[j]
270
+ if len(o1) < len(o2):
271
+ i, j = j, i
272
+ o1, o2 = o2, o1
273
+ o1.update(o2)
274
+ orbits[j] = set()
275
+ for v in o2:
276
+ orbit_map[v] = i
277
+
278
+ first_perm = aut_perms[0]
279
+ for idx, node in enumerate(first_perm):
280
+ orbit_map[node] = idx
281
+ orbits.append({node})
282
+
283
+ for perm in aut_perms:
284
+ for idx, node in enumerate(perm):
285
+ union_orbits(idx, orbit_map[node])
286
+
287
+ return [o for o in orbits if o]
288
+
289
+ def graph_signature(self, G):
290
+ G_canon = self.canonical_form(G)
291
+ label = self._build_label(G_canon, sorted(G_canon.nodes()))
292
+ return hashlib.sha256(label.encode("utf-8")).hexdigest()
@@ -1,7 +1,7 @@
1
1
  from copy import copy
2
2
  import networkx as nx
3
3
  from operator import eq
4
- from typing import List, Any, Set, Tuple
4
+ from typing import List, Set, Any, Tuple
5
5
  from networkx.algorithms.isomorphism import generic_node_match, generic_edge_match
6
6
 
7
7
  from synkit.Graph.Feature.graph_descriptors import GraphDescriptor
@@ -91,7 +91,22 @@ def h_to_implicit(G: nx.Graph) -> nx.Graph:
91
91
  return H2
92
92
 
93
93
 
94
- def h_to_explicit(G: nx.Graph, nodes: List[int] = None) -> nx.Graph:
94
+ def normalize_edge_orders(G: nx.Graph) -> None:
95
+ """
96
+ In-place normalize all edge attributes in G:
97
+ - If 'order' is a float or int, replace it with (order, order).
98
+ - If 'standard_order' is missing, set it to 0.0.
99
+ """
100
+ for _, _, data in G.edges(data=True):
101
+ o = data.get("order")
102
+ # Wrap scalar orders into tuples
103
+ if isinstance(o, (int, float)):
104
+ data["order"] = (float(o), float(o))
105
+ if "standard_order" not in data:
106
+ data["standard_order"] = 0.0
107
+
108
+
109
+ def h_to_explicit(G: nx.Graph, nodes: List[int] = None, its: bool = False) -> nx.Graph:
95
110
  """
96
111
  Convert implicit hydrogen counts on heavy atoms into explicit hydrogen nodes.
97
112
 
@@ -146,6 +161,8 @@ def h_to_explicit(G: nx.Graph, nodes: List[int] = None) -> nx.Graph:
146
161
  tgh_list = [list(row) for row in tgh]
147
162
  tgh_list[0][2] -= count # Assume hcount is stored at position [0][2]
148
163
  H2.nodes[heavy]["typesGH"] = tuple(tuple(row) for row in tgh_list)
164
+ if its:
165
+ normalize_edge_orders(H2)
149
166
 
150
167
  return H2
151
168
 
@@ -0,0 +1,10 @@
1
+ # from .graph_matcher import GraphMatcherEngine
2
+ # from .subgraph_matcher import SubgraphMatch, SubgraphSearchEngine
3
+ # from .graph_cluster import GraphCluster
4
+
5
+ # __all__ = [
6
+ # "GraphMatcherEngine",
7
+ # "SubgraphMatch",
8
+ # "SubgraphSearchEngine",
9
+ # "GraphCluster",
10
+ # ]