synkit 1.6.5__py3-none-win_amd64.whl

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 (636) hide show
  1. synkit/CRN/Benchmark/__init__.py +53 -0
  2. synkit/CRN/Benchmark/crosschecks.py +13 -0
  3. synkit/CRN/Benchmark/kegg.py +12 -0
  4. synkit/CRN/Benchmark/networks.py +13 -0
  5. synkit/CRN/Benchmark/scaling.py +13 -0
  6. synkit/CRN/Benchmark/validate.py +13 -0
  7. synkit/CRN/Construct/__init__.py +16 -0
  8. synkit/CRN/Construct/abstract.py +668 -0
  9. synkit/CRN/Construct/arity.py +36 -0
  10. synkit/CRN/Construct/builder.py +619 -0
  11. synkit/CRN/Construct/derivation.py +63 -0
  12. synkit/CRN/Construct/flattener.py +100 -0
  13. synkit/CRN/Construct/keys.py +14 -0
  14. synkit/CRN/Construct/mixtures.py +117 -0
  15. synkit/CRN/Construct/rdkit_worker.py +69 -0
  16. synkit/CRN/Construct/smiles.py +99 -0
  17. synkit/CRN/Construct/state.py +31 -0
  18. synkit/CRN/Construct/strategy.py +75 -0
  19. synkit/CRN/Construct/worker.py +42 -0
  20. synkit/CRN/IO/__init__.py +36 -0
  21. synkit/CRN/IO/sbml.py +925 -0
  22. synkit/CRN/Pathway/__init__.py +72 -0
  23. synkit/CRN/Pathway/_adapter.py +333 -0
  24. synkit/CRN/Pathway/pathfinder.py +500 -0
  25. synkit/CRN/Pathway/reachability.py +796 -0
  26. synkit/CRN/Pathway/realizability.py +1322 -0
  27. synkit/CRN/Petrinet/__init__.py +93 -0
  28. synkit/CRN/Petrinet/analyzer.py +486 -0
  29. synkit/CRN/Petrinet/minimal_semiflows.py +436 -0
  30. synkit/CRN/Petrinet/net.py +1041 -0
  31. synkit/CRN/Petrinet/persistence.py +97 -0
  32. synkit/CRN/Petrinet/semiflows.py +483 -0
  33. synkit/CRN/Petrinet/siphon_search.py +402 -0
  34. synkit/CRN/Petrinet/structure.py +286 -0
  35. synkit/CRN/Props/__init__.py +130 -0
  36. synkit/CRN/Props/_rank.py +29 -0
  37. synkit/CRN/Props/deficiency.py +953 -0
  38. synkit/CRN/Props/dynamics.py +832 -0
  39. synkit/CRN/Props/helper.py +151 -0
  40. synkit/CRN/Props/stoich.py +751 -0
  41. synkit/CRN/Props/thermo.py +584 -0
  42. synkit/CRN/Query/__init__.py +66 -0
  43. synkit/CRN/Query/cache.py +41 -0
  44. synkit/CRN/Query/kegg_api.py +99 -0
  45. synkit/CRN/Query/kegg_extract.py +714 -0
  46. synkit/CRN/Query/kegg_impute.py +707 -0
  47. synkit/CRN/Query/kegg_parse.py +480 -0
  48. synkit/CRN/Query/to_syncrn.py +290 -0
  49. synkit/CRN/README.md +234 -0
  50. synkit/CRN/Structure/__init__.py +12 -0
  51. synkit/CRN/Structure/_graph_io.py +713 -0
  52. synkit/CRN/Structure/_matrices.py +88 -0
  53. synkit/CRN/Structure/_parse.py +544 -0
  54. synkit/CRN/Structure/reaction.py +237 -0
  55. synkit/CRN/Structure/rule.py +55 -0
  56. synkit/CRN/Structure/species.py +46 -0
  57. synkit/CRN/Structure/syncrn.py +929 -0
  58. synkit/CRN/Symmetry/__init__.py +20 -0
  59. synkit/CRN/Symmetry/_common.py +1059 -0
  60. synkit/CRN/Symmetry/_ir.py +940 -0
  61. synkit/CRN/Symmetry/automorphism.py +472 -0
  62. synkit/CRN/Symmetry/canon.py +441 -0
  63. synkit/CRN/Symmetry/isomorphism.py +412 -0
  64. synkit/CRN/Symmetry/symmetry.py +61 -0
  65. synkit/CRN/Symmetry/wl_canon.py +967 -0
  66. synkit/CRN/Visualize/__init__.py +11 -0
  67. synkit/CRN/Visualize/labels.py +113 -0
  68. synkit/CRN/Visualize/layout.py +708 -0
  69. synkit/CRN/Visualize/palette.py +292 -0
  70. synkit/CRN/Visualize/validation.py +122 -0
  71. synkit/CRN/Visualize/vis.py +1152 -0
  72. synkit/CRN/__init__.py +233 -0
  73. synkit/CRN/_build.py +50 -0
  74. synkit/CRN/_native_build.py +175 -0
  75. synkit/CRN/analysis.py +629 -0
  76. synkit/CRN/backend.py +74 -0
  77. synkit/CRN/cpp/CMakeLists.txt +58 -0
  78. synkit/CRN/cpp/algebra.cpp +46 -0
  79. synkit/CRN/cpp/closure.cpp +29 -0
  80. synkit/CRN/cpp/exact.h +55 -0
  81. synkit/CRN/cpp/pathway.cpp +46 -0
  82. synkit/CRN/cpp/semiflows.cpp +209 -0
  83. synkit/CRN/cpp/structural.cpp +240 -0
  84. synkit/CRN/cpp/symmetry.cpp +45 -0
  85. synkit/CRN/cpp/write_manifest.cmake +28 -0
  86. synkit/CRN/kinds.py +137 -0
  87. synkit/CRN/lib/synkit_crn.dll +0 -0
  88. synkit/CRN/lib/synkit_crn.json +33 -0
  89. synkit/CRN/native.py +388 -0
  90. synkit/CRN/native_analysis.py +459 -0
  91. synkit/Chem/Cluster/__init__.py +0 -0
  92. synkit/Chem/Cluster/butina.py +140 -0
  93. synkit/Chem/Fingerprint/__init__.py +0 -0
  94. synkit/Chem/Fingerprint/fp_calculator.py +155 -0
  95. synkit/Chem/Fingerprint/smiles_featurizer.py +258 -0
  96. synkit/Chem/Fingerprint/transformation_fp.py +139 -0
  97. synkit/Chem/Mapper/README.rst +43 -0
  98. synkit/Chem/Mapper/__init__.py +255 -0
  99. synkit/Chem/Mapper/_compat.py +130 -0
  100. synkit/Chem/Mapper/adapters/__init__.py +1 -0
  101. synkit/Chem/Mapper/adapters/prediction.py +126 -0
  102. synkit/Chem/Mapper/adapters/template.py +412 -0
  103. synkit/Chem/Mapper/chem/__init__.py +52 -0
  104. synkit/Chem/Mapper/chem/aam.py +673 -0
  105. synkit/Chem/Mapper/chem/blind.py +160 -0
  106. synkit/Chem/Mapper/chem/its.py +309 -0
  107. synkit/Chem/Mapper/chem/smiles.py +564 -0
  108. synkit/Chem/Mapper/graph/__init__.py +66 -0
  109. synkit/Chem/Mapper/graph/automorphism.py +395 -0
  110. synkit/Chem/Mapper/graph/blockcut.py +98 -0
  111. synkit/Chem/Mapper/graph/labeled_graph.py +104 -0
  112. synkit/Chem/Mapper/graph/refine.py +176 -0
  113. synkit/Chem/Mapper/graph/synkit_adapter.py +110 -0
  114. synkit/Chem/Mapper/io/__init__.py +13 -0
  115. synkit/Chem/Mapper/io/idxmapstr.py +82 -0
  116. synkit/Chem/Mapper/mapper.py +685 -0
  117. synkit/Chem/Mapper/reaction_search.py +554 -0
  118. synkit/Chem/Mapper/reporting/__init__.py +1 -0
  119. synkit/Chem/Mapper/reporting/alternatives.py +451 -0
  120. synkit/Chem/Mapper/reporting/analysis.py +681 -0
  121. synkit/Chem/Mapper/reporting/native_analysis.py +485 -0
  122. synkit/Chem/Mapper/reporting/spectrum.py +642 -0
  123. synkit/Chem/Mapper/solvers/__init__.py +1 -0
  124. synkit/Chem/Mapper/solvers/exact/__init__.py +141 -0
  125. synkit/Chem/Mapper/solvers/exact/assignment/__init__.py +1 -0
  126. synkit/Chem/Mapper/solvers/exact/assignment/assignment_certificate.py +113 -0
  127. synkit/Chem/Mapper/solvers/exact/assignment/assignment_domains.py +162 -0
  128. synkit/Chem/Mapper/solvers/exact/assignment/assignment_verification.py +58 -0
  129. synkit/Chem/Mapper/solvers/exact/assignment/incremental_assignment.py +247 -0
  130. synkit/Chem/Mapper/solvers/exact/certificate.py +128 -0
  131. synkit/Chem/Mapper/solvers/exact/core.py +183 -0
  132. synkit/Chem/Mapper/solvers/exact/distance_search/__init__.py +1 -0
  133. synkit/Chem/Mapper/solvers/exact/distance_search/bounds.py +319 -0
  134. synkit/Chem/Mapper/solvers/exact/distance_search/exhaustive.py +179 -0
  135. synkit/Chem/Mapper/solvers/exact/distance_search/inputs.py +283 -0
  136. synkit/Chem/Mapper/solvers/exact/distance_search/records.py +242 -0
  137. synkit/Chem/Mapper/solvers/exact/distance_search/search.py +1025 -0
  138. synkit/Chem/Mapper/solvers/exact/distance_search/verify.py +378 -0
  139. synkit/Chem/Mapper/solvers/exact/edit_support.py +375 -0
  140. synkit/Chem/Mapper/solvers/exact/hybrid.py +224 -0
  141. synkit/Chem/Mapper/solvers/exact/hydrogen.py +264 -0
  142. synkit/Chem/Mapper/solvers/exact/kernel_solvers/__init__.py +1 -0
  143. synkit/Chem/Mapper/solvers/exact/kernel_solvers/branching.py +499 -0
  144. synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumerate.py +1050 -0
  145. synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumeration_result.py +30 -0
  146. synkit/Chem/Mapper/solvers/exact/kernel_solvers/kernel.py +256 -0
  147. synkit/Chem/Mapper/solvers/exact/kernel_solvers/milp.py +194 -0
  148. synkit/Chem/Mapper/solvers/exact/native/__init__.py +1 -0
  149. synkit/Chem/Mapper/solvers/exact/native/build.py +154 -0
  150. synkit/Chem/Mapper/solvers/exact/native/candidates.py +593 -0
  151. synkit/Chem/Mapper/solvers/exact/native/canonical.py +396 -0
  152. synkit/Chem/Mapper/solvers/exact/native/disk_frontier.py +415 -0
  153. synkit/Chem/Mapper/solvers/exact/native/frontier.py +390 -0
  154. synkit/Chem/Mapper/solvers/exact/native/hydrogen_quotient.py +213 -0
  155. synkit/Chem/Mapper/solvers/exact/native/its.py +528 -0
  156. synkit/Chem/Mapper/solvers/exact/native/native_distance.cpp +2803 -0
  157. synkit/Chem/Mapper/solvers/exact/native/orbit_aggregation.py +328 -0
  158. synkit/Chem/Mapper/solvers/exact/native/parallel.py +205 -0
  159. synkit/Chem/Mapper/solvers/exact/native/pmcd.py +203 -0
  160. synkit/Chem/Mapper/solvers/exact/native/runtime.py +40 -0
  161. synkit/Chem/Mapper/solvers/exact/native/search.py +294 -0
  162. synkit/Chem/Mapper/solvers/exact/native/separator.py +224 -0
  163. synkit/Chem/Mapper/solvers/exact/native_build.py +12 -0
  164. synkit/Chem/Mapper/solvers/exact/pabs/__init__.py +1 -0
  165. synkit/Chem/Mapper/solvers/exact/pabs/cost_lattice.py +43 -0
  166. synkit/Chem/Mapper/solvers/exact/pabs/factor_spectrum.py +110 -0
  167. synkit/Chem/Mapper/solvers/exact/pabs/multi_shell.py +368 -0
  168. synkit/Chem/Mapper/solvers/exact/pabs/propagation.py +679 -0
  169. synkit/Chem/Mapper/solvers/exact/pabs/propagation_bounds.py +436 -0
  170. synkit/Chem/Mapper/solvers/exact/pabs/propagation_limits.py +13 -0
  171. synkit/Chem/Mapper/solvers/exact/pabs/propagation_search.py +856 -0
  172. synkit/Chem/Mapper/solvers/exact/pabs/propagation_symmetry.py +66 -0
  173. synkit/Chem/Mapper/solvers/exact/pabs/residual_typed_bonds.py +136 -0
  174. synkit/Chem/Mapper/solvers/exact/pabs/reward_frontier.py +201 -0
  175. synkit/Chem/Mapper/solvers/exact/pabs/separator_bound.py +258 -0
  176. synkit/Chem/Mapper/solvers/exact/pabs/separator_spectrum.py +308 -0
  177. synkit/Chem/Mapper/solvers/exact/pabs/sparse_reward_bound.py +207 -0
  178. synkit/Chem/Mapper/solvers/exact/pabs/suffix_spectrum.py +299 -0
  179. synkit/Chem/Mapper/solvers/exact/search.py +55 -0
  180. synkit/Chem/Mapper/solvers/exact/seeds/__init__.py +1 -0
  181. synkit/Chem/Mapper/solvers/exact/seeds/seed.py +104 -0
  182. synkit/Chem/Mapper/solvers/exact/seeds/seed_fragments.py +148 -0
  183. synkit/Chem/Mapper/solvers/exact/seeds/seed_relaxation.py +181 -0
  184. synkit/Chem/Mapper/solvers/exact/symmetry.py +223 -0
  185. synkit/Chem/Mapper/solvers/native/__init__.py +4 -0
  186. synkit/Chem/Mapper/solvers/native/assignment.py +99 -0
  187. synkit/Chem/Mapper/solvers/native/astar.py +22 -0
  188. synkit/Chem/Mapper/solvers/native/build.py +385 -0
  189. synkit/Chem/Mapper/solvers/native/classification.py +641 -0
  190. synkit/Chem/Mapper/solvers/native/core.py +378 -0
  191. synkit/Chem/Mapper/solvers/native/cpp/CMakeLists.txt +120 -0
  192. synkit/Chem/Mapper/solvers/native/cpp/examples/map_reaction.cpp +29 -0
  193. synkit/Chem/Mapper/solvers/native/cpp/examples/slap_graph.cpp +63 -0
  194. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/assignment/assignment.h +18 -0
  195. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/astar.h +18 -0
  196. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/bound.h +33 -0
  197. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/pmcd_witness.h +16 -0
  198. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/runtime.h +130 -0
  199. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/hydrogen/hydrogen.h +73 -0
  200. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mapper.h +175 -0
  201. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mces/mces.h +9 -0
  202. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/pabs/seed.h +34 -0
  203. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/reaction/reaction.h +30 -0
  204. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/sepdp/sepdp.h +21 -0
  205. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/slap.h +72 -0
  206. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/solvers/solvers.h +16 -0
  207. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/classification.h +140 -0
  208. synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/cpabs.h +33 -0
  209. synkit/Chem/Mapper/solvers/native/cpp/src/assignment/abb.cpp +302 -0
  210. synkit/Chem/Mapper/solvers/native/cpp/src/astar/CMakeLists.txt +12 -0
  211. synkit/Chem/Mapper/solvers/native/cpp/src/astar/README.md +47 -0
  212. synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar.cpp +228 -0
  213. synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar_graph.cpp +37 -0
  214. synkit/Chem/Mapper/solvers/native/cpp/src/astar/bound.cpp +212 -0
  215. synkit/Chem/Mapper/solvers/native/cpp/src/core/dispatch.cpp +272 -0
  216. synkit/Chem/Mapper/solvers/native/cpp/src/core/pmcd_witness.cpp +36 -0
  217. synkit/Chem/Mapper/solvers/native/cpp/src/hydrogen/hydrogen.cpp +380 -0
  218. synkit/Chem/Mapper/solvers/native/cpp/src/mces/README.md +57 -0
  219. synkit/Chem/Mapper/solvers/native/cpp/src/mces/mces.cpp +287 -0
  220. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/bridge.cpp +334 -0
  221. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/chemical_floor.inc +181 -0
  222. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/endpoint_symmetry.inc +104 -0
  223. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/exact_helpers.h +20 -0
  224. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_assignment_certificate.h +165 -0
  225. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_certificate.h +125 -0
  226. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_model.h +365 -0
  227. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_provider.inc +319 -0
  228. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_rounding.inc +170 -0
  229. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_test_hooks.h +342 -0
  230. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/objective_reduction.inc +137 -0
  231. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/overlap_dual.inc +351 -0
  232. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/profile_seed.inc +214 -0
  233. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/seed_heuristics.inc +640 -0
  234. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_embedding.cpp +430 -0
  235. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_root_lp.h +176 -0
  236. synkit/Chem/Mapper/solvers/native/cpp/src/pabs/test_hooks.inc +262 -0
  237. synkit/Chem/Mapper/solvers/native/cpp/src/reaction/reaction.cpp +611 -0
  238. synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/README.md +61 -0
  239. synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/sepdp.cpp +253 -0
  240. synkit/Chem/Mapper/solvers/native/cpp/src/slap.cpp +810 -0
  241. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/formulation.h +138 -0
  242. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/maxsat.cpp +526 -0
  243. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/ortools.cpp +475 -0
  244. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/seed.h +80 -0
  245. synkit/Chem/Mapper/solvers/native/cpp/src/solvers/weighted_cnf.h +256 -0
  246. synkit/Chem/Mapper/solvers/native/cpp/src/supporting/README.md +43 -0
  247. synkit/Chem/Mapper/solvers/native/cpp/src/supporting/dfs.cpp +123 -0
  248. synkit/Chem/Mapper/solvers/native/cpp/src/supporting/edit_support.cpp +224 -0
  249. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification.cpp +553 -0
  250. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_pipeline.cpp +124 -0
  251. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_stream.h +13 -0
  252. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/cpabs.cpp +455 -0
  253. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/edge_deck.cpp +743 -0
  254. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
  255. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
  256. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naugraph.c +732 -0
  257. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.c +94 -0
  258. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.h +49 -0
  259. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.c +1771 -0
  260. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.h +133 -0
  261. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nautil.c +750 -0
  262. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naututil.h +355 -0
  263. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.c +1256 -0
  264. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.h +1544 -0
  265. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.c +1131 -0
  266. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.h +70 -0
  267. synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/sorttemplates.c +580 -0
  268. synkit/Chem/Mapper/solvers/native/hydrogen.py +711 -0
  269. synkit/Chem/Mapper/solvers/native/implicit.py +506 -0
  270. synkit/Chem/Mapper/solvers/native/implicit_certificate.py +187 -0
  271. synkit/Chem/Mapper/solvers/native/implicit_preparation.py +309 -0
  272. synkit/Chem/Mapper/solvers/native/lib/synkit_mapper.dll +0 -0
  273. synkit/Chem/Mapper/solvers/native/mces.py +16 -0
  274. synkit/Chem/Mapper/solvers/native/reaction.py +179 -0
  275. synkit/Chem/Mapper/solvers/native/reaction_build.py +277 -0
  276. synkit/Chem/Mapper/solvers/native/reaction_text.py +26 -0
  277. synkit/Chem/Mapper/solvers/native/runtime.py +242 -0
  278. synkit/Chem/Mapper/solvers/native/sepdp.py +47 -0
  279. synkit/Chem/Mapper/solvers/native/slap.py +266 -0
  280. synkit/Chem/Mapper/solvers/native/solvers.py +67 -0
  281. synkit/Chem/Mapper/solvers/native/stereo.py +86 -0
  282. synkit/Chem/Mapper/solvers/native/supporting.py +34 -0
  283. synkit/Chem/Mapper/solvers/native/symmetry.py +91 -0
  284. synkit/Chem/Mapper/solvers/slap/__init__.py +29 -0
  285. synkit/Chem/Mapper/solvers/slap/lap.py +174 -0
  286. synkit/Chem/Mapper/solvers/slap/sequential.py +696 -0
  287. synkit/Chem/Mapper/validation/__init__.py +1 -0
  288. synkit/Chem/Mapper/validation/aam_validator.py +442 -0
  289. synkit/Chem/Mapper/validation/annotations.py +274 -0
  290. synkit/Chem/Mapper/validation/bonds.py +124 -0
  291. synkit/Chem/Mapper/validation/cohorts.py +72 -0
  292. synkit/Chem/Mapper/validation/identifiability.py +150 -0
  293. synkit/Chem/Mapper/validation/numerical_scope.py +36 -0
  294. synkit/Chem/Mapper/validation/orbits.py +137 -0
  295. synkit/Chem/Molecule/__init__.py +0 -0
  296. synkit/Chem/Molecule/_chirality_loci.py +165 -0
  297. synkit/Chem/Molecule/_chirality_types.py +231 -0
  298. synkit/Chem/Molecule/_helical_loci.py +167 -0
  299. synkit/Chem/Molecule/_stereo_axis_evidence.py +202 -0
  300. synkit/Chem/Molecule/_stereo_element_detection.py +193 -0
  301. synkit/Chem/Molecule/_stereo_orientation_constraints.py +511 -0
  302. synkit/Chem/Molecule/_tetrahedral_carriers.py +63 -0
  303. synkit/Chem/Molecule/atom_features.py +379 -0
  304. synkit/Chem/Molecule/chirality.py +1041 -0
  305. synkit/Chem/Molecule/cip_assignment.py +569 -0
  306. synkit/Chem/Molecule/cip_ranking.py +843 -0
  307. synkit/Chem/Molecule/coordinate_stereo.py +503 -0
  308. synkit/Chem/Molecule/derived_stereo_naming.py +165 -0
  309. synkit/Chem/Molecule/descriptors.py +452 -0
  310. synkit/Chem/Molecule/formula.py +249 -0
  311. synkit/Chem/Molecule/global_stereo.py +175 -0
  312. synkit/Chem/Molecule/graph_annotator.py +300 -0
  313. synkit/Chem/Molecule/standardize.py +817 -0
  314. synkit/Chem/Molecule/stereo_evidence.py +179 -0
  315. synkit/Chem/Molecule/stereo_perception.py +1032 -0
  316. synkit/Chem/Molecule/valence.py +123 -0
  317. synkit/Chem/Reaction/Mapper/__init__.py +44 -0
  318. synkit/Chem/Reaction/__init__.py +20 -0
  319. synkit/Chem/Reaction/aam_validator.py +5 -0
  320. synkit/Chem/Reaction/balance_check.py +138 -0
  321. synkit/Chem/Reaction/canon_rsmi.py +249 -0
  322. synkit/Chem/Reaction/cleaning.py +58 -0
  323. synkit/Chem/Reaction/deionize.py +199 -0
  324. synkit/Chem/Reaction/explicit_h_audit.py +232 -0
  325. synkit/Chem/Reaction/fix_aam.py +64 -0
  326. synkit/Chem/Reaction/neutralize.py +207 -0
  327. synkit/Chem/Reaction/radical_wildcard.py +224 -0
  328. synkit/Chem/Reaction/standardize.py +203 -0
  329. synkit/Chem/Reaction/tautomerize.py +205 -0
  330. synkit/Chem/__init__.py +1 -0
  331. synkit/Chem/utils.py +315 -0
  332. synkit/Data/__init__.py +0 -0
  333. synkit/Data/aldol.json.gz +1 -0
  334. synkit/Data/gen_partial_aam.py +147 -0
  335. synkit/Data/paracetamol.json.gz +1 -0
  336. synkit/Graph/Canon/__init__.py +16 -0
  337. synkit/Graph/Canon/_native/__init__.py +13 -0
  338. synkit/Graph/Canon/_native/build.py +93 -0
  339. synkit/Graph/Canon/_native/lib/synkit_canon.dll +0 -0
  340. synkit/Graph/Canon/_native/prepare.py +49 -0
  341. synkit/Graph/Canon/_native/runtime.py +140 -0
  342. synkit/Graph/Canon/_native/search.py +137 -0
  343. synkit/Graph/Canon/canon_algs.py +228 -0
  344. synkit/Graph/Canon/canon_graph.py +531 -0
  345. synkit/Graph/Canon/cpp/CMakeLists.txt +42 -0
  346. synkit/Graph/Canon/cpp/README.md +123 -0
  347. synkit/Graph/Canon/cpp/examples/canonical_graph.cpp +16 -0
  348. synkit/Graph/Canon/cpp/include/synkit_canon/canon.h +85 -0
  349. synkit/Graph/Canon/cpp/src/canonical.cpp +391 -0
  350. synkit/Graph/Canon/cpp/src/symmetry.h +83 -0
  351. synkit/Graph/Canon/cpp/tests/symmetry.cpp +93 -0
  352. synkit/Graph/Canon/exact.py +1020 -0
  353. synkit/Graph/Canon/exact_refinement.py +368 -0
  354. synkit/Graph/Canon/nauty.py +143 -0
  355. synkit/Graph/Context/__init__.py +0 -0
  356. synkit/Graph/Context/hier_context.py +244 -0
  357. synkit/Graph/Context/radius_expand.py +252 -0
  358. synkit/Graph/FG/__init__.py +19 -0
  359. synkit/Graph/FG/api.py +39 -0
  360. synkit/Graph/FG/audit.py +155 -0
  361. synkit/Graph/FG/catalog.py +919 -0
  362. synkit/Graph/FG/detector.py +288 -0
  363. synkit/Graph/FG/model.py +97 -0
  364. synkit/Graph/FG/recognizers.py +274 -0
  365. synkit/Graph/FG/ring_system.py +154 -0
  366. synkit/Graph/Feature/Descriptors/topology.py +857 -0
  367. synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
  368. synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +222 -0
  369. synkit/Graph/Feature/__init__.py +5 -0
  370. synkit/Graph/Feature/graph_descriptors.py +323 -0
  371. synkit/Graph/Feature/graph_fps.py +96 -0
  372. synkit/Graph/Feature/graph_signature.py +239 -0
  373. synkit/Graph/Feature/hash_fps.py +133 -0
  374. synkit/Graph/Feature/morgan_fps.py +88 -0
  375. synkit/Graph/Feature/path_fps.py +81 -0
  376. synkit/Graph/Feature/wl_hash.py +134 -0
  377. synkit/Graph/Fusion/__init__.py +85 -0
  378. synkit/Graph/Fusion/candidate.py +559 -0
  379. synkit/Graph/Fusion/constructor.py +877 -0
  380. synkit/Graph/Fusion/identity.py +184 -0
  381. synkit/Graph/Fusion/interface.py +700 -0
  382. synkit/Graph/Fusion/search.py +241 -0
  383. synkit/Graph/Hyrogen/__init__.py +6 -0
  384. synkit/Graph/Hyrogen/_misc.py +591 -0
  385. synkit/Graph/Hyrogen/hcomplete.py +995 -0
  386. synkit/Graph/Hyrogen/hcomplete_legacy.py +368 -0
  387. synkit/Graph/Hyrogen/hcompletion_algorithms.py +713 -0
  388. synkit/Graph/Hyrogen/hextend.py +570 -0
  389. synkit/Graph/Hyrogen/hextend_legacy.py +215 -0
  390. synkit/Graph/ITS/__init__.py +4 -0
  391. synkit/Graph/ITS/its_builder.py +120 -0
  392. synkit/Graph/ITS/its_construction.py +719 -0
  393. synkit/Graph/ITS/its_decompose.py +572 -0
  394. synkit/Graph/ITS/its_destruction.py +331 -0
  395. synkit/Graph/ITS/its_expand.py +1251 -0
  396. synkit/Graph/ITS/its_relabel.py +187 -0
  397. synkit/Graph/ITS/its_reverter.py +356 -0
  398. synkit/Graph/ITS/normalize_aam.py +145 -0
  399. synkit/Graph/ITS/partial_its.py +238 -0
  400. synkit/Graph/ITS/rc_extractor.py +656 -0
  401. synkit/Graph/ITS/stereo.py +74 -0
  402. synkit/Graph/MTG/__init__.py +107 -0
  403. synkit/Graph/MTG/gluing.py +451 -0
  404. synkit/Graph/MTG/history.py +928 -0
  405. synkit/Graph/MTG/inventory.py +118 -0
  406. synkit/Graph/MTG/local.py +447 -0
  407. synkit/Graph/MTG/mcs_matcher.py +246 -0
  408. synkit/Graph/MTG/mtg.py +611 -0
  409. synkit/Graph/MTG/mtg_explore.py +74 -0
  410. synkit/Graph/MTG/observation.py +209 -0
  411. synkit/Graph/MTG/process.py +577 -0
  412. synkit/Graph/MTG/queries.py +66 -0
  413. synkit/Graph/MTG/realization.py +95 -0
  414. synkit/Graph/MTG/series_parallel.py +107 -0
  415. synkit/Graph/MTG/utils.py +424 -0
  416. synkit/Graph/Matcher/__init__.py +10 -0
  417. synkit/Graph/Matcher/approx_mcs.py +1264 -0
  418. synkit/Graph/Matcher/auto_est.py +736 -0
  419. synkit/Graph/Matcher/automorphism.py +314 -0
  420. synkit/Graph/Matcher/batch_cluster.py +224 -0
  421. synkit/Graph/Matcher/dedup_matches.py +247 -0
  422. synkit/Graph/Matcher/graph_cluster.py +191 -0
  423. synkit/Graph/Matcher/graph_matcher.py +312 -0
  424. synkit/Graph/Matcher/graph_morphism.py +421 -0
  425. synkit/Graph/Matcher/mcs_matcher.py +804 -0
  426. synkit/Graph/Matcher/multi_turbo_iso.py +186 -0
  427. synkit/Graph/Matcher/orbit.py +284 -0
  428. synkit/Graph/Matcher/partial_matcher.py +694 -0
  429. synkit/Graph/Matcher/sing.py +544 -0
  430. synkit/Graph/Matcher/subgraph_matcher.py +812 -0
  431. synkit/Graph/Matcher/turbo_iso.py +272 -0
  432. synkit/Graph/Matcher/wl_sel.py +537 -0
  433. synkit/Graph/Mech/__init__.py +26 -0
  434. synkit/Graph/Mech/conversion.py +1466 -0
  435. synkit/Graph/Mech/conversion_debug.py +237 -0
  436. synkit/Graph/Mech/electron_accounting.py +200 -0
  437. synkit/Graph/Mech/lwg_editor.py +406 -0
  438. synkit/Graph/Mech/lwg_ops.py +313 -0
  439. synkit/Graph/Morphism/__init__.py +99 -0
  440. synkit/Graph/Morphism/constraints.py +460 -0
  441. synkit/Graph/Morphism/llg.py +999 -0
  442. synkit/Graph/Morphism/morphism.py +349 -0
  443. synkit/Graph/Morphism/stereo.py +258 -0
  444. synkit/Graph/Morphism/stereo_morphism.py +383 -0
  445. synkit/Graph/Morphism/stereo_morphism_helpers.py +957 -0
  446. synkit/Graph/Morphism/stereo_morphism_types.py +117 -0
  447. synkit/Graph/Stereo/__init__.py +376 -0
  448. synkit/Graph/Stereo/_descriptor_core.py +207 -0
  449. synkit/Graph/Stereo/_descriptor_serialization.py +138 -0
  450. synkit/Graph/Stereo/canonical.py +648 -0
  451. synkit/Graph/Stereo/changes.py +964 -0
  452. synkit/Graph/Stereo/composition.py +391 -0
  453. synkit/Graph/Stereo/configured.py +946 -0
  454. synkit/Graph/Stereo/couplings.py +245 -0
  455. synkit/Graph/Stereo/descriptors.py +1030 -0
  456. synkit/Graph/Stereo/enumeration.py +597 -0
  457. synkit/Graph/Stereo/extended_descriptors.py +406 -0
  458. synkit/Graph/Stereo/global_configured.py +152 -0
  459. synkit/Graph/Stereo/global_stereo.py +304 -0
  460. synkit/Graph/Stereo/identity.py +648 -0
  461. synkit/Graph/Stereo/interchange.py +496 -0
  462. synkit/Graph/Stereo/legacy.py +460 -0
  463. synkit/Graph/Stereo/matching.py +899 -0
  464. synkit/Graph/Stereo/native.py +154 -0
  465. synkit/Graph/Stereo/orbits.py +668 -0
  466. synkit/Graph/Stereo/outcomes.py +99 -0
  467. synkit/Graph/Stereo/pairwise.py +350 -0
  468. synkit/Graph/Stereo/rdkit_adapter.py +662 -0
  469. synkit/Graph/Stereo/rdkit_identity.py +118 -0
  470. synkit/Graph/Stereo/rdkit_resonance.py +140 -0
  471. synkit/Graph/Stereo/reaction_interchange.py +266 -0
  472. synkit/Graph/Stereo/semantics.py +296 -0
  473. synkit/Graph/Stereo/supports.py +257 -0
  474. synkit/Graph/Stereo/wire.py +339 -0
  475. synkit/Graph/Wildcard/__init__.py +0 -0
  476. synkit/Graph/Wildcard/fuse_graph.py +156 -0
  477. synkit/Graph/Wildcard/graph_wc.py +303 -0
  478. synkit/Graph/Wildcard/its_merge.py +857 -0
  479. synkit/Graph/Wildcard/radwc.py +114 -0
  480. synkit/Graph/Wildcard/wc_matcher.py +576 -0
  481. synkit/Graph/Wildcard/wildcard.py +225 -0
  482. synkit/Graph/__init__.py +17 -0
  483. synkit/Graph/canon_graph.py +14 -0
  484. synkit/Graph/syn_graph.py +199 -0
  485. synkit/Graph/utils.py +324 -0
  486. synkit/IO/__init__.py +4 -0
  487. synkit/IO/chem_converter.py +1059 -0
  488. synkit/IO/combinatorial/__init__.py +8 -0
  489. synkit/IO/combinatorial/gml_to_graph.py +223 -0
  490. synkit/IO/combinatorial/graph_to_gml.py +280 -0
  491. synkit/IO/combinatorial/graph_to_smarts.py +186 -0
  492. synkit/IO/combinatorial/smarts_expander.py +147 -0
  493. synkit/IO/combinatorial/smarts_generalizer.py +133 -0
  494. synkit/IO/combinatorial/smarts_to_graph.py +179 -0
  495. synkit/IO/conversion.py +42 -0
  496. synkit/IO/data_io.py +269 -0
  497. synkit/IO/data_process.py +143 -0
  498. synkit/IO/debug.py +73 -0
  499. synkit/IO/gml_to_nx.py +150 -0
  500. synkit/IO/graph_to_mol.py +199 -0
  501. synkit/IO/mol_to_graph.py +1105 -0
  502. synkit/IO/mol_to_graph_chemistry.py +545 -0
  503. synkit/IO/nx_to_gml.py +209 -0
  504. synkit/IO/smiles_to_id.py +123 -0
  505. synkit/Mechanism/__init__.py +151 -0
  506. synkit/Mechanism/adapters.py +136 -0
  507. synkit/Mechanism/audit.py +323 -0
  508. synkit/Mechanism/benchmark.py +638 -0
  509. synkit/Mechanism/compatibility.py +13 -0
  510. synkit/Mechanism/drawing.py +85 -0
  511. synkit/Mechanism/electron_models.py +725 -0
  512. synkit/Mechanism/equivalence.py +521 -0
  513. synkit/Mechanism/evidence.py +262 -0
  514. synkit/Mechanism/interchange.py +141 -0
  515. synkit/Mechanism/model.py +918 -0
  516. synkit/Mechanism/neighbor_stereo.py +312 -0
  517. synkit/Mechanism/radical_data.py +740 -0
  518. synkit/Mechanism/replay.py +986 -0
  519. synkit/Mechanism/schema.py +264 -0
  520. synkit/Mechanism/stereo_state.py +416 -0
  521. synkit/Mechanism/symbols.py +91 -0
  522. synkit/Rule/Apply/__init__.py +35 -0
  523. synkit/Rule/Apply/conditions.py +45 -0
  524. synkit/Rule/Apply/dpo.py +998 -0
  525. synkit/Rule/Apply/rule_matcher.py +218 -0
  526. synkit/Rule/Apply/validation.py +354 -0
  527. synkit/Rule/Compose/__init__.py +106 -0
  528. synkit/Rule/Compose/_identity.py +43 -0
  529. synkit/Rule/Compose/composition.py +976 -0
  530. synkit/Rule/Compose/laws.py +439 -0
  531. synkit/Rule/Compose/search.py +771 -0
  532. synkit/Rule/Compose/synrule.py +170 -0
  533. synkit/Rule/Modify/__init__.py +0 -0
  534. synkit/Rule/Modify/implict_rule.py +56 -0
  535. synkit/Rule/Modify/longest_path.py +92 -0
  536. synkit/Rule/Modify/molecule_rule.py +115 -0
  537. synkit/Rule/Modify/prune_templates.py +78 -0
  538. synkit/Rule/Modify/rule_utils.py +194 -0
  539. synkit/Rule/Modify/strip_rule.py +97 -0
  540. synkit/Rule/__init__.py +28 -0
  541. synkit/Rule/generic_stereo.py +884 -0
  542. synkit/Rule/stereo_identity.py +271 -0
  543. synkit/Rule/syn_rule.py +1090 -0
  544. synkit/Synthesis/MSR/__init__.py +0 -0
  545. synkit/Synthesis/MSR/path_finder.py +239 -0
  546. synkit/Synthesis/Metrics/__init__.py +0 -0
  547. synkit/Synthesis/Metrics/_base.py +52 -0
  548. synkit/Synthesis/Metrics/_plot.py +129 -0
  549. synkit/Synthesis/Metrics/_ranking.py +179 -0
  550. synkit/Synthesis/RBL/__init__.py +80 -0
  551. synkit/Synthesis/RBL/engine.py +1249 -0
  552. synkit/Synthesis/RBL/matching.py +652 -0
  553. synkit/Synthesis/RBL/overlap.py +336 -0
  554. synkit/Synthesis/RBL/pipeline.py +376 -0
  555. synkit/Synthesis/RBL/policy.py +139 -0
  556. synkit/Synthesis/RBL/proof.py +370 -0
  557. synkit/Synthesis/RBL/reaction.py +506 -0
  558. synkit/Synthesis/RBL/state.py +169 -0
  559. synkit/Synthesis/RBL/validation.py +1060 -0
  560. synkit/Synthesis/Reactor/__init__.py +60 -0
  561. synkit/Synthesis/Reactor/core/__init__.py +1 -0
  562. synkit/Synthesis/Reactor/core/engine.py +967 -0
  563. synkit/Synthesis/Reactor/core/product.py +621 -0
  564. synkit/Synthesis/Reactor/core/rewrite.py +561 -0
  565. synkit/Synthesis/Reactor/core/strategy.py +44 -0
  566. synkit/Synthesis/Reactor/matching/__init__.py +1 -0
  567. synkit/Synthesis/Reactor/matching/mixin.py +920 -0
  568. synkit/Synthesis/Reactor/matching/policy.py +345 -0
  569. synkit/Synthesis/Reactor/matching/symmetry.py +309 -0
  570. synkit/Synthesis/Reactor/output/__init__.py +1 -0
  571. synkit/Synthesis/Reactor/output/deduplication.py +703 -0
  572. synkit/Synthesis/Reactor/output/policy.py +16 -0
  573. synkit/Synthesis/Reactor/output/serialization.py +485 -0
  574. synkit/Synthesis/Reactor/output/structural.py +893 -0
  575. synkit/Synthesis/Reactor/stereo/__init__.py +1 -0
  576. synkit/Synthesis/Reactor/stereo/assignment.py +35 -0
  577. synkit/Synthesis/Reactor/stereo/mixin.py +780 -0
  578. synkit/Synthesis/Reactor/variants/__init__.py +1 -0
  579. synkit/Synthesis/Reactor/variants/imbalanced.py +180 -0
  580. synkit/Synthesis/Reactor/variants/partial.py +84 -0
  581. synkit/Synthesis/Reactor/workflow/__init__.py +1 -0
  582. synkit/Synthesis/Reactor/workflow/batch.py +550 -0
  583. synkit/Synthesis/Reactor/workflow/benchmark.py +143 -0
  584. synkit/Synthesis/Reactor/workflow/postprocess.py +267 -0
  585. synkit/Synthesis/Reactor/workflow/rule_filter.py +210 -0
  586. synkit/Synthesis/__init__.py +0 -0
  587. synkit/Synthesis/reactor_utils.py +316 -0
  588. synkit/Utils/__init__.py +0 -0
  589. synkit/Utils/utils.py +172 -0
  590. synkit/Vis/__init__.py +91 -0
  591. synkit/Vis/crn/__init__.py +40 -0
  592. synkit/Vis/epd/__init__.py +20 -0
  593. synkit/Vis/epd/arrows.py +425 -0
  594. synkit/Vis/epd/chem.py +197 -0
  595. synkit/Vis/epd/constants.py +83 -0
  596. synkit/Vis/epd/layout.py +283 -0
  597. synkit/Vis/epd/mapping.py +51 -0
  598. synkit/Vis/epd/models.py +136 -0
  599. synkit/Vis/epd/render.py +543 -0
  600. synkit/Vis/epd/utils.py +108 -0
  601. synkit/Vis/epd/visualizer.py +811 -0
  602. synkit/Vis/graph_visualizer.py +375 -0
  603. synkit/Vis/its/__init__.py +19 -0
  604. synkit/Vis/its/drawer.py +615 -0
  605. synkit/Vis/its_drawer.py +35 -0
  606. synkit/Vis/molecule/__init__.py +31 -0
  607. synkit/Vis/molecule/drawer.py +563 -0
  608. synkit/Vis/molecule/reaction.py +285 -0
  609. synkit/Vis/molecule_drawer.py +33 -0
  610. synkit/Vis/mtg/__init__.py +17 -0
  611. synkit/Vis/mtg/drawer.py +769 -0
  612. synkit/Vis/mtg_drawer.py +34 -0
  613. synkit/Vis/pdf_writer.py +146 -0
  614. synkit/Vis/reaction/__init__.py +19 -0
  615. synkit/Vis/reaction/rule.py +141 -0
  616. synkit/Vis/reaction/rxn.py +532 -0
  617. synkit/Vis/reaction_drawer.py +41 -0
  618. synkit/Vis/space/__init__.py +17 -0
  619. synkit/Vis/space/chemical.py +80 -0
  620. synkit/Vis/space/embedding.py +87 -0
  621. synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
  622. synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
  623. synkit/Vis/vis_synedu/rxn_vis.py +377 -0
  624. synkit/Vis/vis_synedu/vis.py +501 -0
  625. synkit/Vis/visual_drawer.py +215 -0
  626. synkit/Vis/visual_model.py +520 -0
  627. synkit/__init__.py +3 -0
  628. synkit/_native.py +91 -0
  629. synkit/examples.py +50 -0
  630. synkit/version.py +23 -0
  631. synkit-1.6.5.dist-info/METADATA +156 -0
  632. synkit-1.6.5.dist-info/RECORD +636 -0
  633. synkit-1.6.5.dist-info/WHEEL +4 -0
  634. synkit-1.6.5.dist-info/licenses/LICENSE +21 -0
  635. synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
  636. synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
@@ -0,0 +1,1105 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ import random
5
+
6
+ import networkx as nx
7
+ from rdkit import Chem
8
+ from rdkit.Chem import AllChem
9
+
10
+ from synkit.IO.debug import setup_logging
11
+ from synkit.Chem.Molecule.descriptors import (
12
+ compute_gasteiger_inplace,
13
+ PerMolDescriptors,
14
+ )
15
+ from synkit.Chem.Molecule.atom_features import AtomFeatureExtractor
16
+ from synkit.Chem.Molecule.graph_annotator import GraphAnnotator
17
+ from synkit.IO.mol_to_graph_chemistry import MolToGraphChemistryMixin
18
+
19
+ logger = setup_logging()
20
+
21
+
22
+ class MolToGraph(MolToGraphChemistryMixin):
23
+ """Convert an RDKit molecule into a NetworkX molecular graph.
24
+
25
+ The converter preserves the public API while adding corrected lone-pair
26
+ bookkeeping for aromatic heteroatoms, especially pyrrolic / ``[nH]``-like
27
+ aromatic nitrogen. RDKit aromatic bonds have order ``1.5``; for aromatic
28
+ lone-pair donor heteroatoms, this class counts aromatic bonds as sigma bonds
29
+ during lone-pair estimation.
30
+
31
+ Important node fields are ``estimated_lone_pairs``, ``lone_pairs``
32
+ backward-compatible alias, ``available_lone_pairs``, ``available_lp``,
33
+ ``bond_order_sum``, ``lp_bond_order_sum``, ``valence_electrons``, and
34
+ ``oxidation_state``.
35
+
36
+ :param node_attrs: Optional whitelist of node attributes to keep.
37
+ :type node_attrs: Optional[List[str]]
38
+ :param edge_attrs: Optional whitelist of edge attributes to keep.
39
+ :type edge_attrs: Optional[List[str]]
40
+ :param attr_profile: Atom feature profile, either ``"minimal"`` or
41
+ ``"full"``.
42
+ :type attr_profile: str
43
+ :param with_topology: If ``True``, run :class:`GraphAnnotator` on the graph.
44
+ :type with_topology: bool
45
+ :param include_stereo_descriptors: If ``False``, omit the graph-level
46
+ relative-stereochemistry registry. Defaults to ``True``.
47
+ :type include_stereo_descriptors: bool
48
+ :raises ValueError: If ``attr_profile`` is unsupported.
49
+
50
+ .. code-block:: python
51
+
52
+ from rdkit import Chem
53
+ from synkit.IO.mol_to_graph import MolToGraph
54
+
55
+ mol = Chem.MolFromSmiles("c1cc[nH]c1")
56
+ graph = MolToGraph(attr_profile="minimal").transform(mol)
57
+
58
+ for node, data in graph.nodes(data=True):
59
+ print(node, data["element"], data["lone_pairs"], data["available_lp"])
60
+
61
+ .. code-block:: python
62
+
63
+ mol = Chem.MolFromSmiles("[CH3:1][CH2:2][Br:3]")
64
+ graph = MolToGraph(
65
+ node_attrs=["element", "atom_map", "charge", "lone_pairs"],
66
+ edge_attrs=["order", "kekule_order"],
67
+ ).transform(mol, use_index_as_atom_map=True)
68
+ """
69
+
70
+ SUPPORTED_PROFILES = ("minimal", "full")
71
+ AUGMENTED_NODE_ATTRS = frozenset(
72
+ {
73
+ "oxidation_state",
74
+ "available_lp",
75
+ "lone_pairs",
76
+ "valence_electrons",
77
+ "stereo_descriptor",
78
+ "cip_label",
79
+ "chiral_tag",
80
+ "bond_order_sum",
81
+ "lp_bond_order_sum",
82
+ "estimated_lone_pairs",
83
+ "available_lone_pairs",
84
+ }
85
+ )
86
+ DIRECT_NODE_ATTRS = frozenset(
87
+ {
88
+ "element",
89
+ "isotope",
90
+ "aromatic",
91
+ "hcount",
92
+ "charge",
93
+ "radical",
94
+ "neighbors",
95
+ "atom_map",
96
+ }
97
+ )
98
+ KEKULE_EDGE_ATTRS = frozenset(
99
+ {"kekule_order", "sigma_order", "pi_order", "kekule_bond_type"}
100
+ )
101
+ DIRECT_EDGE_ATTRS = frozenset({"order", "aromatic"})
102
+
103
+ # Pauling electronegativities used for oxidation-state bookkeeping.
104
+ # Missing elements are skipped instead of guessed.
105
+ PAULING_EN: Dict[str, float] = {
106
+ "H": 2.20,
107
+ "B": 2.04,
108
+ "C": 2.55,
109
+ "N": 3.04,
110
+ "O": 3.44,
111
+ "F": 3.98,
112
+ "P": 2.19,
113
+ "S": 2.58,
114
+ "Cl": 3.16,
115
+ "Br": 2.96,
116
+ "I": 2.66,
117
+ "Se": 2.55,
118
+ }
119
+
120
+ def __init__(
121
+ self,
122
+ node_attrs: Optional[List[str]] = None,
123
+ edge_attrs: Optional[List[str]] = None,
124
+ *,
125
+ attr_profile: str = "minimal",
126
+ with_topology: bool = False,
127
+ include_stereo_descriptors: bool = True,
128
+ ) -> None:
129
+ """Initialize the converter.
130
+
131
+ :param node_attrs: Optional node-attribute whitelist.
132
+ :type node_attrs: Optional[List[str]]
133
+ :param edge_attrs: Optional edge-attribute whitelist.
134
+ :type edge_attrs: Optional[List[str]]
135
+ :param attr_profile: Feature profile, ``"minimal"`` or ``"full"``.
136
+ :type attr_profile: str
137
+ :param with_topology: Whether to add topology annotations.
138
+ :type with_topology: bool
139
+ :param include_stereo_descriptors: Whether to construct the graph-level
140
+ relative-stereochemistry registry.
141
+ :type include_stereo_descriptors: bool
142
+ :raises ValueError: If ``attr_profile`` is unsupported.
143
+ """
144
+ if attr_profile not in self.SUPPORTED_PROFILES:
145
+ raise ValueError(
146
+ f"Unsupported attr_profile: {attr_profile!r}. "
147
+ f"Supported: {self.SUPPORTED_PROFILES}"
148
+ )
149
+
150
+ self.node_attrs: Optional[List[str]] = (
151
+ None if node_attrs is None else list(node_attrs)
152
+ )
153
+ self.edge_attrs: Optional[List[str]] = (
154
+ None if edge_attrs is None else list(edge_attrs)
155
+ )
156
+ self.attr_profile: str = attr_profile
157
+ self.with_topology: bool = bool(with_topology)
158
+ self.include_stereo_descriptors: bool = bool(include_stereo_descriptors)
159
+
160
+ self._graph: Optional[nx.Graph] = None
161
+ self._last_mol: Optional[Chem.Mol] = None
162
+
163
+ # ------------------------------------------------------------------
164
+ # Public conversion API
165
+ # ------------------------------------------------------------------
166
+
167
+ def transform(
168
+ self,
169
+ mol: Chem.Mol,
170
+ drop_non_aam: bool = False,
171
+ use_index_as_atom_map: bool = False,
172
+ ) -> nx.Graph:
173
+ """Build a NetworkX graph from an RDKit molecule.
174
+
175
+ :param mol: RDKit molecule.
176
+ :type mol: Chem.Mol
177
+ :param drop_non_aam: If ``True``, exclude atoms with atom-map ``0``.
178
+ :type drop_non_aam: bool
179
+ :param use_index_as_atom_map: If ``True``, use non-zero atom-map numbers
180
+ as node identifiers; otherwise use ``atom index + 1``.
181
+ :type use_index_as_atom_map: bool
182
+ :return: Molecular graph with atom and bond attributes.
183
+ :rtype: nx.Graph
184
+ :raises ValueError: If ``drop_non_aam=True`` but
185
+ ``use_index_as_atom_map=False``.
186
+
187
+ .. code-block:: python
188
+
189
+ mol = Chem.MolFromSmiles("[CH3:1][CH2:2][Br:3]")
190
+ graph = MolToGraph().transform(
191
+ mol,
192
+ drop_non_aam=True,
193
+ use_index_as_atom_map=True,
194
+ )
195
+ """
196
+ if drop_non_aam and not use_index_as_atom_map:
197
+ raise ValueError(
198
+ "drop_non_aam and use_index_as_atom_map must both be True "
199
+ "to drop unmapped atoms."
200
+ )
201
+
202
+ self._last_mol = mol
203
+
204
+ needs_partial_charge = (
205
+ self.node_attrs is None or "partial_charge" in self.node_attrs
206
+ )
207
+ if needs_partial_charge:
208
+ try:
209
+ compute_gasteiger_inplace(mol)
210
+ except Exception:
211
+ logger.debug("Gasteiger computation failed (best-effort). Continuing.")
212
+
213
+ needs_oxidation_state = (
214
+ self.node_attrs is None or "oxidation_state" in self.node_attrs
215
+ )
216
+ needs_kekule = (
217
+ self.edge_attrs is None
218
+ or bool(self.KEKULE_EDGE_ATTRS.intersection(self.edge_attrs))
219
+ or needs_oxidation_state
220
+ )
221
+ kek_mol: Optional[Chem.Mol] = (
222
+ self._make_kekule_copy(mol) if needs_kekule else None
223
+ )
224
+ oxidation_states = (
225
+ self.estimate_oxidation_states(mol, kek_mol=kek_mol)
226
+ if needs_oxidation_state
227
+ else {}
228
+ )
229
+
230
+ per: Optional[PerMolDescriptors] = None
231
+ if self.attr_profile == "full":
232
+ try:
233
+ per = PerMolDescriptors.compute(mol)
234
+ except Exception as exc:
235
+ logger.debug("PerMolDescriptors.compute failed: %s", exc)
236
+ per = None
237
+
238
+ extractor = AtomFeatureExtractor(mol, per=per, profile=self.attr_profile)
239
+ needs_augmentation = self.node_attrs is None or bool(
240
+ self.AUGMENTED_NODE_ATTRS.intersection(self.node_attrs)
241
+ )
242
+ requested_node_attrs = frozenset(self.node_attrs or ())
243
+ use_direct_node_attrs = self.node_attrs is not None and (
244
+ requested_node_attrs.issubset(self.DIRECT_NODE_ATTRS)
245
+ )
246
+ requested_edge_attrs = frozenset(self.edge_attrs or ())
247
+ use_direct_edge_attrs = self.edge_attrs is not None and (
248
+ requested_edge_attrs.issubset(self.DIRECT_EDGE_ATTRS)
249
+ )
250
+
251
+ graph = nx.Graph()
252
+ index_to_id: Dict[int, int] = {}
253
+
254
+ for atom in mol.GetAtoms():
255
+ atom_map = self._safe_atom_map(atom)
256
+ atom_id = (
257
+ atom_map
258
+ if (use_index_as_atom_map and atom_map != 0)
259
+ else atom.GetIdx() + 1
260
+ )
261
+
262
+ if drop_non_aam and atom_map == 0:
263
+ continue
264
+
265
+ if use_direct_node_attrs:
266
+ props = self._gather_direct_atom_properties(atom, requested_node_attrs)
267
+ else:
268
+ try:
269
+ props = extractor.build_dict(atom)
270
+ if needs_augmentation:
271
+ props = self._augment_atom_properties(
272
+ atom,
273
+ props,
274
+ oxidation_state=oxidation_states.get(atom.GetIdx()),
275
+ profile=self.attr_profile,
276
+ )
277
+ except Exception as exc:
278
+ logger.debug(
279
+ "AtomFeatureExtractor failed for atom %s: %s",
280
+ atom.GetIdx(),
281
+ exc,
282
+ )
283
+ props = self._gather_atom_properties(
284
+ atom,
285
+ oxidation_state=oxidation_states.get(atom.GetIdx()),
286
+ profile=self.attr_profile,
287
+ )
288
+
289
+ if self.node_attrs is not None:
290
+ props = {k: v for k, v in props.items() if k in self.node_attrs}
291
+
292
+ graph.add_node(atom_id, **props)
293
+ index_to_id[atom.GetIdx()] = atom_id
294
+
295
+ for bond in mol.GetBonds():
296
+ begin_idx = bond.GetBeginAtomIdx()
297
+ end_idx = bond.GetEndAtomIdx()
298
+
299
+ begin = index_to_id.get(begin_idx)
300
+ end = index_to_id.get(end_idx)
301
+ if begin is None or end is None:
302
+ continue
303
+
304
+ kek_bond: Optional[Chem.Bond] = None
305
+ if kek_mol is not None:
306
+ try:
307
+ kek_bond = kek_mol.GetBondWithIdx(bond.GetIdx())
308
+ except Exception:
309
+ kek_bond = None
310
+
311
+ if use_direct_edge_attrs:
312
+ bprops = self._gather_direct_bond_properties(bond, requested_edge_attrs)
313
+ else:
314
+ try:
315
+ bprops = self._gather_bond_properties(bond, kek_bond=kek_bond)
316
+ except Exception as exc:
317
+ logger.debug(
318
+ "Bond property collection failed for bond %s: %s",
319
+ bond.GetIdx(),
320
+ exc,
321
+ )
322
+ original_order = (
323
+ bond.GetBondTypeAsDouble()
324
+ if hasattr(bond, "GetBondTypeAsDouble")
325
+ else 1.0
326
+ )
327
+ bprops = {
328
+ "order": original_order,
329
+ "bond_type": (
330
+ str(bond.GetBondType())
331
+ if hasattr(bond, "GetBondType")
332
+ else "UNKNOWN"
333
+ ),
334
+ "kekule_order": (
335
+ kek_bond.GetBondTypeAsDouble()
336
+ if kek_bond is not None
337
+ and hasattr(kek_bond, "GetBondTypeAsDouble")
338
+ else original_order
339
+ ),
340
+ "kekule_bond_type": (
341
+ str(kek_bond.GetBondType())
342
+ if kek_bond is not None and hasattr(kek_bond, "GetBondType")
343
+ else (
344
+ str(bond.GetBondType())
345
+ if hasattr(bond, "GetBondType")
346
+ else "UNKNOWN"
347
+ )
348
+ ),
349
+ "aromatic": (
350
+ bond.GetIsAromatic()
351
+ if hasattr(bond, "GetIsAromatic")
352
+ else False
353
+ ),
354
+ }
355
+
356
+ if self.edge_attrs is not None:
357
+ bprops = {k: v for k, v in bprops.items() if k in self.edge_attrs}
358
+
359
+ graph.add_edge(begin, end, **bprops)
360
+
361
+ # Relative stereo is registry-based because descriptors depend on
362
+ # multiple atoms. For ordinary unmapped reactor substrates, graph node
363
+ # IDs (RDKit index + 1) provide stable internal references; external
364
+ # atom maps remain reserved for mapped rules and serialized reactions.
365
+ descriptors = {}
366
+ if self.include_stereo_descriptors:
367
+ try:
368
+ from synkit.Graph.Stereo import descriptors_from_rdkit
369
+
370
+ atom_maps = [int(atom.GetAtomMapNum()) for atom in mol.GetAtoms()]
371
+ mapped = all(value > 0 for value in atom_maps) and len(
372
+ set(atom_maps)
373
+ ) == len(atom_maps)
374
+ descriptors = descriptors_from_rdkit(mol, require_atom_maps=mapped)
375
+ except ImportError:
376
+ descriptors = {}
377
+ graph.graph["stereo_descriptors"] = descriptors
378
+ node_by_map = {
379
+ int(attrs.get("atom_map", 0)): node
380
+ for node, attrs in graph.nodes(data=True)
381
+ if int(attrs.get("atom_map", 0) or 0) > 0
382
+ }
383
+ for descriptor_id, descriptor in descriptors.items():
384
+ if descriptor_id.startswith("atom:"):
385
+ node = node_by_map.get(int(descriptor.center))
386
+ if node is not None:
387
+ graph.nodes[node].setdefault("stereo_descriptor_ids", []).append(
388
+ descriptor_id
389
+ )
390
+ else:
391
+ left, right = (int(value) for value in descriptor.atoms[2:4])
392
+ if (
393
+ left in node_by_map
394
+ and right in node_by_map
395
+ and graph.has_edge(node_by_map[left], node_by_map[right])
396
+ ):
397
+ graph.edges[node_by_map[left], node_by_map[right]].setdefault(
398
+ "stereo_descriptor_ids", []
399
+ ).append(descriptor_id)
400
+
401
+ if self.with_topology:
402
+ try:
403
+ GraphAnnotator(graph, in_place=True).annotate()
404
+ except Exception as exc:
405
+ logger.debug("GraphAnnotator failed: %s", exc)
406
+
407
+ return graph
408
+
409
+ @staticmethod
410
+ def _gather_direct_atom_properties(
411
+ atom: Chem.Atom,
412
+ attributes: frozenset[str],
413
+ ) -> Dict[str, Any]:
414
+ """Read a requested subset of inexpensive RDKit atom properties."""
415
+ props: Dict[str, Any] = {}
416
+ if "element" in attributes:
417
+ props["element"] = atom.GetSymbol()
418
+ if "isotope" in attributes:
419
+ props["isotope"] = atom.GetIsotope()
420
+ if "aromatic" in attributes:
421
+ props["aromatic"] = atom.GetIsAromatic()
422
+ if "hcount" in attributes:
423
+ props["hcount"] = atom.GetTotalNumHs()
424
+ if "charge" in attributes:
425
+ props["charge"] = atom.GetFormalCharge()
426
+ if "radical" in attributes:
427
+ props["radical"] = atom.GetNumRadicalElectrons()
428
+ if "neighbors" in attributes:
429
+ props["neighbors"] = sorted(
430
+ neighbor.GetSymbol() for neighbor in atom.GetNeighbors()
431
+ )
432
+ if "atom_map" in attributes:
433
+ props["atom_map"] = atom.GetAtomMapNum()
434
+ return props
435
+
436
+ @staticmethod
437
+ def _gather_direct_bond_properties(
438
+ bond: Chem.Bond,
439
+ attributes: frozenset[str],
440
+ ) -> Dict[str, Any]:
441
+ """Read requested bond attributes without building discarded fields."""
442
+ props: Dict[str, Any] = {}
443
+ if "order" in attributes:
444
+ props["order"] = bond.GetBondTypeAsDouble()
445
+ if "aromatic" in attributes:
446
+ props["aromatic"] = bond.GetIsAromatic()
447
+ return props
448
+
449
+ def transform_store(
450
+ self,
451
+ mol: Chem.Mol,
452
+ drop_non_aam: bool = False,
453
+ use_index_as_atom_map: bool = False,
454
+ ) -> "MolToGraph":
455
+ """Build, store, and return ``self``.
456
+
457
+ :param mol: RDKit molecule.
458
+ :type mol: Chem.Mol
459
+ :param drop_non_aam: If ``True``, exclude atoms with atom-map ``0``.
460
+ :type drop_non_aam: bool
461
+ :param use_index_as_atom_map: If ``True``, use atom maps as node IDs.
462
+ :type use_index_as_atom_map: bool
463
+ :return: Current converter instance.
464
+ :rtype: MolToGraph
465
+ """
466
+ self._graph = self.transform(
467
+ mol,
468
+ drop_non_aam=drop_non_aam,
469
+ use_index_as_atom_map=use_index_as_atom_map,
470
+ )
471
+ return self
472
+
473
+ @property
474
+ def graph(self) -> nx.Graph:
475
+ """Return the graph produced by :meth:`transform_store`.
476
+
477
+ :return: Stored molecular graph.
478
+ :rtype: nx.Graph
479
+ :raises RuntimeError: If no graph has been stored yet.
480
+ """
481
+ if self._graph is None:
482
+ raise RuntimeError(
483
+ "No graph produced yet. Call `transform_store(mol)` first."
484
+ )
485
+ return self._graph
486
+
487
+ def __repr__(self) -> str:
488
+ """Return a compact representation.
489
+
490
+ :return: Developer-facing representation string.
491
+ :rtype: str
492
+ """
493
+ try:
494
+ n = self._graph.number_of_nodes() if self._graph is not None else 0
495
+ except Exception:
496
+ n = -1
497
+
498
+ return (
499
+ f"{self.__class__.__name__}(profile={self.attr_profile!r}, "
500
+ f"with_topology={self.with_topology}, "
501
+ f"include_stereo_descriptors={self.include_stereo_descriptors}, "
502
+ f"node_attrs={self.node_attrs!r}, "
503
+ f"edge_attrs={self.edge_attrs!r}, last_nodes={n})"
504
+ )
505
+
506
+ @classmethod
507
+ def help(cls) -> str:
508
+ """Return a short usage string.
509
+
510
+ :return: Usage summary.
511
+ :rtype: str
512
+ """
513
+ return (
514
+ "MolToGraph.help() -> str\n\n"
515
+ "Create with MolToGraph(node_attrs=[...], edge_attrs=[...], "
516
+ "attr_profile='minimal'|'full', with_topology=False).\n"
517
+ "Use `.transform(mol)` to get an nx.Graph, or `.transform_store(mol)` "
518
+ "to build and store the graph on the instance."
519
+ )
520
+
521
+ @classmethod
522
+ def _augment_atom_properties(
523
+ cls,
524
+ atom: Chem.Atom,
525
+ props: Dict[str, Any],
526
+ oxidation_state: Optional[float] = None,
527
+ *,
528
+ profile: str = "full",
529
+ ) -> Dict[str, Any]:
530
+ """Add electron-bookkeeping fields to existing atom attributes.
531
+
532
+ For both profiles sets ``oxidation_state``, ``radical``,
533
+ ``available_lp``, and the backward-compatible ``lone_pairs`` alias.
534
+ The ``"full"`` profile additionally sets ``bond_order_sum``,
535
+ ``lp_bond_order_sum``, ``valence_electrons``,
536
+ ``estimated_lone_pairs``, and ``available_lone_pairs``.
537
+
538
+ :param atom: RDKit atom.
539
+ :type atom: Chem.Atom
540
+ :param props: Existing atom attributes from
541
+ :class:`~synkit.Chem.Molecule.atom_features.AtomFeatureExtractor`.
542
+ :type props: Dict[str, Any]
543
+ :param oxidation_state: Pre-computed oxidation state, or ``None``.
544
+ :type oxidation_state: Optional[float]
545
+ :param profile: Feature profile — ``"minimal"`` or ``"full"``.
546
+ :type profile: str
547
+ :return: Augmented atom attributes dict.
548
+ :rtype: Dict[str, Any]
549
+ """
550
+ new_props = dict(props)
551
+
552
+ estimated_lone_pairs = cls.estimate_lone_pairs(atom)
553
+ available_lone_pairs = cls.estimate_available_lone_pairs(atom)
554
+
555
+ new_props["oxidation_state"] = (
556
+ None if oxidation_state is None else round(float(oxidation_state), 3)
557
+ )
558
+ new_props["radical"] = int(atom.GetNumRadicalElectrons())
559
+ new_props["available_lp"] = available_lone_pairs > 0
560
+ # Backward-compatible field used by SynEltra.
561
+ new_props["lone_pairs"] = estimated_lone_pairs
562
+ new_props["valence_electrons"] = cls._safe_valence_electrons(atom)
563
+ # v2 keeps relative stereo identity separate from derived display
564
+ # labels. Sprint 5 fills ``stereo_descriptor`` with an ordered,
565
+ # map-based descriptor; keeping the field now avoids schema churn.
566
+ new_props["stereo_descriptor"] = None
567
+ new_props["cip_label"] = (
568
+ atom.GetProp("_CIPCode") if atom.HasProp("_CIPCode") else None
569
+ )
570
+ new_props["chiral_tag"] = cls.get_chiral_tag(atom)
571
+
572
+ if profile == "full":
573
+ new_props["bond_order_sum"] = round(cls._safe_bond_order_sum(atom), 3)
574
+ new_props["lp_bond_order_sum"] = round(
575
+ cls._bond_order_sum_for_lone_pairs(atom), 3
576
+ )
577
+ new_props["estimated_lone_pairs"] = estimated_lone_pairs
578
+ new_props["available_lone_pairs"] = available_lone_pairs
579
+
580
+ return new_props
581
+
582
+ @staticmethod
583
+ def _gather_atom_properties(
584
+ atom: Chem.Atom,
585
+ oxidation_state: Optional[float] = None,
586
+ *,
587
+ profile: str = "full",
588
+ ) -> Dict[str, Any]:
589
+ """Collect fallback atom-level node attributes.
590
+
591
+ Minimal profile keys: ``element``, ``aromatic``, ``hcount``,
592
+ ``charge``, ``radical``, ``isomer``, ``partial_charge``,
593
+ ``hybridization``, ``in_ring``, ``neighbors``, ``atom_map``,
594
+ ``oxidation_state``, ``available_lp``, ``lone_pairs``,
595
+ ``valence_electrons``.
596
+
597
+ Full profile additionally includes ``bond_order_sum``,
598
+ ``lp_bond_order_sum``, ``estimated_lone_pairs``,
599
+ ``available_lone_pairs``.
600
+
601
+ :param atom: RDKit atom.
602
+ :type atom: Chem.Atom
603
+ :param oxidation_state: Pre-computed oxidation state, or ``None``.
604
+ :type oxidation_state: Optional[float]
605
+ :param profile: Feature profile — ``"minimal"`` or ``"full"``.
606
+ :type profile: str
607
+ :return: Node attribute dict.
608
+ :rtype: Dict[str, Any]
609
+ """
610
+ try:
611
+ gcharge = (
612
+ round(float(atom.GetProp("_GasteigerCharge")), 3)
613
+ if atom.HasProp("_GasteigerCharge")
614
+ else 0.0
615
+ )
616
+ except Exception:
617
+ gcharge = 0.0
618
+
619
+ try:
620
+ neighbors = sorted(nb.GetSymbol() for nb in atom.GetNeighbors())
621
+ except Exception:
622
+ neighbors = []
623
+
624
+ atom_map = MolToGraph._safe_atom_map(atom)
625
+ estimated_lone_pairs = MolToGraph.estimate_lone_pairs(atom)
626
+ available_lone_pairs = MolToGraph.estimate_available_lone_pairs(atom)
627
+
628
+ props: Dict[str, Any] = {
629
+ "element": atom.GetSymbol(),
630
+ "isotope": atom.GetIsotope(),
631
+ "aromatic": atom.GetIsAromatic(),
632
+ "hcount": atom.GetTotalNumHs(),
633
+ "charge": atom.GetFormalCharge(),
634
+ "radical": atom.GetNumRadicalElectrons(),
635
+ "stereo_descriptor": None,
636
+ "cip_label": atom.GetProp("_CIPCode") if atom.HasProp("_CIPCode") else None,
637
+ "chiral_tag": MolToGraph.get_chiral_tag(atom),
638
+ # Compatibility display field only; do not use as a graph-stereo
639
+ # identity because it historically mapped RDKit CW/CCW to R/S.
640
+ "isomer": MolToGraph.get_stereochemistry(atom),
641
+ "partial_charge": gcharge,
642
+ "hybridization": str(atom.GetHybridization()),
643
+ "in_ring": atom.IsInRing(),
644
+ "neighbors": neighbors,
645
+ "atom_map": atom_map,
646
+ "oxidation_state": (
647
+ None if oxidation_state is None else round(float(oxidation_state), 3)
648
+ ),
649
+ "available_lp": available_lone_pairs > 0,
650
+ "lone_pairs": estimated_lone_pairs,
651
+ "valence_electrons": MolToGraph._safe_valence_electrons(atom),
652
+ }
653
+
654
+ if profile == "full":
655
+ props["bond_order_sum"] = round(MolToGraph._safe_bond_order_sum(atom), 3)
656
+ props["lp_bond_order_sum"] = round(
657
+ MolToGraph._bond_order_sum_for_lone_pairs(atom), 3
658
+ )
659
+ props["estimated_lone_pairs"] = estimated_lone_pairs
660
+ props["available_lone_pairs"] = available_lone_pairs
661
+
662
+ return props
663
+
664
+ @staticmethod
665
+ def _gather_bond_properties(
666
+ bond: Chem.Bond,
667
+ kek_bond: Optional[Chem.Bond] = None,
668
+ ) -> Dict[str, Any]:
669
+ """Collect bond-level edge attributes.
670
+
671
+ :param bond: Original RDKit bond.
672
+ :type bond: Chem.Bond
673
+ :param kek_bond: Matching bond from a kekulized copy.
674
+ :type kek_bond: Optional[Chem.Bond]
675
+ :return: Edge attributes.
676
+ :rtype: Dict[str, Any]
677
+ """
678
+ try:
679
+ order = bond.GetBondTypeAsDouble()
680
+ except Exception:
681
+ order = 1.0
682
+
683
+ try:
684
+ bond_type = str(bond.GetBondType())
685
+ except Exception:
686
+ bond_type = "UNKNOWN"
687
+
688
+ try:
689
+ ez = MolToGraph.get_bond_stereochemistry(bond)
690
+ except Exception:
691
+ ez = "N"
692
+
693
+ try:
694
+ conjugated = bond.GetIsConjugated()
695
+ except Exception:
696
+ conjugated = False
697
+
698
+ try:
699
+ in_ring = bond.IsInRing()
700
+ except Exception:
701
+ in_ring = False
702
+
703
+ try:
704
+ aromatic = bond.GetIsAromatic()
705
+ except Exception:
706
+ aromatic = False
707
+
708
+ try:
709
+ kekule_order = (
710
+ kek_bond.GetBondTypeAsDouble() if kek_bond is not None else order
711
+ )
712
+ except Exception:
713
+ kekule_order = order
714
+
715
+ try:
716
+ kekule_bond_type = (
717
+ str(kek_bond.GetBondType()) if kek_bond is not None else bond_type
718
+ )
719
+ except Exception:
720
+ kekule_bond_type = bond_type
721
+
722
+ sigma_order, pi_order = MolToGraph._split_sigma_pi_order(kekule_order)
723
+
724
+ return {
725
+ "order": order,
726
+ "bond_type": bond_type,
727
+ "aromatic": aromatic,
728
+ "kekule_order": kekule_order,
729
+ "sigma_order": sigma_order,
730
+ "pi_order": pi_order,
731
+ "kekule_bond_type": kekule_bond_type,
732
+ "ez_isomer": ez,
733
+ "stereo_descriptor": None,
734
+ "ez_label": ez,
735
+ "conjugated": conjugated,
736
+ "in_ring": in_ring,
737
+ }
738
+
739
+ @staticmethod
740
+ def _split_sigma_pi_order(kekule_order: float) -> tuple[float, float]:
741
+ """Split a Kekule bond order into sigma and pi contributions."""
742
+ order = max(0.0, float(kekule_order))
743
+ if order <= 0:
744
+ return 0.0, 0.0
745
+ return 1.0, max(0.0, order - 1.0)
746
+
747
+ # ------------------------------------------------------------------
748
+ # Stereochemistry helpers
749
+ # ------------------------------------------------------------------
750
+
751
+ @staticmethod
752
+ def get_stereochemistry(atom: Chem.Atom) -> str:
753
+ """Return the legacy ``R``/``S`` display projection of a chiral tag.
754
+
755
+ This compatibility helper is **not** a CIP assignment and must not be
756
+ used as stereochemical identity. New code should use
757
+ :meth:`get_chiral_tag` until a v2 relative descriptor is available.
758
+
759
+ :param atom: RDKit atom.
760
+ :type atom: Chem.Atom
761
+ :return: Simple atom stereochemistry label.
762
+ :rtype: str
763
+ """
764
+ chiral_tag = atom.GetChiralTag()
765
+ if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CCW:
766
+ return "S"
767
+ if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CW:
768
+ return "R"
769
+ return "N"
770
+
771
+ @staticmethod
772
+ def get_chiral_tag(atom: Chem.Atom) -> str:
773
+ """Return RDKit's atom-order-relative tetrahedral tag.
774
+
775
+ ``CW`` and ``CCW`` encode parity relative to atom ordering, not an
776
+ absolute CIP label. ``NONE`` covers atoms without a tetrahedral tag.
777
+ """
778
+ chiral_tag = atom.GetChiralTag()
779
+ if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CCW:
780
+ return "CCW"
781
+ if chiral_tag == Chem.ChiralType.CHI_TETRAHEDRAL_CW:
782
+ return "CW"
783
+ return "NONE"
784
+
785
+ @staticmethod
786
+ def get_bond_stereochemistry(bond: Chem.Bond) -> str:
787
+ """Return ``E``, ``Z``, or ``N`` for double-bond stereochemistry.
788
+
789
+ :param bond: RDKit bond.
790
+ :type bond: Chem.Bond
791
+ :return: Simple bond stereochemistry label.
792
+ :rtype: str
793
+ """
794
+ if bond.GetBondType() != Chem.BondType.DOUBLE:
795
+ return "N"
796
+
797
+ stereo = bond.GetStereo()
798
+ if stereo == Chem.BondStereo.STEREOE:
799
+ return "E"
800
+ if stereo == Chem.BondStereo.STEREOZ:
801
+ return "Z"
802
+ return "N"
803
+
804
+ # ------------------------------------------------------------------
805
+ # Mapping and legacy API
806
+ # ------------------------------------------------------------------
807
+
808
+ @staticmethod
809
+ def has_atom_mapping(mol: Chem.Mol) -> bool:
810
+ """Return whether any atom has a non-zero atom-map number.
811
+
812
+ :param mol: RDKit molecule.
813
+ :type mol: Chem.Mol
814
+ :return: ``True`` if mapped.
815
+ :rtype: bool
816
+ """
817
+ return any(atom.GetAtomMapNum() != 0 for atom in mol.GetAtoms())
818
+
819
+ @staticmethod
820
+ def random_atom_mapping(mol: Chem.Mol) -> Chem.Mol:
821
+ """Assign random atom-map numbers from ``1`` to ``n`` in-place.
822
+
823
+ :param mol: RDKit molecule to mutate.
824
+ :type mol: Chem.Mol
825
+ :return: Same molecule with assigned atom-map numbers.
826
+ :rtype: Chem.Mol
827
+ """
828
+ indices = list(range(1, mol.GetNumAtoms() + 1))
829
+ random.shuffle(indices)
830
+ for atom, idx in zip(mol.GetAtoms(), indices):
831
+ atom.SetAtomMapNum(idx)
832
+ return mol
833
+
834
+ @classmethod
835
+ def mol_to_graph(
836
+ cls,
837
+ mol: Chem.Mol,
838
+ drop_non_aam: bool = False,
839
+ light_weight: bool = False,
840
+ use_index_as_atom_map: bool = False,
841
+ ) -> nx.Graph:
842
+ """Backward-compatible graph converter.
843
+
844
+ New code should usually prefer :meth:`transform`.
845
+
846
+ :param mol: RDKit molecule.
847
+ :type mol: Chem.Mol
848
+ :param drop_non_aam: If ``True``, remove atoms with atom-map ``0``.
849
+ :type drop_non_aam: bool
850
+ :param light_weight: If ``True``, use reduced attributes.
851
+ :type light_weight: bool
852
+ :param use_index_as_atom_map: If ``True``, use atom maps as node IDs.
853
+ :type use_index_as_atom_map: bool
854
+ :return: Molecular graph.
855
+ :rtype: nx.Graph
856
+ :raises ValueError: If ``drop_non_aam=True`` but
857
+ ``use_index_as_atom_map=False``.
858
+
859
+ .. code-block:: python
860
+
861
+ mol = Chem.MolFromSmiles("[CH3:1][CH2:2][Br:3]")
862
+ graph = MolToGraph.mol_to_graph(
863
+ mol,
864
+ drop_non_aam=True,
865
+ light_weight=True,
866
+ use_index_as_atom_map=True,
867
+ )
868
+ """
869
+ if drop_non_aam and not use_index_as_atom_map:
870
+ raise ValueError(
871
+ "drop_non_aam and use_index_as_atom_map must be both False or both True."
872
+ )
873
+
874
+ if light_weight:
875
+ return cls._create_light_weight_graph(
876
+ mol,
877
+ drop_non_aam=drop_non_aam,
878
+ use_index_as_atom_map=use_index_as_atom_map,
879
+ )
880
+
881
+ return cls._create_detailed_graph(
882
+ mol,
883
+ drop_non_aam=drop_non_aam,
884
+ use_index_as_atom_map=use_index_as_atom_map,
885
+ )
886
+
887
+ @classmethod
888
+ def _create_light_weight_graph(
889
+ cls,
890
+ mol: Chem.Mol,
891
+ drop_non_aam: bool = False,
892
+ use_index_as_atom_map: bool = False,
893
+ ) -> nx.Graph:
894
+ """Create a lightweight graph with corrected lone-pair fields.
895
+
896
+ Node attributes: ``element``, ``aromatic``, ``hcount``, ``charge``,
897
+ ``radical``, ``neighbors``, ``atom_map``, ``oxidation_state``,
898
+ ``available_lp``, ``lone_pairs``, ``valence_electrons``.
899
+ Edge attributes: ``order``, ``bond_type``, ``aromatic``,
900
+ ``kekule_order``, ``sigma_order``, ``pi_order``,
901
+ ``kekule_bond_type``.
902
+
903
+ :param mol: RDKit molecule.
904
+ :type mol: Chem.Mol
905
+ :param drop_non_aam: If ``True``, remove atoms with atom-map ``0``.
906
+ :type drop_non_aam: bool
907
+ :param use_index_as_atom_map: If ``True``, use atom-map numbers as node
908
+ IDs for mapped atoms; unmapped atoms fall back to
909
+ ``atom.GetIdx() + 1``.
910
+ :type use_index_as_atom_map: bool
911
+ :return: Lightweight molecular graph.
912
+ :rtype: nx.Graph
913
+ """
914
+ graph = nx.Graph()
915
+ kek_mol: Optional[Chem.Mol] = cls._make_kekule_copy(mol)
916
+ oxidation_states = cls.estimate_oxidation_states(mol, kek_mol=kek_mol)
917
+
918
+ for atom in mol.GetAtoms():
919
+ atom_map = cls._safe_atom_map(atom)
920
+ atom_id = (
921
+ atom_map
922
+ if (use_index_as_atom_map and atom_map != 0)
923
+ else atom.GetIdx() + 1
924
+ )
925
+
926
+ if drop_non_aam and atom_map == 0:
927
+ continue
928
+
929
+ try:
930
+ neighbors = sorted(nb.GetSymbol() for nb in atom.GetNeighbors())
931
+ except Exception:
932
+ neighbors = []
933
+
934
+ estimated_lone_pairs = cls.estimate_lone_pairs(atom)
935
+ available_lone_pairs = cls.estimate_available_lone_pairs(atom)
936
+
937
+ graph.add_node(
938
+ atom_id,
939
+ element=atom.GetSymbol(),
940
+ aromatic=atom.GetIsAromatic(),
941
+ hcount=atom.GetTotalNumHs(),
942
+ charge=atom.GetFormalCharge(),
943
+ radical=atom.GetNumRadicalElectrons(),
944
+ neighbors=neighbors,
945
+ atom_map=atom_map,
946
+ oxidation_state=round(
947
+ float(oxidation_states.get(atom.GetIdx(), 0.0)), 3
948
+ ),
949
+ available_lp=available_lone_pairs > 0,
950
+ lone_pairs=estimated_lone_pairs,
951
+ valence_electrons=cls._safe_valence_electrons(atom),
952
+ )
953
+
954
+ for bond in mol.GetBonds():
955
+ begin_atom = bond.GetBeginAtom()
956
+ end_atom = bond.GetEndAtom()
957
+
958
+ begin_map = cls._safe_atom_map(begin_atom)
959
+ end_map = cls._safe_atom_map(end_atom)
960
+
961
+ if drop_non_aam and (begin_map == 0 or end_map == 0):
962
+ continue
963
+
964
+ begin_id = (
965
+ begin_map
966
+ if (use_index_as_atom_map and begin_map != 0)
967
+ else begin_atom.GetIdx() + 1
968
+ )
969
+ end_id = (
970
+ end_map
971
+ if (use_index_as_atom_map and end_map != 0)
972
+ else end_atom.GetIdx() + 1
973
+ )
974
+
975
+ kek_bond: Optional[Chem.Bond] = None
976
+ if kek_mol is not None:
977
+ try:
978
+ kek_bond = kek_mol.GetBondWithIdx(bond.GetIdx())
979
+ except Exception:
980
+ kek_bond = None
981
+
982
+ try:
983
+ order = bond.GetBondTypeAsDouble()
984
+ except Exception:
985
+ order = 1.0
986
+
987
+ try:
988
+ aromatic = bond.GetIsAromatic()
989
+ except Exception:
990
+ aromatic = False
991
+
992
+ try:
993
+ bond_type = str(bond.GetBondType())
994
+ except Exception:
995
+ bond_type = "UNKNOWN"
996
+
997
+ try:
998
+ kekule_order = (
999
+ kek_bond.GetBondTypeAsDouble() if kek_bond is not None else order
1000
+ )
1001
+ except Exception:
1002
+ kekule_order = order
1003
+
1004
+ try:
1005
+ kekule_bond_type = (
1006
+ str(kek_bond.GetBondType()) if kek_bond is not None else bond_type
1007
+ )
1008
+ except Exception:
1009
+ kekule_bond_type = bond_type
1010
+
1011
+ graph.add_edge(
1012
+ begin_id,
1013
+ end_id,
1014
+ order=order,
1015
+ bond_type=bond_type,
1016
+ aromatic=aromatic,
1017
+ kekule_order=kekule_order,
1018
+ kekule_bond_type=kekule_bond_type,
1019
+ )
1020
+
1021
+ return graph
1022
+
1023
+ @classmethod
1024
+ def _create_detailed_graph(
1025
+ cls,
1026
+ mol: Chem.Mol,
1027
+ drop_non_aam: bool = False,
1028
+ use_index_as_atom_map: bool = False,
1029
+ ) -> nx.Graph:
1030
+ """Create a detailed graph with fallback atom and bond attributes.
1031
+
1032
+ :param mol: RDKit molecule.
1033
+ :type mol: Chem.Mol
1034
+ :param drop_non_aam: If ``True``, remove unmapped atoms.
1035
+ :type drop_non_aam: bool
1036
+ :param use_index_as_atom_map: If ``True``, use atom maps as node IDs.
1037
+ :type use_index_as_atom_map: bool
1038
+ :return: Detailed molecular graph.
1039
+ :rtype: nx.Graph
1040
+ """
1041
+ try:
1042
+ compute_gasteiger_inplace(mol)
1043
+ except Exception:
1044
+ logger.debug("Gasteiger compute failed inside _create_detailed_graph.")
1045
+
1046
+ graph = nx.Graph()
1047
+ idx_map: Dict[int, int] = {}
1048
+ kek_mol: Optional[Chem.Mol] = cls._make_kekule_copy(mol)
1049
+ oxidation_states = cls.estimate_oxidation_states(mol, kek_mol=kek_mol)
1050
+
1051
+ for atom in mol.GetAtoms():
1052
+ atom_map = cls._safe_atom_map(atom)
1053
+ atom_id = (
1054
+ atom_map
1055
+ if (use_index_as_atom_map and atom_map != 0)
1056
+ else atom.GetIdx() + 1
1057
+ )
1058
+
1059
+ if drop_non_aam and atom_map == 0:
1060
+ continue
1061
+
1062
+ graph.add_node(
1063
+ atom_id,
1064
+ **cls._gather_atom_properties(
1065
+ atom,
1066
+ oxidation_state=oxidation_states.get(atom.GetIdx()),
1067
+ ),
1068
+ )
1069
+ idx_map[atom.GetIdx()] = atom_id
1070
+
1071
+ for bond in mol.GetBonds():
1072
+ begin = idx_map.get(bond.GetBeginAtomIdx())
1073
+ end = idx_map.get(bond.GetEndAtomIdx())
1074
+
1075
+ if begin is None or end is None:
1076
+ continue
1077
+
1078
+ kek_bond: Optional[Chem.Bond] = None
1079
+ if kek_mol is not None:
1080
+ try:
1081
+ kek_bond = kek_mol.GetBondWithIdx(bond.GetIdx())
1082
+ except Exception:
1083
+ kek_bond = None
1084
+
1085
+ graph.add_edge(
1086
+ begin,
1087
+ end,
1088
+ **cls._gather_bond_properties(bond, kek_bond=kek_bond),
1089
+ )
1090
+
1091
+ return graph
1092
+
1093
+ @staticmethod
1094
+ def add_partial_charges(mol: Chem.Mol) -> None:
1095
+ """Compute Gasteiger partial charges in-place.
1096
+
1097
+ :param mol: RDKit molecule to modify.
1098
+ :type mol: Chem.Mol
1099
+ :return: ``None``.
1100
+ :rtype: None
1101
+ """
1102
+ try:
1103
+ AllChem.ComputeGasteigerCharges(mol)
1104
+ except Exception as exc:
1105
+ logger.error("Error computing Gasteiger charges: %s", exc)