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,452 @@
1
+ # synkit/Chem/Molecule/descriptors.py
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import (
6
+ List,
7
+ Tuple,
8
+ Optional,
9
+ Protocol,
10
+ runtime_checkable,
11
+ Dict,
12
+ Any,
13
+ Sequence,
14
+ )
15
+
16
+ from rdkit import Chem
17
+ from rdkit.Chem import AllChem, rdMolDescriptors
18
+ from rdkit import RDLogger
19
+
20
+ # Keep RDKit quiet in helper scope (best effort)
21
+ try:
22
+ RDLogger.DisableLog("rdApp.*")
23
+ except Exception:
24
+ pass
25
+
26
+
27
+ @runtime_checkable
28
+ class _MolLike(Protocol):
29
+ """
30
+ Minimal protocol for RDKit-like molecules used by this module.
31
+ """
32
+
33
+ def GetNumAtoms(self) -> int: ...
34
+ def GetAtoms(self): ...
35
+ def GetAtomWithIdx(self, idx: int): ...
36
+
37
+
38
+ # -----------------------------
39
+ # Internal small helpers
40
+ # -----------------------------
41
+ def compute_gasteiger_inplace(mol: Chem.Mol | Any) -> None:
42
+ """Compatibility helper: compute Gasteiger charges in-place (best-effort).
43
+ Reintroduced for backward compatibility with code that imports this from
44
+ synkit.Chem.Molecule.descriptors.
45
+
46
+ :param mol: RDKit Mol (or mol-like object) to annotate. Mutates in place.
47
+ :return: None
48
+ """
49
+ try:
50
+ AllChem.ComputeGasteigerCharges(mol)
51
+ except Exception:
52
+ # best-effort: swallow RDKit quirks and continue
53
+ return
54
+
55
+
56
+ def _make_copy_and_sanitize(mol: Chem.Mol | _MolLike, sanitize: bool) -> Chem.Mol:
57
+ """Make a defensive copy of ``mol`` and optionally sanitize it.
58
+
59
+ :param mol: input molecule-like object
60
+ :param sanitize: whether to call ``Chem.SanitizeMol`` on the copy
61
+ :return: copied (and possibly sanitized) RDKit Mol
62
+ """
63
+ m = Chem.Mol(mol)
64
+ if sanitize:
65
+ try:
66
+ Chem.SanitizeMol(m)
67
+ except Exception:
68
+ # best-effort: keep working with the copy even if sanitization fails
69
+ pass
70
+ return m
71
+
72
+
73
+ def _safe_get_gasteiger_value(atom: Any) -> float:
74
+ """Extract a Gasteiger charge value from an RDKit atom, tolerant to API variants.
75
+
76
+ :param atom: RDKit atom-like object.
77
+ :return: float value (0.0 on failure).
78
+ """
79
+ try:
80
+ if atom.HasProp("_GasteigerCharge"):
81
+ try:
82
+ return float(atom.GetProp("_GasteigerCharge"))
83
+ except Exception:
84
+ try:
85
+ return float(atom.GetDoubleProp("_GasteigerCharge"))
86
+ except Exception:
87
+ return 0.0
88
+ except Exception:
89
+ return 0.0
90
+ return 0.0
91
+
92
+
93
+ def _compute_gasteiger_list(m: Chem.Mol, n_atoms: int) -> List[float]:
94
+ """
95
+ Compute per-atom Gasteiger charges for molecule ``m``.
96
+
97
+ Best-effort: returns a list of length ``n_atoms`` filled with floats.
98
+ """
99
+ gasteiger: List[float] = [0.0] * n_atoms
100
+ try:
101
+ AllChem.ComputeGasteigerCharges(m)
102
+ for a in m.GetAtoms():
103
+ try:
104
+ idx = a.GetIdx()
105
+ except Exception:
106
+ continue
107
+ gasteiger[idx] = _safe_get_gasteiger_value(a)
108
+ except Exception:
109
+ # On any failure leave zeros
110
+ pass
111
+ return gasteiger
112
+
113
+
114
+ def _compute_estate_list(m: Chem.Mol, n_atoms: int) -> List[float]:
115
+ """
116
+ Compute per-atom EState indices (best-effort). Returns zeros if unavailable.
117
+ """
118
+ try:
119
+ estate_vals = list(
120
+ __import__("rdkit.Chem.EState", fromlist=["EState"]).EState.EStateIndices(m)
121
+ )
122
+ if len(estate_vals) != n_atoms:
123
+ estate_vals = [0.0] * n_atoms
124
+ except Exception:
125
+ estate_vals = [0.0] * n_atoms
126
+ return estate_vals
127
+
128
+
129
+ def _calc_crippen_contribs(m: Chem.Mol) -> Optional[List[Tuple[float, float]]]:
130
+ """
131
+ Version-tolerant wrapper for RDKit's Crippen contribution routine.
132
+
133
+ Kept small and used by _compute_crippen_lists.
134
+ """
135
+ fn = getattr(rdMolDescriptors, "CalcCrippenContribs", None)
136
+ if fn is None:
137
+ fn = getattr(rdMolDescriptors, "_CalcCrippenContribs", None)
138
+ if fn is None:
139
+ return None
140
+ try:
141
+ return list(fn(m))
142
+ except Exception:
143
+ return None
144
+
145
+
146
+ def _compute_crippen_lists(
147
+ m: Chem.Mol, n_atoms: int
148
+ ) -> Tuple[List[float], List[float]]:
149
+ """
150
+ Compute per-atom Crippen (logP, MR) lists. Returns two lists of length n_atoms.
151
+ """
152
+ cr_logp: List[float] = [0.0] * n_atoms
153
+ cr_mr: List[float] = [0.0] * n_atoms
154
+ cr = _calc_crippen_contribs(m)
155
+ if cr is not None and len(cr) == n_atoms:
156
+ try:
157
+ cr_logp = [float(x[0]) for x in cr]
158
+ cr_mr = [float(x[1]) for x in cr]
159
+ except Exception:
160
+ cr_logp = [0.0] * n_atoms
161
+ cr_mr = [0.0] * n_atoms
162
+ return cr_logp, cr_mr
163
+
164
+
165
+ def _normalize_vector(vec: Sequence[float], method: Optional[str]) -> List[float]:
166
+ """
167
+ Normalize a numeric vector in-place style (returns a new list).
168
+
169
+ Supported methods: None, "zscore", "minmax".
170
+ If method is None or vector length <= 1, returns a float copy of vec.
171
+ """
172
+ n = len(vec)
173
+ if n == 0 or method is None:
174
+ return [float(x) for x in vec]
175
+
176
+ vals = [float(x) for x in vec]
177
+ if method == "zscore":
178
+ mean = sum(vals) / n
179
+ var = sum((x - mean) ** 2 for x in vals) / n
180
+ std = var**0.5
181
+ if std == 0.0:
182
+ return [0.0 for _ in vals]
183
+ return [(x - mean) / std for x in vals]
184
+ elif method == "minmax":
185
+ lo = min(vals)
186
+ hi = max(vals)
187
+ if hi == lo:
188
+ return [0.0 for _ in vals]
189
+ return [(x - lo) / (hi - lo) for x in vals]
190
+ else:
191
+ raise ValueError(f"Unsupported normalization method: {method!r}")
192
+
193
+
194
+ def _apply_normalization_to_all(
195
+ vectors: Dict[str, List[float]], method: Optional[str]
196
+ ) -> Dict[str, List[float]]:
197
+ """
198
+ Apply normalization ``method`` to all vectors in the dict and return a new dict.
199
+ """
200
+ if method is None:
201
+ # ensure float conversion
202
+ return {k: [float(x) for x in v] for k, v in vectors.items()}
203
+ normalized: Dict[str, List[float]] = {}
204
+ for k, v in vectors.items():
205
+ normalized[k] = _normalize_vector(v, method)
206
+ return normalized
207
+
208
+
209
+ # -----------------------------
210
+ # Public dataclass + builder
211
+ # -----------------------------
212
+
213
+
214
+ @dataclass(frozen=True)
215
+ class PerMolDescriptors:
216
+ """
217
+ Immutable container for per-atom descriptor lists.
218
+
219
+ :param gasteiger: per-atom Gasteiger charges
220
+ :param estate: per-atom EState indices
221
+ :param crippen_logp: per-atom Crippen logP contributions
222
+ :param crippen_mr: per-atom Crippen MR contributions
223
+ """
224
+
225
+ gasteiger: List[float]
226
+ estate: List[float]
227
+ crippen_logp: List[float]
228
+ crippen_mr: List[float]
229
+
230
+ # ----------------- Convenience constructors ---------------------------
231
+ @classmethod
232
+ def compute(
233
+ cls,
234
+ mol: Chem.Mol | _MolLike,
235
+ sanitize: bool = True,
236
+ normalize: Optional[str] = None,
237
+ ) -> "PerMolDescriptors":
238
+ """Best-effort compute per-atom descriptors for the given molecule.
239
+
240
+ This function delegates work to small helpers for clarity and easier
241
+ testing. Behavior is identical to the previous implementation.
242
+
243
+ :param mol: RDKit molecule (or duck-typed equivalent).
244
+ :param sanitize: try to sanitize the copied molecule (default True).
245
+ :param normalize: normalization method: None (default), "zscore", or "minmax".
246
+ :return: PerMolDescriptors instance.
247
+ """
248
+ # Defensive copy and optional sanitization
249
+ m = _make_copy_and_sanitize(mol, sanitize)
250
+ n_atoms = m.GetNumAtoms() if hasattr(m, "GetNumAtoms") else 0
251
+
252
+ # Compute components (small focused helpers)
253
+ gasteiger = _compute_gasteiger_list(m, n_atoms)
254
+ estate_vals = _compute_estate_list(m, n_atoms)
255
+ cr_logp, cr_mr = _compute_crippen_lists(m, n_atoms)
256
+
257
+ # Apply normalization if requested
258
+ vectors = {
259
+ "gasteiger": gasteiger,
260
+ "estate": estate_vals,
261
+ "crippen_logp": cr_logp,
262
+ "crippen_mr": cr_mr,
263
+ }
264
+ vectors = _apply_normalization_to_all(vectors, normalize)
265
+
266
+ return cls(
267
+ gasteiger=vectors["gasteiger"],
268
+ estate=vectors["estate"],
269
+ crippen_logp=vectors["crippen_logp"],
270
+ crippen_mr=vectors["crippen_mr"],
271
+ )
272
+
273
+ @classmethod
274
+ def from_smiles(
275
+ cls, smiles: str, sanitize: bool = True, normalize: Optional[str] = None
276
+ ) -> "PerMolDescriptors":
277
+ """Parse SMILES and compute descriptors.
278
+
279
+ :param smiles: SMILES string to parse.
280
+ :param sanitize: try to sanitize the parsed molecule (default True).
281
+ :param normalize: optional normalization ("zscore" | "minmax" | None).
282
+ :return: PerMolDescriptors instance.
283
+ :raises ValueError: if SMILES fails to parse.
284
+ """
285
+ m = Chem.MolFromSmiles(smiles)
286
+ if m is None:
287
+ raise ValueError(f"Failed to parse SMILES: {smiles!r}")
288
+ return cls.compute(m, sanitize=sanitize, normalize=normalize)
289
+
290
+ # ----------------- Small helpers / serialization ---------------------
291
+ def __repr__(self) -> str:
292
+ return (
293
+ f"{self.__class__.__name__}(num_atoms={self.num_atoms}, "
294
+ f"has_gasteiger={any(v != 0.0 for v in self.gasteiger)})"
295
+ )
296
+
297
+ @property
298
+ def num_atoms(self) -> int:
299
+ """Infer atom count from stored lists (prefers gasteiger length).
300
+
301
+ :return: inferred atom count.
302
+ """
303
+ return len(self.gasteiger)
304
+
305
+ def to_dict(self) -> Dict[str, List[float]]:
306
+ """Convert to a plain dictionary.
307
+
308
+ :return: dict with the per-atom lists.
309
+ """
310
+ return {
311
+ "gasteiger": list(self.gasteiger),
312
+ "estate": list(self.estate),
313
+ "crippen_logp": list(self.crippen_logp),
314
+ "crippen_mr": list(self.crippen_mr),
315
+ }
316
+
317
+
318
+ class PerMolDescriptorsBuilder:
319
+ """Fluent builder for :class:`PerMolDescriptors`.
320
+
321
+ .. rubric:: Example
322
+
323
+ .. code-block:: python
324
+
325
+ desc = (
326
+ PerMolDescriptorsBuilder(mol)
327
+ .compute_gasteiger()
328
+ .compute_estate()
329
+ .compute_crippen()
330
+ .normalize("zscore")
331
+ .build()
332
+ .descriptor
333
+ )
334
+
335
+ The builder's chainable methods return ``self``; call ``.build()`` then
336
+ access the final PerMolDescriptors via the ``.descriptor`` property.
337
+ """
338
+
339
+ def __init__(self, mol: Chem.Mol | _MolLike, sanitize: bool = True):
340
+ """Create a builder for the given molecule.
341
+
342
+ :param mol: RDKit Mol or equivalent.
343
+ :param sanitize: try to sanitize the internal copy (default True).
344
+ """
345
+ self._mol = Chem.Mol(mol)
346
+ self._sanitize = sanitize
347
+ if sanitize:
348
+ try:
349
+ Chem.SanitizeMol(self._mol)
350
+ except Exception:
351
+ pass
352
+
353
+ n_atoms = self._mol.GetNumAtoms() if hasattr(self._mol, "GetNumAtoms") else 0
354
+ self._n_atoms = n_atoms
355
+
356
+ # Internal working vectors (None until computed)
357
+ self._gasteiger: Optional[List[float]] = None
358
+ self._estate: Optional[List[float]] = None
359
+ self._crippen_logp: Optional[List[float]] = None
360
+ self._crippen_mr: Optional[List[float]] = None
361
+ self._normalized_method: Optional[str] = None
362
+ self._built: Optional[PerMolDescriptors] = None
363
+
364
+ # ---------- Chainable compute methods --------------------------------
365
+ def compute_gasteiger(self) -> "PerMolDescriptorsBuilder":
366
+ """Compute Gasteiger charges (best-effort) and store internally.
367
+
368
+ :return: self (chainable).
369
+ """
370
+ self._gasteiger = _compute_gasteiger_list(self._mol, self._n_atoms)
371
+ self._built = None
372
+ return self
373
+
374
+ def compute_estate(self) -> "PerMolDescriptorsBuilder":
375
+ """Compute EState indices (best-effort).
376
+
377
+ :return: self (chainable).
378
+ """
379
+ self._estate = _compute_estate_list(self._mol, self._n_atoms)
380
+ self._built = None
381
+ return self
382
+
383
+ def compute_crippen(self) -> "PerMolDescriptorsBuilder":
384
+ """Compute Crippen per-atom contributions (best-effort).
385
+
386
+ :return: self (chainable).
387
+ """
388
+ cr_logp, cr_mr = _compute_crippen_lists(self._mol, self._n_atoms)
389
+ self._crippen_logp = cr_logp
390
+ self._crippen_mr = cr_mr
391
+ self._built = None
392
+ return self
393
+
394
+ # ---------- Normalization --------------------------------------------
395
+ def normalize(self, method: Optional[str]) -> "PerMolDescriptorsBuilder":
396
+ """Normalize any computed vectors using ``method`` ("zscore" | "minmax" | None).
397
+
398
+ :param method: normalization method or None to skip.
399
+ :return: self (chainable).
400
+ """
401
+ if method is None:
402
+ self._normalized_method = None
403
+ return self
404
+
405
+ if method not in ("zscore", "minmax"):
406
+ raise ValueError(f"Unsupported normalization method: {method!r}")
407
+
408
+ if self._gasteiger is not None:
409
+ self._gasteiger = _normalize_vector(self._gasteiger, method)
410
+ if self._estate is not None:
411
+ self._estate = _normalize_vector(self._estate, method)
412
+ if self._crippen_logp is not None:
413
+ self._crippen_logp = _normalize_vector(self._crippen_logp, method)
414
+ if self._crippen_mr is not None:
415
+ self._crippen_mr = _normalize_vector(self._crippen_mr, method)
416
+
417
+ self._normalized_method = method
418
+ self._built = None
419
+ return self
420
+
421
+ # ---------- Finalize / retrieve -------------------------------------
422
+ def build(self) -> "PerMolDescriptorsBuilder":
423
+ """Finalize internal state and prepare the immutable PerMolDescriptors.
424
+
425
+ The method stores the result internally and returns ``self``. Use the
426
+ ``.descriptor`` property to access the final object.
427
+
428
+ :return: self
429
+ """
430
+ # If a vector wasn't computed, fill with zeros of appropriate length
431
+ n = self._n_atoms
432
+ g = self._gasteiger if self._gasteiger is not None else [0.0] * n
433
+ e = self._estate if self._estate is not None else [0.0] * n
434
+ crp = self._crippen_logp if self._crippen_logp is not None else [0.0] * n
435
+ crm = self._crippen_mr if self._crippen_mr is not None else [0.0] * n
436
+
437
+ self._built = PerMolDescriptors(
438
+ gasteiger=g, estate=e, crippen_logp=crp, crippen_mr=crm
439
+ )
440
+ return self
441
+
442
+ @property
443
+ def descriptor(self) -> PerMolDescriptors:
444
+ """Retrieve the built PerMolDescriptors. If not built yet, ``build()`` is
445
+ called implicitly.
446
+
447
+ :return: PerMolDescriptors
448
+ """
449
+ if self._built is None:
450
+ self.build()
451
+ assert self._built is not None
452
+ return self._built
@@ -0,0 +1,249 @@
1
+ from typing import Dict, List, Optional, Union, Any
2
+ import re
3
+ from copy import deepcopy
4
+
5
+ import pandas as pd
6
+ from joblib import Parallel, delayed
7
+ from rdkit import Chem
8
+ from rdkit.Chem import Descriptors, rdMolDescriptors
9
+
10
+
11
+ class Formula:
12
+ """
13
+ Decompose SMILES into element counts and generate Hill-order formulas / molecular weights using RDKit.
14
+
15
+ Main APIs:
16
+ - :meth:`decompose`: element counts as a dict (e.g., {'C': 6, 'H': 6})
17
+ - :meth:`hill_formula`: Hill-order formula string
18
+ - :meth:`mol_weight`: RDKit molecular weight (sums '.' fragments)
19
+ - :meth:`process_list`: batch over a list of SMILES
20
+ - :meth:`process_list_dict`: batch over a list of dicts or a pandas DataFrame
21
+
22
+ Hill notation rules implemented:
23
+ - If carbon ('C') present: list 'C', then 'H', then other elements alphabetically.
24
+ - If no carbon: list all elements alphabetically.
25
+ - Counts of 1 are omitted (CH3, not C1H3).
26
+
27
+ :param n_jobs: Number of parallel jobs for batch processing via joblib. Use 1 to disable parallelism.
28
+ :param verbose: Verbosity level passed to joblib.Parallel.
29
+ """
30
+
31
+ _element_pattern = re.compile(r"([A-Z][a-z]*)(\d*)")
32
+
33
+ def __init__(self, n_jobs: int = 1, verbose: int = 0) -> None:
34
+ self.n_jobs = int(n_jobs)
35
+ self.verbose = int(verbose)
36
+
37
+ def __repr__(self) -> str:
38
+ return f"Formula(n_jobs={self.n_jobs}, verbose={self.verbose})"
39
+
40
+ # ---------------------------- Core utilities ---------------------------- #
41
+
42
+ @staticmethod
43
+ def _parse_formula_string(formula: str) -> Dict[str, int]:
44
+ """
45
+ Parse a molecular formula string into a dict of element counts.
46
+
47
+ :param formula: Molecular formula string (e.g., "C6H12O6", "CH4", "H2O").
48
+ :return: Dict mapping element symbol to integer count.
49
+ """
50
+ parts = Formula._element_pattern.findall(formula)
51
+ return {elem: (int(n) if n else 1) for elem, n in parts}
52
+
53
+ @staticmethod
54
+ def _sum_counts(counts_list: List[Dict[str, int]]) -> Dict[str, int]:
55
+ """
56
+ Sum a list of element-count dictionaries.
57
+
58
+ :param counts_list: List of dicts of element counts.
59
+ :return: Single dict with summed counts.
60
+ """
61
+ out: Dict[str, int] = {}
62
+ for d in counts_list:
63
+ for k, v in d.items():
64
+ out[k] = out.get(k, 0) + int(v)
65
+ return out
66
+
67
+ # ----------------------------- Single item ------------------------------ #
68
+
69
+ def decompose(self, smiles: str) -> Dict[str, int]:
70
+ """
71
+ Decompose a SMILES string into element counts using RDKit's CalcMolFormula.
72
+ Disconnected fragments separated by '.' are handled by summing counts.
73
+
74
+ :param smiles: SMILES string (may contain '.' for multiple fragments).
75
+ :return: Dict of element counts (empty dict if invalid/empty).
76
+ """
77
+ if not isinstance(smiles, str) or not smiles.strip():
78
+ return {}
79
+
80
+ per_frag_counts: List[Dict[str, int]] = []
81
+ for frag in smiles.split("."):
82
+ mol = Chem.MolFromSmiles(frag)
83
+ if mol is None:
84
+ # Ignore invalid fragments
85
+ continue
86
+ fstr = rdMolDescriptors.CalcMolFormula(mol)
87
+ per_frag_counts.append(self._parse_formula_string(fstr))
88
+
89
+ return self._sum_counts(per_frag_counts)
90
+
91
+ def hill_formula(self, smiles: str) -> str:
92
+ """
93
+ Convert a SMILES to a Hill-order formula string.
94
+
95
+ Rules:
96
+ - If 'C' present: C then H then other elements alphabetical.
97
+ - If no 'C': all elements alphabetical.
98
+ - Counts of 1 are omitted.
99
+
100
+ :param smiles: SMILES string.
101
+ :return: Hill-order formula string; empty string for invalid/empty SMILES.
102
+ """
103
+ counts = self.decompose(smiles)
104
+ if not counts:
105
+ return ""
106
+
107
+ def fmt(elem: str, n: int) -> str:
108
+ return f"{elem}{n if n != 1 else ''}"
109
+
110
+ if "C" in counts:
111
+ parts: List[str] = []
112
+ parts.append(fmt("C", counts.get("C", 0)))
113
+ if counts.get("H", 0) > 0:
114
+ parts.append(fmt("H", counts["H"]))
115
+ others = sorted(k for k in counts.keys() if k not in ("C", "H"))
116
+ parts.extend(fmt(k, counts[k]) for k in others if counts[k] > 0)
117
+ # Defensive: drop any accidental zeros
118
+ parts = [p for p in parts if not p.endswith("0")]
119
+ return "".join(parts)
120
+
121
+ # No carbon present
122
+ parts = [fmt(k, counts[k]) for k in sorted(counts.keys()) if counts[k] > 0]
123
+ return "".join(parts)
124
+
125
+ def mol_weight(self, smiles: str) -> Optional[float]:
126
+ """
127
+ Compute molecular weight using RDKit (sum over '.' fragments).
128
+
129
+ :param smiles: SMILES string (may contain '.' fragments).
130
+ :return: Molecular weight as float, or None if invalid/empty.
131
+ """
132
+ if not isinstance(smiles, str) or not smiles.strip():
133
+ return None
134
+
135
+ total = 0.0
136
+ valid = False
137
+ for frag in smiles.split("."):
138
+ mol = Chem.MolFromSmiles(frag)
139
+ if mol is None:
140
+ continue
141
+ total += Descriptors.MolWt(mol)
142
+ valid = True
143
+
144
+ return total if valid else None
145
+
146
+ # ----------------------------- Batch APIs -------------------------------- #
147
+
148
+ def process_list(
149
+ self,
150
+ smiles_list: List[str],
151
+ what: str = "hill",
152
+ ) -> List[Union[str, Dict[str, int], float, None]]:
153
+ """
154
+ Batch process a list of SMILES.
155
+
156
+ :param smiles_list: List of SMILES strings.
157
+ :param what: One of {'hill', 'decompose', 'molwt'}.
158
+ :return: List of results corresponding to 'what'.
159
+ - 'hill' -> List[str]
160
+ - 'decompose' -> List[Dict[str,int]]
161
+ - 'molwt' -> List[Optional[float]]
162
+ :raises ValueError: If 'what' is unsupported.
163
+ """
164
+ dispatch = {
165
+ "hill": self.hill_formula,
166
+ "decompose": self.decompose,
167
+ "molwt": self.mol_weight,
168
+ }
169
+ if what not in dispatch:
170
+ raise ValueError(
171
+ "Unsupported 'what'. Choose from {'hill', 'decompose', 'molwt'}."
172
+ )
173
+
174
+ fn = dispatch[what]
175
+ if self.n_jobs == 1:
176
+ return [fn(s) for s in smiles_list]
177
+ return Parallel(n_jobs=self.n_jobs, verbose=self.verbose)(
178
+ delayed(fn)(s) for s in smiles_list
179
+ )
180
+
181
+ def process_list_dict(
182
+ self,
183
+ records: Union[List[Dict[str, Any]], pd.DataFrame],
184
+ smiles_key: str = "smiles",
185
+ out_key: str = "hill",
186
+ what: str = "hill",
187
+ copy: bool = True,
188
+ ) -> List[Dict[str, Any]]:
189
+ """
190
+ Batch process a list of dictionaries (or a pandas DataFrame) containing SMILES,
191
+ and return a list of dictionaries with the computed output appended.
192
+
193
+ - If a pandas DataFrame is provided, it is converted to a list of dicts (records).
194
+ - Input dicts are deep-copied by default to avoid in-place mutation.
195
+
196
+ :param records: List[dict] or pandas DataFrame. Each record must contain `smiles_key`.
197
+ :param smiles_key: Key in each record holding the SMILES string.
198
+ :param out_key: Output key to store the computed value, e.g. 'hill', 'decompose', 'molwt'.
199
+ :param what: One of {'hill', 'decompose', 'molwt'} specifying the computation.
200
+ :param copy: If True, deep-copy each record before adding output.
201
+ :return: List of dicts with an added `out_key`.
202
+ :raises KeyError: If a record is missing `smiles_key`.
203
+ :raises ValueError: If 'what' is unsupported.
204
+ """
205
+ if isinstance(records, pd.DataFrame):
206
+ records = records.to_dict(orient="records")
207
+
208
+ if not isinstance(records, list):
209
+ raise ValueError("`records` must be a list of dicts or a pandas DataFrame.")
210
+
211
+ dispatch = {
212
+ "hill": self.hill_formula,
213
+ "decompose": self.decompose,
214
+ "molwt": self.mol_weight,
215
+ }
216
+ if what not in dispatch:
217
+ raise ValueError(
218
+ "Unsupported 'what'. Choose from {'hill', 'decompose', 'molwt'}."
219
+ )
220
+
221
+ fn = dispatch[what]
222
+
223
+ # Extract SMILES list with index mapping
224
+ smiles_seq: List[str] = []
225
+ for i, rec in enumerate(records):
226
+ if not isinstance(rec, dict):
227
+ raise ValueError(f"Record at index {i} is not a dict.")
228
+ if smiles_key not in rec:
229
+ raise KeyError(
230
+ f"Record at index {i} missing required key '{smiles_key}'."
231
+ )
232
+ smiles_seq.append(rec[smiles_key])
233
+
234
+ # Compute results
235
+ if self.n_jobs == 1:
236
+ outputs = [fn(s) for s in smiles_seq]
237
+ else:
238
+ outputs = Parallel(n_jobs=self.n_jobs, verbose=self.verbose)(
239
+ delayed(fn)(s) for s in smiles_seq
240
+ )
241
+
242
+ # Attach outputs
243
+ out_records: List[Dict[str, Any]] = []
244
+ for rec, val in zip(records, outputs):
245
+ r = deepcopy(rec) if copy else rec
246
+ r[out_key] = val
247
+ out_records.append(r)
248
+
249
+ return out_records