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,550 @@
1
+ import logging
2
+ import weakref
3
+ from typing import Any, Dict, Iterable, List, Optional, Union
4
+
5
+ import networkx as nx
6
+ from joblib import Parallel, delayed
7
+
8
+ from synkit.IO import smiles_to_graph, rsmi_to_its
9
+ from .rule_filter import RuleFilter
10
+ from ..core.engine import SynReactor
11
+
12
+ __all__ = ["BatchReactor"]
13
+
14
+ _UNSUPPORTED_CACHE_VALUE = object()
15
+
16
+
17
+ def _freeze_cache_value(value: Any, active: set[int] | None = None) -> Any:
18
+ """Return an exact hashable value, or a sentinel for unsupported state."""
19
+ value_type = type(value)
20
+ if value_type in {str, int, float, bool, bytes, type(None)}:
21
+ return value_type, value
22
+ if active is None:
23
+ active = set()
24
+ value_id = id(value)
25
+ if value_id in active:
26
+ return _UNSUPPORTED_CACHE_VALUE
27
+ active.add(value_id)
28
+ try:
29
+ if isinstance(value, dict):
30
+ records = []
31
+ for key, item in value.items():
32
+ frozen_key = _freeze_cache_value(key, active)
33
+ frozen_item = _freeze_cache_value(item, active)
34
+ if (
35
+ frozen_key is _UNSUPPORTED_CACHE_VALUE
36
+ or frozen_item is _UNSUPPORTED_CACHE_VALUE
37
+ ):
38
+ return _UNSUPPORTED_CACHE_VALUE
39
+ records.append((frozen_key, frozen_item))
40
+ return dict, frozenset(records)
41
+ if isinstance(value, list):
42
+ records = tuple(_freeze_cache_value(item, active) for item in value)
43
+ return (
44
+ _UNSUPPORTED_CACHE_VALUE
45
+ if _UNSUPPORTED_CACHE_VALUE in records
46
+ else (list, records)
47
+ )
48
+ if isinstance(value, tuple):
49
+ records = tuple(_freeze_cache_value(item, active) for item in value)
50
+ return (
51
+ _UNSUPPORTED_CACHE_VALUE
52
+ if _UNSUPPORTED_CACHE_VALUE in records
53
+ else (tuple, records)
54
+ )
55
+ if isinstance(value, (set, frozenset)):
56
+ records = tuple(_freeze_cache_value(item, active) for item in value)
57
+ return (
58
+ _UNSUPPORTED_CACHE_VALUE
59
+ if _UNSUPPORTED_CACHE_VALUE in records
60
+ else (value_type, frozenset(records))
61
+ )
62
+ # A lossy repr or identity token could conflate states after object-id
63
+ # reuse. Failing open preserves correctness for third-party objects.
64
+ return _UNSUPPORTED_CACHE_VALUE
65
+ finally:
66
+ active.remove(value_id)
67
+
68
+
69
+ def _graph_cache_state(graph: nx.Graph) -> Any:
70
+ """Capture exact graph topology and supported attribute state."""
71
+ graph_attrs = _freeze_cache_value(graph.graph)
72
+ node_records = []
73
+ for node, attrs in graph.nodes(data=True):
74
+ frozen_node = _freeze_cache_value(node)
75
+ frozen_attrs = _freeze_cache_value(attrs)
76
+ if (
77
+ frozen_node is _UNSUPPORTED_CACHE_VALUE
78
+ or frozen_attrs is _UNSUPPORTED_CACHE_VALUE
79
+ ):
80
+ return _UNSUPPORTED_CACHE_VALUE
81
+ node_records.append((frozen_node, frozen_attrs))
82
+
83
+ edge_records = []
84
+ edges = (
85
+ graph.edges(keys=True, data=True)
86
+ if graph.is_multigraph()
87
+ else (
88
+ (left, right, None, attrs) for left, right, attrs in graph.edges(data=True)
89
+ )
90
+ )
91
+ for left, right, key, attrs in edges:
92
+ frozen = tuple(
93
+ _freeze_cache_value(value) for value in (left, right, key, attrs)
94
+ )
95
+ if _UNSUPPORTED_CACHE_VALUE in frozen:
96
+ return _UNSUPPORTED_CACHE_VALUE
97
+ edge_records.append(frozen)
98
+ if graph_attrs is _UNSUPPORTED_CACHE_VALUE:
99
+ return _UNSUPPORTED_CACHE_VALUE
100
+ return (
101
+ type(graph),
102
+ graph_attrs,
103
+ frozenset(node_records),
104
+ frozenset(edge_records),
105
+ )
106
+
107
+
108
+ # =============================================================================
109
+ # Low-level rule application
110
+ # =============================================================================
111
+
112
+
113
+ def _apply_rule_raw(
114
+ substrate: nx.Graph,
115
+ rule: nx.Graph,
116
+ invert: bool,
117
+ *,
118
+ strategy: str,
119
+ explicit_h: bool,
120
+ implicit_temp: bool,
121
+ ) -> List[str]:
122
+ """Apply one rule graph to a substrate graph using SynReactor.
123
+
124
+ :param substrate: Graph representing the substrate molecule.
125
+ :type substrate: networkx.Graph
126
+ :param rule: Graph representing the reaction rule (ITS template).
127
+ :type rule: networkx.Graph
128
+ :param invert: Whether to apply the rule in reverse.
129
+ :type invert: bool
130
+ :param strategy: Matching strategy passed to SynReactor.
131
+ :type strategy: str
132
+ :param explicit_h: Use explicit hydrogens in SynReactor.
133
+ :type explicit_h: bool
134
+ :param implicit_temp: Use implicit templates in SynReactor.
135
+ :type implicit_temp: bool
136
+ :return: A list of product SMARTS strings or reaction SMILES.
137
+ :rtype: list of str
138
+ """
139
+ try:
140
+ reactor = SynReactor(
141
+ substrate=substrate,
142
+ template=rule,
143
+ invert=invert,
144
+ strategy=strategy,
145
+ explicit_h=explicit_h,
146
+ implicit_temp=implicit_temp,
147
+ )
148
+ return list(getattr(reactor, "smarts_list", getattr(reactor, "smarts", [])))
149
+
150
+ except Exception as exc: # pragma: no cover
151
+ logging.getLogger(__name__).debug(
152
+ "SynReactor failed (invert=%s): %s", invert, exc
153
+ )
154
+ return []
155
+
156
+
157
+ # =============================================================================
158
+ # Picklable rule-applier with small FIFO cache
159
+ # =============================================================================
160
+
161
+
162
+ class _RuleApplier:
163
+ """
164
+ Callable wrapper around `_apply_rule_raw` with a FIFO cache.
165
+
166
+ Entries are indexed by object identity and validated against exact graph
167
+ snapshots before reuse, so graph mutation and Python id reuse cannot
168
+ produce stale results.
169
+ """
170
+
171
+ __slots__ = (
172
+ "_strategy",
173
+ "_explicit_h",
174
+ "_implicit_temp",
175
+ "_cache",
176
+ "_cache_max",
177
+ )
178
+
179
+ def __init__(
180
+ self,
181
+ *,
182
+ strategy: str,
183
+ explicit_h: bool,
184
+ implicit_temp: bool,
185
+ cache_enabled: bool,
186
+ cache_maxsize: int,
187
+ ) -> None:
188
+ """
189
+ Initialize the rule applier.
190
+
191
+ :param strategy: Matching strategy for SynReactor.
192
+ :param explicit_h: Pass explicit hydrogens to SynReactor.
193
+ :param implicit_temp: Pass implicit template flag to SynReactor.
194
+ :param cache_enabled: Whether to enable in-process caching.
195
+ :param cache_maxsize: Maximum cache entries before eviction.
196
+ """
197
+ self._strategy = strategy
198
+ self._explicit_h = explicit_h
199
+ self._implicit_temp = implicit_temp
200
+ self._cache = ( # type: ignore[var-annotated]
201
+ {} if cache_enabled and cache_maxsize > 0 else None
202
+ )
203
+ self._cache_max = max(0, int(cache_maxsize))
204
+
205
+ def _execute(
206
+ self,
207
+ substrate: nx.Graph,
208
+ rule: nx.Graph,
209
+ inv: bool,
210
+ ) -> List[str]:
211
+ """Directly call `_apply_rule_raw` without caching."""
212
+ return _apply_rule_raw(
213
+ substrate,
214
+ rule,
215
+ inv,
216
+ strategy=self._strategy,
217
+ explicit_h=self._explicit_h,
218
+ implicit_temp=self._implicit_temp,
219
+ )
220
+
221
+ def __call__(
222
+ self,
223
+ substrate: nx.Graph,
224
+ rule: nx.Graph,
225
+ inv: bool,
226
+ ) -> List[str]:
227
+ """Apply a rule to a substrate, using the cache if enabled.
228
+
229
+ :param substrate: Substrate graph.
230
+ :param rule: Rule graph.
231
+ :param inv: Inversion flag.
232
+ :return: List of reaction outputs.
233
+ """
234
+ if self._cache is None:
235
+ return self._execute(substrate, rule, inv)
236
+
237
+ key = (id(substrate), id(rule), inv)
238
+ entry = self._cache.get(key)
239
+ substrate_state = _graph_cache_state(substrate)
240
+ rule_state = _graph_cache_state(rule)
241
+ cacheable = (
242
+ substrate_state is not _UNSUPPORTED_CACHE_VALUE
243
+ and rule_state is not _UNSUPPORTED_CACHE_VALUE
244
+ )
245
+ if entry is not None and cacheable:
246
+ substrate_ref, rule_ref, old_substrate_state, old_rule_state, result = entry
247
+ if (
248
+ substrate_ref() is substrate
249
+ and rule_ref() is rule
250
+ and old_substrate_state == substrate_state
251
+ and old_rule_state == rule_state
252
+ ):
253
+ return list(result)
254
+
255
+ res = self._execute(substrate, rule, inv)
256
+ if not cacheable:
257
+ return res
258
+ if len(self._cache) >= self._cache_max:
259
+ self._cache.pop(next(iter(self._cache))) # FIFO eviction
260
+ self._cache[key] = (
261
+ weakref.ref(substrate),
262
+ weakref.ref(rule),
263
+ substrate_state,
264
+ rule_state,
265
+ tuple(res),
266
+ )
267
+ return res
268
+
269
+
270
+ # =============================================================================
271
+ # Helper utilities
272
+ # =============================================================================
273
+
274
+
275
+ def _dedupe(items: Iterable[Any]) -> List[Any]:
276
+ """Remove duplicates while preserving order."""
277
+ seen: set[Any] = set()
278
+ out: List[Any] = []
279
+ for x in items:
280
+ if x not in seen:
281
+ seen.add(x)
282
+ out.append(x)
283
+ return out
284
+
285
+
286
+ # =============================================================================
287
+ # Public API
288
+ # =============================================================================
289
+
290
+
291
+ class BatchReactor:
292
+ """
293
+ Parallel, cache-aware batch application of reaction rules to SMILES substrates.
294
+
295
+ :param data: List of SMILES strings or dicts containing SMILES under `host_key`.
296
+ :type data: list of str or dict
297
+ :param host_key: Key to extract SMILES from dict entries (optional).
298
+ :type host_key: str or None
299
+ :param pre_filter_engine: Pre-filtering engine for rules (None to skip).
300
+ :type pre_filter_engine: str or None
301
+ :param explicit_h: Use explicit hydrogens in SynReactor.
302
+ :type explicit_h: bool
303
+ :param implicit_temp: Use implicit templates in SynReactor.
304
+ :type implicit_temp: bool
305
+ :param strategy: Matching strategy for SynReactor.
306
+ :type strategy: str
307
+ :param dedupe: Deduplicate results per-substrate.
308
+ :type dedupe: bool
309
+ :param entry_n_jobs: Number of parallel jobs for substrates.
310
+ :type entry_n_jobs: int
311
+ :param rule_n_jobs: Number of parallel jobs for rules per substrate.
312
+ :type rule_n_jobs: int
313
+ :param parallel_rules: Enable parallelism over rules.
314
+ :type parallel_rules: bool
315
+ :param allow_nested: Allow nested parallelism.
316
+ :type allow_nested: bool
317
+ :param cache_enabled: Enable in-process per-rule caching.
318
+ :type cache_enabled: bool
319
+ :param cache_maxsize: Max entries in per-process cache.
320
+ :type cache_maxsize: int
321
+ :param logger: Optional custom logger.
322
+ :type logger: logging.Logger or None
323
+
324
+ :raises ValueError: If SMILES/rule conversion fails.
325
+ """
326
+
327
+ def __init__(
328
+ self,
329
+ data: List[Union[str, Dict[str, Any]]],
330
+ host_key: Optional[str] = None,
331
+ *,
332
+ pre_filter_engine: Optional[str] = None,
333
+ explicit_h: bool = True,
334
+ implicit_temp: bool = False,
335
+ strategy: str = "bt",
336
+ dedupe: bool = True,
337
+ entry_n_jobs: int = 1,
338
+ rule_n_jobs: int = 1,
339
+ parallel_rules: bool = False,
340
+ allow_nested: bool = False,
341
+ cache_enabled: bool = True,
342
+ cache_maxsize: int = 32768,
343
+ logger: Optional[logging.Logger] = None,
344
+ enable_logging: bool = True,
345
+ ) -> None:
346
+ """
347
+ Initialize the BatchReactor.
348
+
349
+ See class docstring for parameter details.
350
+ """
351
+ self._data = list(data)
352
+ self._host_key = host_key
353
+ self._engine = "syn"
354
+ self._pre_filter = pre_filter_engine.lower() if pre_filter_engine else None
355
+ self._explicit_h = explicit_h
356
+ self._implicit_temp = implicit_temp
357
+ self._strategy = strategy
358
+ self._dedupe = dedupe
359
+ self._entry_jobs = max(1, entry_n_jobs)
360
+ self._rule_jobs = max(1, rule_n_jobs)
361
+ self._parallel_rules = parallel_rules
362
+ self._allow_nested = allow_nested
363
+ self._log = logger or logging.getLogger(__name__)
364
+ if enable_logging is False:
365
+ logging.disable(logging.CRITICAL)
366
+
367
+ self._apply_rule = _RuleApplier(
368
+ strategy=strategy,
369
+ explicit_h=explicit_h,
370
+ implicit_temp=implicit_temp,
371
+ cache_enabled=cache_enabled,
372
+ cache_maxsize=cache_maxsize,
373
+ )
374
+
375
+ def help(self) -> str:
376
+ """Return usage examples and API description.
377
+
378
+ :return: Multi-line help text.
379
+ :rtype: str
380
+ """
381
+ return (
382
+ "BatchReactor(data, host_key=None, pre_filter_engine=None, "
383
+ "explicit_h=True, implicit_temp=False, strategy='bt', dedupe=True, "
384
+ "entry_n_jobs=1, rule_n_jobs=1, parallel_rules=False, allow_nested=False, "
385
+ "cache_enabled=True, cache_maxsize=32768, logger=None)" + "\n"
386
+ "Use .fit(rules, invert=False) to apply reaction rules to the SMILES batch."
387
+ )
388
+
389
+ def describe(self) -> str:
390
+ """Return a configuration summary.
391
+
392
+ :return: Human-readable settings overview.
393
+ :rtype: str
394
+ """
395
+ lines = [
396
+ "BatchReactor configuration:",
397
+ f" entries : {len(self._data)}",
398
+ f" engine : {self._engine}",
399
+ f" pre_filter : {self._pre_filter}",
400
+ f" explicit_h : {self._explicit_h}",
401
+ f" implicit_temp : {self._implicit_temp}",
402
+ f" strategy : {self._strategy}",
403
+ f" dedupe : {self._dedupe}",
404
+ f" entry_n_jobs : {self._entry_jobs}",
405
+ f" rule_n_jobs : {self._rule_jobs}",
406
+ f" parallel_rules : {self._parallel_rules}",
407
+ f" allow_nested : {self._allow_nested}",
408
+ f" cache_enabled : {self._apply_rule._cache is not None}",
409
+ f" cache_maxsize : {self._apply_rule._cache_max}",
410
+ ]
411
+ return "\n".join(lines)
412
+
413
+ def __len__(self) -> int:
414
+ """Return the number of substrates in the batch."""
415
+ return len(self._data)
416
+
417
+ def __iter__(self):
418
+ """Yield each entry from the batch data."""
419
+ yield from self._data
420
+
421
+ def __getitem__(self, idx: int) -> Union[str, Dict[str, Any]]:
422
+ """Get the data entry at index `idx`."""
423
+ return self._data[idx]
424
+
425
+ def __repr__(self) -> str:
426
+ """Concise summary of the BatchReactor instance."""
427
+ return (
428
+ f"<BatchReactor entries={len(self._data)} engine={self._engine} "
429
+ f"pre_filter={self._pre_filter} entry_jobs={self._entry_jobs} "
430
+ f"rule_jobs={self._rule_jobs} parallel_rules={self._parallel_rules}>"
431
+ )
432
+
433
+ def fit(
434
+ self,
435
+ rules: Iterable[Any],
436
+ *,
437
+ invert: bool = False,
438
+ ) -> List[Dict[str, Any]]:
439
+ """Apply reaction rules to each substrate in the batch.
440
+
441
+ :param rules: Iterable of rule graphs or SMILES strings.
442
+ :type rules: iterable
443
+ :param invert: Whether to apply rules in reverse direction.
444
+ :type invert: bool
445
+ :return: A list of dicts, each with:
446
+ - "out": list of product SMARTS/reaction SMILES
447
+ - "count": number of outputs
448
+ :rtype: list of dict
449
+ """
450
+ rule_graphs = self._ensure_graph_rules(rules)
451
+ direction = "bw" if invert else "fw"
452
+
453
+ def worker(entry: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
454
+ g = self._to_graph(entry)
455
+ filtered = (
456
+ RuleFilter(g, rule_graphs, invert=invert, engine=self._pre_filter).new_rules # type: ignore[arg-type]
457
+ if self._pre_filter
458
+ else rule_graphs
459
+ )
460
+ out = self._apply_bulk(g, filtered, invert)
461
+ return {f"{self._engine}_{direction}": out, "count": len(out)}
462
+
463
+ if self._entry_jobs == 1:
464
+ return [worker(e) for e in self._data]
465
+
466
+ return Parallel(
467
+ n_jobs=self._entry_jobs,
468
+ backend="loky",
469
+ prefer="processes",
470
+ )(delayed(worker)(e) for e in self._data)
471
+
472
+ # ------------------------------------------------------------------
473
+ # Additional helpers
474
+ # ------------------------------------------------------------------
475
+
476
+ def _to_graph(self, entry: Union[str, Dict[str, Any]]) -> nx.Graph:
477
+ """Convert a SMILES string or dict entry into a networkx Graph.
478
+
479
+ :param entry: SMILES or dict holding SMILES under host_key.
480
+ :type entry: str or dict
481
+ :return: Molecule graph.
482
+ :rtype: networkx.Graph
483
+ :raises KeyError: if host_key is missing for dict.
484
+ :raises TypeError: if entry is not str or dict.
485
+ :raises ValueError: if SMILES conversion fails.
486
+ """
487
+ if isinstance(entry, dict):
488
+ if self._host_key is None:
489
+ raise KeyError("host_key missing for dict entry")
490
+ entry = entry[self._host_key]
491
+ if not isinstance(entry, str):
492
+ raise TypeError("Each entry must be a SMILES string or dict containing one")
493
+ g = smiles_to_graph(entry, drop_non_aam=False, use_index_as_atom_map=False)
494
+ if g is None:
495
+ raise ValueError(f"Invalid SMILES: {entry}")
496
+ return g
497
+
498
+ @staticmethod
499
+ def _ensure_graph_rules(rules: Iterable[Any]) -> List[nx.Graph]:
500
+ """Convert an iterable of SMILES or graph rules into graph objects.
501
+
502
+ :param rules: Iterable of rule SMILES or networkx.Graph.
503
+ :type rules: iterable
504
+ :return: List of rule graphs.
505
+ :rtype: list of networkx.Graph
506
+ :raises ValueError: if a rule SMILES is invalid.
507
+ :raises TypeError: for unsupported rule types.
508
+ """
509
+ out: List[nx.Graph] = []
510
+ for r in rules:
511
+ if isinstance(r, str):
512
+ g = rsmi_to_its(r, core=True)
513
+ if g is None:
514
+ raise ValueError(f"Invalid rule SMILES: {r}")
515
+ out.append(g)
516
+ elif isinstance(r, nx.Graph):
517
+ out.append(r)
518
+ else:
519
+ raise TypeError("Rules must be str or nx.Graph")
520
+ return out
521
+
522
+ def _apply_bulk(
523
+ self, g: nx.Graph, rules: List[nx.Graph], invert: bool
524
+ ) -> List[str]:
525
+ """Apply a list of rule graphs to a substrate graph.
526
+
527
+ :param g: Substrate graph.
528
+ :type g: networkx.Graph
529
+ :param rules: List of rule graphs.
530
+ :type rules: list of networkx.Graph
531
+ :param invert: Direction flag.
532
+ :type invert: bool
533
+ :return: Flattened list of reaction outputs.
534
+ :rtype: list of str
535
+ """
536
+ jobs = 1
537
+ if (
538
+ self._parallel_rules
539
+ and self._rule_jobs > 1
540
+ and (self._allow_nested or self._entry_jobs == 1)
541
+ ):
542
+ jobs = self._rule_jobs
543
+ if jobs == 1:
544
+ nested = [self._apply_rule(g, r, invert) for r in rules]
545
+ else:
546
+ nested = Parallel(n_jobs=jobs, backend="loky", prefer="processes")(
547
+ delayed(self._apply_rule)(g, r, invert) for r in rules
548
+ )
549
+ flat = [x for sub in nested for x in sub]
550
+ return _dedupe(flat) if self._dedupe else flat
@@ -0,0 +1,143 @@
1
+ import logging
2
+ from typing import Any, Dict, Iterable, List, Optional
3
+ from .batch import BatchReactor
4
+
5
+ # =============================================================================
6
+ # Benchmark subclass
7
+ # =============================================================================
8
+
9
+
10
+ class Benchmark(BatchReactor): # pylint: disable=too-many-arguments
11
+ """
12
+ Extension of BatchReactor to benchmark forward/backward application on reaction-SMILES entries.
13
+
14
+ :param data: List of dicts containing reaction SMILES under `reaction_key`.
15
+ :type data: list of dict
16
+ :param reaction_key: Key for reaction-SMILES strings (format 'reactants>>products').
17
+ :type reaction_key: str
18
+ :param pre_filter_engine: Pre-filtering engine for rules (None to skip).
19
+ :type pre_filter_engine: str or None
20
+ :param explicit_h: Use explicit hydrogens in SynReactor.
21
+ :type explicit_h: bool
22
+ :param implicit_temp: Use implicit templates in SynReactor.
23
+ :type implicit_temp: bool
24
+ :param strategy: Matching strategy for SynReactor.
25
+ :type strategy: str
26
+ :param dedupe: Deduplicate results per substrate.
27
+ :type dedupe: bool
28
+ :param entry_n_jobs: Parallel jobs for substrates.
29
+ :type entry_n_jobs: int
30
+ :param rule_n_jobs: Parallel jobs for rules per substrate.
31
+ :type rule_n_jobs: int
32
+ :param parallel_rules: Enable rule-level parallelism.
33
+ :type parallel_rules: bool
34
+ :param allow_nested: Allow nested parallelism.
35
+ :type allow_nested: bool
36
+ :param cache_enabled: Enable per-process caching.
37
+ :type cache_enabled: bool
38
+ :param cache_maxsize: Max cache entries before eviction.
39
+ :type cache_maxsize: int
40
+ :param logger: Optional custom logger.
41
+ :type logger: logging.Logger or None
42
+ :raises ValueError: If reaction_key entry malformed or SMILES invalid.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ data: List[Dict[str, Any]],
48
+ reaction_key: str = "reactions",
49
+ *,
50
+ pre_filter_engine: Optional[str] = None,
51
+ explicit_h: bool = True,
52
+ implicit_temp: bool = False,
53
+ strategy: str = "bt",
54
+ dedupe: bool = True,
55
+ entry_n_jobs: int = 1,
56
+ rule_n_jobs: int = 1,
57
+ parallel_rules: bool = False,
58
+ allow_nested: bool = False,
59
+ cache_enabled: bool = True,
60
+ cache_maxsize: int = 32768,
61
+ logger: Optional[logging.Logger] = None,
62
+ enable_logging: bool = True,
63
+ ) -> None:
64
+ """
65
+ Initialize Benchmark with reaction entries.
66
+
67
+ Splits each reaction-SMILES into reactant 'r' and product 'p'.
68
+ All other parameters mirror BatchReactor (host_key set to 'r').
69
+ """
70
+ data_prepped = self._get_host(data, reaction_key)
71
+ super().__init__(
72
+ data_prepped,
73
+ host_key="r",
74
+ pre_filter_engine=pre_filter_engine,
75
+ explicit_h=explicit_h,
76
+ implicit_temp=implicit_temp,
77
+ strategy=strategy,
78
+ dedupe=dedupe,
79
+ entry_n_jobs=entry_n_jobs,
80
+ rule_n_jobs=rule_n_jobs,
81
+ parallel_rules=parallel_rules,
82
+ allow_nested=allow_nested,
83
+ cache_enabled=cache_enabled,
84
+ cache_maxsize=cache_maxsize,
85
+ logger=logger,
86
+ enable_logging=enable_logging,
87
+ )
88
+
89
+ @staticmethod
90
+ def _get_host(
91
+ data: List[Dict[str, Any]], reaction_key: str = "reactions"
92
+ ) -> List[Dict[str, Any]]:
93
+ """Populate 'r' and 'p' SMILES fields for each dict entry.
94
+
95
+ :param data: List of dict entries.
96
+ :type data: list of dict
97
+ :param reaction_key: Key for reaction-SMILES string.
98
+ :type reaction_key: str
99
+ :return: Same list with 'r' and 'p' keys added.
100
+ :rtype: list of dict
101
+ :raises ValueError: If any string lacks '>>'.
102
+ """
103
+ for entry in data:
104
+ rxn = entry.get(reaction_key)
105
+ if not isinstance(rxn, str) or ">>" not in rxn:
106
+ raise ValueError(
107
+ f"Invalid reaction string for key '{reaction_key}': {rxn!r}"
108
+ )
109
+ r, p = rxn.split(">>", 1)
110
+ entry["r"], entry["p"] = r, p
111
+ return data
112
+
113
+ def fit(
114
+ self,
115
+ rules: Iterable[Any],
116
+ ) -> List[Dict[str, Any]]:
117
+ """Perform forward (invert=False) on 'r' and backward (invert=True) on 'p'.
118
+
119
+ :param rules: Iterable of rule graphs or SMILES.
120
+ :type rules: iterable
121
+ :return: List of dicts each with keys 'fw','bw','fw_count','bw_count'.
122
+ :rtype: list of dict
123
+ """
124
+ fw_out = super().fit(rules, invert=False)
125
+ self._host_key = "p"
126
+ bw_out = super().fit(rules, invert=True)
127
+ self._host_key = "r"
128
+
129
+ for entry, fw, bw in zip(self._data, fw_out, bw_out):
130
+ entry["fw"] = fw.get(f"{self._engine}_fw", fw.get("out", fw))
131
+ entry["fw_count"] = fw.get("count", len(entry["fw"]))
132
+ entry["bw"] = bw.get(f"{self._engine}_bw", bw.get("out", bw))
133
+ entry["bw_count"] = bw.get("count", len(entry["bw"]))
134
+ return self._data
135
+
136
+ def describe(self) -> str:
137
+ """Return detailed configuration for Benchmark, including reaction_key.
138
+
139
+ :return: Multi-line summary.
140
+ :rtype: str
141
+ """
142
+ base = super().describe().splitlines()
143
+ return "".join(base + ["Benchmark host_key: 'r' (product host: 'p')"])