synkit 1.1.2__tar.gz → 1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. {synkit-1.1.2 → synkit-1.2}/PKG-INFO +2 -1
  2. {synkit-1.1.2 → synkit-1.2}/pyproject.toml +2 -2
  3. synkit-1.2/synkit/CRN/Construct/__init__.py +16 -0
  4. synkit-1.2/synkit/CRN/Construct/arity.py +36 -0
  5. synkit-1.2/synkit/CRN/Construct/builder.py +539 -0
  6. synkit-1.2/synkit/CRN/Construct/derivation.py +63 -0
  7. synkit-1.2/synkit/CRN/Construct/flattener.py +100 -0
  8. synkit-1.2/synkit/CRN/Construct/keys.py +14 -0
  9. synkit-1.2/synkit/CRN/Construct/mixtures.py +117 -0
  10. synkit-1.2/synkit/CRN/Construct/smiles.py +99 -0
  11. synkit-1.2/synkit/CRN/Construct/state.py +31 -0
  12. synkit-1.2/synkit/CRN/Construct/strategy.py +75 -0
  13. synkit-1.2/synkit/CRN/Construct/worker.py +42 -0
  14. synkit-1.2/synkit/CRN/Pathway/__init__.py +27 -0
  15. synkit-1.2/synkit/CRN/Pathway/_adapter.py +342 -0
  16. synkit-1.2/synkit/CRN/Pathway/pathfinder.py +503 -0
  17. synkit-1.2/synkit/CRN/Pathway/reachability.py +813 -0
  18. synkit-1.2/synkit/CRN/Pathway/realizability.py +1130 -0
  19. synkit-1.2/synkit/CRN/Petrinet/__init__.py +47 -0
  20. synkit-1.2/synkit/CRN/Petrinet/analyzer.py +462 -0
  21. synkit-1.2/synkit/CRN/Petrinet/net.py +1079 -0
  22. synkit-1.2/synkit/CRN/Petrinet/persistence.py +71 -0
  23. synkit-1.2/synkit/CRN/Petrinet/semiflows.py +403 -0
  24. synkit-1.2/synkit/CRN/Petrinet/structure.py +240 -0
  25. synkit-1.2/synkit/CRN/Props/dynamics.py +847 -0
  26. synkit-1.2/synkit/CRN/Props/helper.py +202 -0
  27. synkit-1.2/synkit/CRN/Props/stoich.py +601 -0
  28. synkit-1.2/synkit/CRN/Props/thermo.py +560 -0
  29. synkit-1.2/synkit/CRN/Structure/__init__.py +12 -0
  30. synkit-1.2/synkit/CRN/Structure/reaction.py +244 -0
  31. synkit-1.2/synkit/CRN/Structure/rule.py +57 -0
  32. synkit-1.2/synkit/CRN/Structure/species.py +47 -0
  33. synkit-1.2/synkit/CRN/Structure/syncrn.py +1886 -0
  34. synkit-1.2/synkit/CRN/Symmetry/__init__.py +20 -0
  35. synkit-1.2/synkit/CRN/Symmetry/_common.py +1080 -0
  36. synkit-1.2/synkit/CRN/Symmetry/_ir.py +865 -0
  37. synkit-1.2/synkit/CRN/Symmetry/automorphism.py +477 -0
  38. synkit-1.2/synkit/CRN/Symmetry/canon.py +440 -0
  39. synkit-1.2/synkit/CRN/Symmetry/isomorphism.py +440 -0
  40. synkit-1.2/synkit/CRN/Symmetry/symmetry.py +64 -0
  41. synkit-1.2/synkit/CRN/Symmetry/wl_canon.py +1010 -0
  42. synkit-1.2/synkit/CRN/Visualize/__init__.py +11 -0
  43. synkit-1.2/synkit/CRN/Visualize/labels.py +113 -0
  44. synkit-1.2/synkit/CRN/Visualize/layout.py +721 -0
  45. synkit-1.2/synkit/CRN/Visualize/palette.py +292 -0
  46. synkit-1.2/synkit/CRN/Visualize/validation.py +100 -0
  47. synkit-1.2/synkit/CRN/Visualize/vis.py +1187 -0
  48. synkit-1.2/synkit/CRN/dev_crn/Structure/rxn.py +482 -0
  49. {synkit-1.1.2/synkit/CRN/Topo → synkit-1.2/synkit/CRN/dev_crn/Symmetry}/automorphism.py +2 -2
  50. {synkit-1.1.2/synkit/CRN/Topo → synkit-1.2/synkit/CRN/dev_crn/Symmetry}/canon.py +2 -2
  51. {synkit-1.1.2/synkit/CRN/Topo → synkit-1.2/synkit/CRN/dev_crn/Symmetry}/wl_canon.py +2 -2
  52. {synkit-1.1.2/synkit/CRN/Props → synkit-1.2/synkit/CRN/dev_crn}/deficiency.py +1 -1
  53. {synkit-1.1.2/synkit/CRN/Props → synkit-1.2/synkit/CRN/dev_crn}/injectivity.py +1 -1
  54. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/syn_reactor.py +1 -1
  55. synkit-1.1.2/synkit/CRN/Hypergraph/rxn.py +0 -283
  56. synkit-1.1.2/synkit/CRN/Path/reachability.py +0 -683
  57. synkit-1.1.2/synkit/CRN/Path/realizability.py +0 -655
  58. synkit-1.1.2/synkit/CRN/Petri/__init__.py +0 -42
  59. synkit-1.1.2/synkit/CRN/Petri/analyzer.py +0 -219
  60. synkit-1.1.2/synkit/CRN/Petri/net.py +0 -161
  61. synkit-1.1.2/synkit/CRN/Petri/persistence.py +0 -80
  62. synkit-1.1.2/synkit/CRN/Petri/semiflows.py +0 -67
  63. synkit-1.1.2/synkit/CRN/Petri/structure.py +0 -207
  64. synkit-1.1.2/synkit/CRN/Props/stoich.py +0 -854
  65. synkit-1.1.2/synkit/CRN/Props/thermo.py +0 -107
  66. synkit-1.1.2/synkit/CRN/Props/utils.py +0 -129
  67. synkit-1.1.2/synkit/Synthesis/__init__.py +0 -0
  68. synkit-1.1.2/synkit/Utils/__init__.py +0 -0
  69. {synkit-1.1.2 → synkit-1.2}/.gitignore +0 -0
  70. {synkit-1.1.2 → synkit-1.2}/LICENSE +0 -0
  71. {synkit-1.1.2 → synkit-1.2}/README.md +0 -0
  72. {synkit-1.1.2/synkit/CRN → synkit-1.2/synkit/CRN/Construct}/DAG/__init__.py +0 -0
  73. {synkit-1.1.2/synkit/CRN → synkit-1.2/synkit/CRN/Construct}/DAG/crn.py +0 -0
  74. {synkit-1.1.2/synkit/CRN → synkit-1.2/synkit/CRN/Construct}/DAG/mod_crn.py +0 -0
  75. {synkit-1.1.2/synkit/CRN → synkit-1.2/synkit/CRN/Construct}/DAG/syncrn.py +0 -0
  76. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/Props/__init__.py +0 -0
  77. {synkit-1.1.2/synkit/CRN/viz → synkit-1.2/synkit/CRN/Visualize}/crn_vis.py +0 -0
  78. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/__init__.py +0 -0
  79. {synkit-1.1.2/synkit/CRN/Hypergraph → synkit-1.2/synkit/CRN/dev_crn/Structure}/__init__.py +0 -0
  80. {synkit-1.1.2/synkit/CRN/Hypergraph → synkit-1.2/synkit/CRN/dev_crn/Structure}/backend.py +0 -0
  81. {synkit-1.1.2/synkit/CRN/Hypergraph → synkit-1.2/synkit/CRN/dev_crn/Structure}/conversion.py +0 -0
  82. {synkit-1.1.2/synkit/CRN/Hypergraph → synkit-1.2/synkit/CRN/dev_crn/Structure}/hyperedge.py +0 -0
  83. {synkit-1.1.2/synkit/CRN/Hypergraph → synkit-1.2/synkit/CRN/dev_crn/Structure}/hypergraph.py +0 -0
  84. {synkit-1.1.2/synkit/CRN/Path → synkit-1.2/synkit/CRN/dev_crn/Symmetry}/__init__.py +0 -0
  85. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/__init__.py +0 -0
  86. {synkit-1.1.2/synkit/CRN/Stochastic → synkit-1.2/synkit/CRN/dev_crn/configs}/__init__.py +0 -0
  87. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/configs/loader.py +0 -0
  88. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/configs/models.py +0 -0
  89. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/constants.py +0 -0
  90. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/crn_formula.py +0 -0
  91. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/enumerator.py +0 -0
  92. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/exceptions.py +0 -0
  93. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/explorer.py +0 -0
  94. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/helpers.py +0 -0
  95. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/motif.py +0 -0
  96. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/network.py +0 -0
  97. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/pathway.py +0 -0
  98. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/properties.py +0 -0
  99. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/reaction.py +0 -0
  100. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/utils.py +0 -0
  101. {synkit-1.1.2 → synkit-1.2}/synkit/CRN/dev_crn/viz.py +0 -0
  102. {synkit-1.1.2/synkit/CRN/Topo → synkit-1.2/synkit/Chem/Cluster}/__init__.py +0 -0
  103. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Cluster/butina.py +0 -0
  104. {synkit-1.1.2/synkit/CRN/dev_crn/configs → synkit-1.2/synkit/Chem/Fingerprint}/__init__.py +0 -0
  105. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
  106. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
  107. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Fingerprint/transformation_fp.py +0 -0
  108. {synkit-1.1.2/synkit/Chem/Cluster → synkit-1.2/synkit/Chem/Molecule}/__init__.py +0 -0
  109. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Molecule/atom_features.py +0 -0
  110. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Molecule/descriptors.py +0 -0
  111. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Molecule/formula.py +0 -0
  112. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Molecule/graph_annotator.py +0 -0
  113. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Molecule/standardize.py +0 -0
  114. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Molecule/valence.py +0 -0
  115. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/Mapper/__init__.py +0 -0
  116. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/Mapper/wl_mapper.py +0 -0
  117. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/__init__.py +0 -0
  118. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/aam_validator.py +0 -0
  119. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/balance_check.py +0 -0
  120. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
  121. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/cleaning.py +0 -0
  122. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/deionize.py +0 -0
  123. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/fix_aam.py +0 -0
  124. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/neutralize.py +0 -0
  125. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/radical_wildcard.py +0 -0
  126. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/standardize.py +0 -0
  127. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/Reaction/tautomerize.py +0 -0
  128. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/__init__.py +0 -0
  129. {synkit-1.1.2 → synkit-1.2}/synkit/Chem/utils.py +0 -0
  130. {synkit-1.1.2/synkit/Chem/Fingerprint → synkit-1.2/synkit/Data}/__init__.py +0 -0
  131. {synkit-1.1.2 → synkit-1.2}/synkit/Data/gen_partial_aam.py +0 -0
  132. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Canon/__init__.py +0 -0
  133. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Canon/canon_algs.py +0 -0
  134. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Canon/canon_graph.py +0 -0
  135. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Canon/nauty.py +0 -0
  136. {synkit-1.1.2/synkit/Chem/Molecule → synkit-1.2/synkit/Graph/Context}/__init__.py +0 -0
  137. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Context/hier_context.py +0 -0
  138. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Context/radius_expand.py +0 -0
  139. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/Descriptors/topology.py +0 -0
  140. {synkit-1.1.2/synkit/Data → synkit-1.2/synkit/Graph/Feature/Fingerprint}/__init__.py +0 -0
  141. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +0 -0
  142. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/__init__.py +0 -0
  143. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/graph_descriptors.py +0 -0
  144. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/graph_fps.py +0 -0
  145. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/graph_signature.py +0 -0
  146. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/hash_fps.py +0 -0
  147. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/morgan_fps.py +0 -0
  148. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/path_fps.py +0 -0
  149. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Feature/wl_hash.py +0 -0
  150. {synkit-1.1.2/synkit/Graph/Context → synkit-1.2/synkit/Graph/Hyrogen}/__init__.py +0 -0
  151. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Hyrogen/_misc.py +0 -0
  152. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
  153. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Hyrogen/hextend.py +0 -0
  154. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/__init__.py +0 -0
  155. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/its_builder.py +0 -0
  156. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/its_construction.py +0 -0
  157. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/its_decompose.py +0 -0
  158. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/its_destruction.py +0 -0
  159. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/its_expand.py +0 -0
  160. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/its_relabel.py +0 -0
  161. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/normalize_aam.py +0 -0
  162. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/ITS/partial_its.py +0 -0
  163. {synkit-1.1.2/synkit/Graph/Feature/Fingerprint → synkit-1.2/synkit/Graph/MTG}/__init__.py +0 -0
  164. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/MTG/group_comp.py +0 -0
  165. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/MTG/groupoid.py +0 -0
  166. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/MTG/mcs_matcher.py +0 -0
  167. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/MTG/mtg.py +0 -0
  168. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/MTG/mtg_explore.py +0 -0
  169. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/MTG/utils.py +0 -0
  170. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/__init__.py +0 -0
  171. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/approx_mcs.py +0 -0
  172. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/auto_est.py +0 -0
  173. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/automorphism.py +0 -0
  174. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/batch_cluster.py +0 -0
  175. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/dedup_matches.py +0 -0
  176. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/graph_cluster.py +0 -0
  177. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/graph_matcher.py +0 -0
  178. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/graph_morphism.py +0 -0
  179. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/mcs_matcher.py +0 -0
  180. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/multi_turbo_iso.py +0 -0
  181. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/orbit.py +0 -0
  182. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/partial_matcher.py +0 -0
  183. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/sing.py +0 -0
  184. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/subgraph_matcher.py +0 -0
  185. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/turbo_iso.py +0 -0
  186. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Matcher/wl_sel.py +0 -0
  187. {synkit-1.1.2/synkit/Graph/Hyrogen → synkit-1.2/synkit/Graph/Wildcard}/__init__.py +0 -0
  188. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Wildcard/fuse_graph.py +0 -0
  189. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Wildcard/graph_wc.py +0 -0
  190. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Wildcard/its_merge.py +0 -0
  191. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Wildcard/radwc.py +0 -0
  192. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Wildcard/wc_matcher.py +0 -0
  193. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/Wildcard/wildcard.py +0 -0
  194. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/__init__.py +0 -0
  195. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/canon_graph.py +0 -0
  196. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/syn_graph.py +0 -0
  197. {synkit-1.1.2 → synkit-1.2}/synkit/Graph/utils.py +0 -0
  198. {synkit-1.1.2 → synkit-1.2}/synkit/IO/__init__.py +0 -0
  199. {synkit-1.1.2 → synkit-1.2}/synkit/IO/chem_converter.py +0 -0
  200. {synkit-1.1.2 → synkit-1.2}/synkit/IO/combinatorial/__init__.py +0 -0
  201. {synkit-1.1.2 → synkit-1.2}/synkit/IO/combinatorial/gml_to_graph.py +0 -0
  202. {synkit-1.1.2 → synkit-1.2}/synkit/IO/combinatorial/graph_to_gml.py +0 -0
  203. {synkit-1.1.2 → synkit-1.2}/synkit/IO/combinatorial/graph_to_smarts.py +0 -0
  204. {synkit-1.1.2 → synkit-1.2}/synkit/IO/combinatorial/smarts_expander.py +0 -0
  205. {synkit-1.1.2 → synkit-1.2}/synkit/IO/combinatorial/smarts_generalizer.py +0 -0
  206. {synkit-1.1.2 → synkit-1.2}/synkit/IO/combinatorial/smarts_to_graph.py +0 -0
  207. {synkit-1.1.2 → synkit-1.2}/synkit/IO/data_io.py +0 -0
  208. {synkit-1.1.2 → synkit-1.2}/synkit/IO/data_process.py +0 -0
  209. {synkit-1.1.2 → synkit-1.2}/synkit/IO/debug.py +0 -0
  210. {synkit-1.1.2 → synkit-1.2}/synkit/IO/dg_to_gml.py +0 -0
  211. {synkit-1.1.2 → synkit-1.2}/synkit/IO/gml_to_nx.py +0 -0
  212. {synkit-1.1.2 → synkit-1.2}/synkit/IO/graph_to_mol.py +0 -0
  213. {synkit-1.1.2 → synkit-1.2}/synkit/IO/mol_to_graph.py +0 -0
  214. {synkit-1.1.2 → synkit-1.2}/synkit/IO/nx_to_gml.py +0 -0
  215. {synkit-1.1.2 → synkit-1.2}/synkit/IO/smiles_to_id.py +0 -0
  216. {synkit-1.1.2/synkit/Graph/MTG → synkit-1.2/synkit/Rule/Apply}/__init__.py +0 -0
  217. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Apply/reactor_rule.py +0 -0
  218. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Apply/retro_reactor.py +0 -0
  219. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Apply/rule_matcher.py +0 -0
  220. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Apply/rule_rbl.py +0 -0
  221. {synkit-1.1.2/synkit/Graph/Wildcard → synkit-1.2/synkit/Rule/Compose}/__init__.py +0 -0
  222. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Compose/compose_rule.py +0 -0
  223. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Compose/rule_compose.py +0 -0
  224. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Compose/rule_mapping.py +0 -0
  225. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Compose/seq_comp.py +0 -0
  226. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Compose/valence_constrain.py +0 -0
  227. {synkit-1.1.2/synkit/Rule/Apply → synkit-1.2/synkit/Rule/Modify}/__init__.py +0 -0
  228. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Modify/implict_rule.py +0 -0
  229. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Modify/longest_path.py +0 -0
  230. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Modify/molecule_rule.py +0 -0
  231. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Modify/prune_templates.py +0 -0
  232. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Modify/rule_utils.py +0 -0
  233. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/Modify/strip_rule.py +0 -0
  234. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/__init__.py +0 -0
  235. {synkit-1.1.2 → synkit-1.2}/synkit/Rule/syn_rule.py +0 -0
  236. {synkit-1.1.2/synkit/Rule/Compose → synkit-1.2/synkit/Synthesis/MSR}/__init__.py +0 -0
  237. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/MSR/multi_steps.py +0 -0
  238. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/MSR/path_finder.py +0 -0
  239. {synkit-1.1.2/synkit/Rule/Modify → synkit-1.2/synkit/Synthesis/Metrics}/__init__.py +0 -0
  240. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Metrics/_base.py +0 -0
  241. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Metrics/_plot.py +0 -0
  242. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Metrics/_ranking.py +0 -0
  243. {synkit-1.1.2/synkit/Synthesis/MSR → synkit-1.2/synkit/Synthesis/Reactor}/__init__.py +0 -0
  244. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/batch_reactor.py +0 -0
  245. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/benchmark.py +0 -0
  246. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/imba_engine.py +0 -0
  247. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
  248. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
  249. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/partial_engine.py +0 -0
  250. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/post_syn.py +0 -0
  251. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/rbl_engine.py +0 -0
  252. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/rule_filter.py +0 -0
  253. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
  254. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/Reactor/strategy.py +0 -0
  255. {synkit-1.1.2/synkit/Synthesis/Metrics → synkit-1.2/synkit/Synthesis}/__init__.py +0 -0
  256. {synkit-1.1.2 → synkit-1.2}/synkit/Synthesis/reactor_utils.py +0 -0
  257. {synkit-1.1.2/synkit/Synthesis/Reactor → synkit-1.2/synkit/Utils}/__init__.py +0 -0
  258. {synkit-1.1.2 → synkit-1.2}/synkit/Utils/utils.py +0 -0
  259. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/__init__.py +0 -0
  260. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/chemical_space.py +0 -0
  261. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/crn_vis.py +0 -0
  262. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/embedding.py +0 -0
  263. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/graph_visualizer.py +0 -0
  264. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/pdf_writer.py +0 -0
  265. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/rule_vis.py +0 -0
  266. {synkit-1.1.2 → synkit-1.2}/synkit/Vis/rxn_vis.py +0 -0
  267. {synkit-1.1.2 → synkit-1.2}/synkit/__init__.py +0 -0
  268. {synkit-1.1.2 → synkit-1.2}/synkit/examples.py +0 -0
  269. {synkit-1.1.2 → synkit-1.2}/synkit/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synkit
3
- Version: 1.1.2
3
+ Version: 1.2
4
4
  Summary: Utility for reaction modeling using graph grammar
5
5
  Project-URL: homepage, https://github.com/TieuLongPhan/SynKit
6
6
  Project-URL: source, https://github.com/TieuLongPhan/SynKit
@@ -22,6 +22,7 @@ Requires-Dist: scikit-learn>=1.4.0
22
22
  Requires-Dist: seaborn>=0.13.2
23
23
  Provides-Extra: all
24
24
  Requires-Dist: numpy>=2.2.0; extra == 'all'
25
+ Requires-Dist: sympy>=1.13.1; extra == 'all'
25
26
  Requires-Dist: torch>=2.2.0; extra == 'all'
26
27
  Provides-Extra: docs
27
28
  Requires-Dist: graphviz; extra == 'docs'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "synkit"
7
- version = "1.1.2"
7
+ version = "1.2"
8
8
  description = "Utility for reaction modeling using graph grammar"
9
9
  readme = "README.md"
10
10
  long-description = { file = "CHANGELOG.md" }
@@ -31,7 +31,7 @@ dependencies = [
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
34
- all = ["numpy>=2.2.0", "torch>=2.2.0"]
34
+ all = ["numpy>=2.2.0", "torch>=2.2.0", "sympy>=1.13.1"]
35
35
  docs = [
36
36
  # Core Sphinx
37
37
  "sphinx>=6.0",
@@ -0,0 +1,16 @@
1
+ from .builder import CRNExpand, build_crn_from_smarts
2
+ from .flattener import ReactionDeltaFlattener
3
+ from .state import DerivationState
4
+ from .strategy import ConstructionStrategy, FrontierStrategy
5
+ from .derivation import DerivationRecord, DerivationLog
6
+
7
+ __all__ = [
8
+ "CRNExpand",
9
+ "ReactionDeltaFlattener",
10
+ "build_crn_from_smarts",
11
+ "DerivationState",
12
+ "ConstructionStrategy",
13
+ "FrontierStrategy",
14
+ "DerivationRecord",
15
+ "DerivationLog",
16
+ ]
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ def count_lhs_components(text: str) -> Optional[int]:
7
+ if not text:
8
+ return None
9
+ lhs = text.split(">>", 1)[0].strip() if ">>" in text else text.strip()
10
+ parts = [p for p in lhs.split(".") if p.strip()]
11
+ return len(parts) if parts else None
12
+
13
+
14
+ def infer_rule_arity(rule: Any) -> int:
15
+ ar: Optional[int] = None
16
+ if isinstance(rule, str):
17
+ ar = count_lhs_components(rule)
18
+ else:
19
+ for attr in ("smarts", "smirks", "template"):
20
+ if not hasattr(rule, attr):
21
+ continue
22
+ try:
23
+ ar = count_lhs_components(str(getattr(rule, attr)))
24
+ if ar is not None:
25
+ break
26
+ except Exception:
27
+ continue
28
+ if ar is None:
29
+ try:
30
+ ar = count_lhs_components(repr(rule))
31
+ except Exception:
32
+ ar = None
33
+
34
+ if ar is None or ar < 1:
35
+ ar = 2
36
+ return int(ar)
@@ -0,0 +1,539 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Dict, Iterable, Iterator, List, Optional, Set, Tuple
6
+ import logging
7
+ from concurrent.futures import ProcessPoolExecutor
8
+
9
+ import networkx as nx
10
+
11
+ from .arity import infer_rule_arity
12
+ from .keys import make_dedup_key
13
+ from .smiles import Chem, standardize_smiles_rdkit
14
+ from .worker import apply_rule_worker
15
+ from .state import DerivationState
16
+ from .strategy import FrontierStrategy
17
+ from .derivation import DerivationLog
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class CRNExpand:
24
+ rules: List[Any]
25
+ repeats: int = 50
26
+
27
+ explicit_h: bool = False
28
+ implicit_temp: bool = False
29
+ strategy: Optional[str] = None
30
+ keep_aam: bool = True
31
+
32
+ max_components: int = 3
33
+ use_frontier: bool = True
34
+ max_mixtures_per_rule_step: int = 50_000
35
+ max_tasks_per_step: int = 200_000
36
+ allow_self_mixtures: bool = False
37
+
38
+ skip_no_change: bool = True
39
+ allow_empty_side: bool = False
40
+
41
+ dedup_delta: bool = True
42
+ dedup_across_rules: bool = False
43
+
44
+ graph: nx.DiGraph = field(init=False)
45
+
46
+ _species_index: Dict[str, int] = field(init=False)
47
+ _next_node_id: int = field(init=False)
48
+ _smiles_cache: Dict[str, Optional[str]] = field(init=False)
49
+ _app_counter: Dict[int, int] = field(init=False)
50
+
51
+ _seen_attempts: Set[Tuple[int, Tuple[str, ...]]] = field(init=False)
52
+ _seen_delta: Set[Tuple[Optional[int], Tuple[str, ...], Tuple[str, ...]]] = field(
53
+ init=False
54
+ )
55
+ _rule_arity_cache: Dict[int, int] = field(init=False)
56
+ _warned_no_rdkit: bool = field(init=False)
57
+ state: DerivationState = field(init=False, repr=False)
58
+ strategy_engine: FrontierStrategy = field(init=False, repr=False)
59
+ derivations: DerivationLog = field(init=False, repr=False)
60
+
61
+ def __post_init__(self) -> None:
62
+ if self.max_components < 1:
63
+ raise ValueError("max_components must be >= 1")
64
+ self.state = DerivationState()
65
+ self.strategy_engine = FrontierStrategy()
66
+ self.derivations = DerivationLog()
67
+ self.reset()
68
+
69
+ def reset(self) -> None:
70
+ self.graph = nx.DiGraph()
71
+ self._species_index = {}
72
+ self._next_node_id = 1
73
+ self._smiles_cache = {}
74
+ self._app_counter = {}
75
+
76
+ self._seen_attempts = set()
77
+ self._seen_delta = set()
78
+ self._rule_arity_cache = {}
79
+ self._warned_no_rdkit = False
80
+ self.state.set_initial(pool_keys=set(), frontier_keys=set())
81
+ self.derivations.clear()
82
+
83
+ def _alloc_node_id(self) -> int:
84
+ nid = self._next_node_id
85
+ self._next_node_id += 1
86
+ return nid
87
+
88
+ def _next_app_index(self, *, rule_index: int) -> int:
89
+ cur = self._app_counter.get(rule_index, 0) + 1
90
+ self._app_counter[rule_index] = cur
91
+ return cur
92
+
93
+ def _standardize_smiles(self, smiles: str) -> Optional[str]:
94
+ if smiles in self._smiles_cache:
95
+ return self._smiles_cache[smiles]
96
+
97
+ out = standardize_smiles_rdkit(smiles, keep_aam=self.keep_aam)
98
+ self._smiles_cache[smiles] = out
99
+
100
+ if Chem is None and out is not None and not self._warned_no_rdkit:
101
+ logger.warning(
102
+ "RDKit not available; SMILES standardization is a passthrough."
103
+ )
104
+ self._warned_no_rdkit = True
105
+
106
+ return out
107
+
108
+ def _standardize_product_mixture(self, prod_mix: str) -> List[str]:
109
+ out: List[str] = []
110
+ for s in (prod_mix or "").split("."):
111
+ if not s:
112
+ continue
113
+ std = self._standardize_smiles(s)
114
+ if std is None:
115
+ continue
116
+ out.append(std)
117
+ return out
118
+
119
+ def _infer_rule_arity(self, rule: Any, rule_index: int) -> int:
120
+ if rule_index in self._rule_arity_cache:
121
+ return self._rule_arity_cache[rule_index]
122
+ arity = infer_rule_arity(rule)
123
+ self._rule_arity_cache[rule_index] = int(arity)
124
+ return int(arity)
125
+
126
+ def _add_species_node(self, smiles: str) -> Optional[int]:
127
+ std = self._standardize_smiles(smiles)
128
+ if std is None:
129
+ return None
130
+
131
+ if std in self._species_index:
132
+ return self._species_index[std]
133
+
134
+ nid = self._alloc_node_id()
135
+ self._species_index[std] = nid
136
+ self.graph.add_node(
137
+ nid,
138
+ kind="species",
139
+ smiles=std,
140
+ label=std,
141
+ )
142
+ return nid
143
+
144
+ def _add_rxn_event_node(self, *, step: int, rule_index: int) -> int:
145
+ app_index = self._next_app_index(rule_index=rule_index)
146
+ label = f"r@{rule_index}@{app_index}"
147
+
148
+ eid = self._alloc_node_id()
149
+ self.graph.add_node(
150
+ eid,
151
+ kind="rule",
152
+ label=label,
153
+ step=step,
154
+ rule_index=rule_index,
155
+ app_index=app_index,
156
+ rule_repr=repr(self.rules[rule_index]),
157
+ )
158
+ return eid
159
+
160
+ def _delta_keep_smiles(
161
+ self,
162
+ reactant_ids: List[int],
163
+ products_std_all: List[str],
164
+ ) -> Tuple[List[int], List[str], Tuple[str, ...], Tuple[str, ...]]:
165
+ r_smiles_all = [self.graph.nodes[rid]["smiles"] for rid in reactant_ids]
166
+ unchanged = set(r_smiles_all) & set(products_std_all)
167
+
168
+ r_keep_ids = [
169
+ rid for rid, rs in zip(reactant_ids, r_smiles_all) if rs not in unchanged
170
+ ]
171
+ p_keep_smiles = [ps for ps in products_std_all if ps not in unchanged]
172
+
173
+ r_keep_keys = tuple(
174
+ sorted(self.graph.nodes[rid]["smiles"] for rid in r_keep_ids)
175
+ )
176
+ p_keep_keys = tuple(sorted(p_keep_smiles))
177
+
178
+ return r_keep_ids, p_keep_smiles, r_keep_keys, p_keep_keys
179
+
180
+ def _iter_mixtures_for_rule(
181
+ self,
182
+ pool_keys: List[str],
183
+ frontier_keys: List[str],
184
+ *,
185
+ arity: int,
186
+ cap: int,
187
+ ) -> Iterator[Tuple[str, ...]]:
188
+ return self.strategy_engine.iter_mixtures(
189
+ pool_keys=pool_keys,
190
+ frontier_keys=frontier_keys,
191
+ arity=arity,
192
+ use_frontier=self.use_frontier,
193
+ allow_self_mixtures=self.allow_self_mixtures,
194
+ cap=cap,
195
+ max_components=self.max_components,
196
+ )
197
+
198
+ def build(
199
+ self,
200
+ seeds: Iterable[str],
201
+ *,
202
+ parallel: bool = False,
203
+ max_workers: Optional[int] = None,
204
+ reset: bool = True,
205
+ ) -> nx.DiGraph:
206
+ if reset:
207
+ self.reset()
208
+
209
+ pool_keys, frontier_keys = self._init_pool(seeds)
210
+ self.state.set_initial(pool_keys=pool_keys, frontier_keys=frontier_keys)
211
+ if not pool_keys:
212
+ return self.graph
213
+
214
+ for step in range(1, self.repeats + 1):
215
+ self.state.begin_step(step)
216
+ if self.use_frontier and not frontier_keys:
217
+ break
218
+
219
+ tasks = self._make_tasks_for_step(pool_keys, frontier_keys)
220
+ if not tasks:
221
+ break
222
+
223
+ results = self._run_tasks(tasks, parallel=parallel, max_workers=max_workers)
224
+ next_frontier = self._integrate_results(results, pool_keys, step)
225
+
226
+ frontier_keys = next_frontier
227
+ self.state.advance(next_frontier)
228
+ if self.use_frontier and not frontier_keys:
229
+ break
230
+
231
+ return self.graph
232
+
233
+ def _init_pool(self, seeds: Iterable[str]) -> Tuple[Set[str], Set[str]]:
234
+ pool_keys: Set[str] = set()
235
+ frontier_keys: Set[str] = set()
236
+
237
+ for s in seeds:
238
+ sid = self._add_species_node(s)
239
+ if sid is None:
240
+ continue
241
+ k = self.graph.nodes[sid]["smiles"]
242
+ pool_keys.add(k)
243
+ frontier_keys.add(k)
244
+
245
+ return pool_keys, frontier_keys
246
+
247
+ def _make_tasks_for_step(
248
+ self,
249
+ pool_keys: Set[str],
250
+ frontier_keys: Set[str],
251
+ ) -> List[Tuple[int, Any, str, bool, bool, Optional[str], Tuple[str, ...]]]:
252
+ budget = int(self.max_tasks_per_step)
253
+ tasks: List[
254
+ Tuple[int, Any, str, bool, bool, Optional[str], Tuple[str, ...]]
255
+ ] = []
256
+
257
+ for ridx, rule in enumerate(self.rules):
258
+ if budget <= 0:
259
+ break
260
+
261
+ arity = self._infer_rule_arity(rule, ridx)
262
+ if arity > self.max_components:
263
+ continue
264
+
265
+ cap = min(int(self.max_mixtures_per_rule_step), budget)
266
+ mix_iter = self._iter_mixtures_for_rule(
267
+ list(pool_keys),
268
+ list(frontier_keys),
269
+ arity=arity,
270
+ cap=cap,
271
+ )
272
+
273
+ for mix_keys in mix_iter:
274
+ if budget <= 0:
275
+ break
276
+ task = self._task_from_mix(
277
+ ridx=ridx,
278
+ rule=rule,
279
+ mix_keys=mix_keys,
280
+ )
281
+ if task is None:
282
+ continue
283
+ tasks.append(task)
284
+ budget -= 1
285
+
286
+ return tasks
287
+
288
+ def _task_from_mix(
289
+ self,
290
+ *,
291
+ ridx: int,
292
+ rule: Any,
293
+ mix_keys: Tuple[str, ...],
294
+ ) -> Optional[Tuple[int, Any, str, bool, bool, Optional[str], Tuple[str, ...]]]:
295
+ app_key = (int(ridx), mix_keys)
296
+ if app_key in self._seen_attempts:
297
+ return None
298
+ self._seen_attempts.add(app_key)
299
+
300
+ substrate = ".".join(mix_keys)
301
+ return (
302
+ int(ridx),
303
+ rule,
304
+ substrate,
305
+ self.explicit_h,
306
+ self.implicit_temp,
307
+ self.strategy,
308
+ mix_keys,
309
+ )
310
+
311
+ def _run_tasks(
312
+ self,
313
+ tasks: List[Tuple[int, Any, str, bool, bool, Optional[str], Tuple[str, ...]]],
314
+ *,
315
+ parallel: bool,
316
+ max_workers: Optional[int],
317
+ ) -> List[Tuple[int, Tuple[str, ...], List[str]]]:
318
+ results: List[Tuple[int, Tuple[str, ...], List[str]]] = []
319
+
320
+ if parallel and len(tasks) > 1:
321
+ with ProcessPoolExecutor(max_workers=max_workers) as ex:
322
+ for idx, mix_keys, products_list in ex.map(apply_rule_worker, tasks):
323
+ results.append((idx, mix_keys, products_list))
324
+ return results
325
+
326
+ for t in tasks:
327
+ results.append(apply_rule_worker(t))
328
+ return results
329
+
330
+ def _integrate_results(
331
+ self,
332
+ results: List[Tuple[int, Tuple[str, ...], List[str]]],
333
+ pool_keys: Set[str],
334
+ step: int,
335
+ ) -> Set[str]:
336
+ next_frontier: Set[str] = set()
337
+
338
+ for rule_index, mix_keys, products_list in results:
339
+ if not products_list:
340
+ continue
341
+ self._integrate_one_result(
342
+ rule_index=rule_index,
343
+ mix_keys=mix_keys,
344
+ products_list=products_list,
345
+ pool_keys=pool_keys,
346
+ next_frontier=next_frontier,
347
+ step=step,
348
+ )
349
+
350
+ return next_frontier
351
+
352
+ def _integrate_one_result(
353
+ self,
354
+ *,
355
+ rule_index: int,
356
+ mix_keys: Tuple[str, ...],
357
+ products_list: List[str],
358
+ pool_keys: Set[str],
359
+ next_frontier: Set[str],
360
+ step: int,
361
+ ) -> None:
362
+ reactant_ids = [self._species_index.get(k) for k in mix_keys]
363
+ if any(nid is None for nid in reactant_ids):
364
+ return
365
+ reactant_ids_int = [int(nid) for nid in reactant_ids if nid is not None]
366
+
367
+ seen_prod_mix: Set[str] = set()
368
+ for prod_mix in products_list:
369
+ prod_mix = (prod_mix or "").strip()
370
+ if not prod_mix or prod_mix in seen_prod_mix:
371
+ continue
372
+ seen_prod_mix.add(prod_mix)
373
+
374
+ products_std_all = self._standardize_product_mixture(prod_mix)
375
+ if not products_std_all:
376
+ continue
377
+
378
+ r_keep, p_keep_smiles, r_keep_keys, p_keep_keys = self._delta_keep_smiles(
379
+ reactant_ids_int,
380
+ products_std_all,
381
+ )
382
+
383
+ if self.skip_no_change and (not r_keep and not p_keep_smiles):
384
+ continue
385
+ if (not self.allow_empty_side) and (not r_keep or not p_keep_smiles):
386
+ continue
387
+
388
+ if self.dedup_delta:
389
+ dkey = make_dedup_key(
390
+ dedup_across_rules=self.dedup_across_rules,
391
+ rule_index=int(rule_index),
392
+ r_keep_keys=r_keep_keys,
393
+ p_keep_keys=p_keep_keys,
394
+ )
395
+ if dkey in self._seen_delta:
396
+ continue
397
+ self._seen_delta.add(dkey)
398
+
399
+ eid = self._add_rxn_event_node(step=step, rule_index=int(rule_index))
400
+ nd = self.graph.nodes[eid]
401
+ self._add_reactant_edges(
402
+ reactant_ids=r_keep,
403
+ eid=eid,
404
+ step=step,
405
+ rule_index=int(rule_index),
406
+ )
407
+ self._add_product_edges(
408
+ product_smiles=p_keep_smiles,
409
+ eid=eid,
410
+ step=step,
411
+ rule_index=int(rule_index),
412
+ )
413
+
414
+ self.derivations.append(
415
+ event_id=eid,
416
+ label=str(nd.get("label")),
417
+ step=int(step),
418
+ rule_index=int(rule_index),
419
+ reactants=tuple(
420
+ sorted(self.graph.nodes[rid]["smiles"] for rid in r_keep)
421
+ ),
422
+ products=tuple(sorted(p_keep_smiles)),
423
+ )
424
+
425
+ self._update_pool_with_products(
426
+ products_std_all=products_std_all,
427
+ pool_keys=pool_keys,
428
+ next_frontier=next_frontier,
429
+ )
430
+
431
+ def _add_reactant_edges(
432
+ self,
433
+ *,
434
+ reactant_ids: List[int],
435
+ eid: int,
436
+ step: int,
437
+ rule_index: int,
438
+ ) -> None:
439
+ for rid, stoich in Counter(reactant_ids).items():
440
+ self.graph.add_edge(
441
+ rid,
442
+ eid,
443
+ step=step,
444
+ rule_index=rule_index,
445
+ rxn_id=eid,
446
+ role="reactant",
447
+ stoich=int(stoich),
448
+ )
449
+
450
+ def _add_product_edges(
451
+ self,
452
+ *,
453
+ product_smiles: List[str],
454
+ eid: int,
455
+ step: int,
456
+ rule_index: int,
457
+ ) -> None:
458
+ for ps, stoich in Counter(product_smiles).items():
459
+ pid = self._add_species_node(ps)
460
+ if pid is None:
461
+ continue
462
+ self.graph.add_edge(
463
+ eid,
464
+ pid,
465
+ step=step,
466
+ rule_index=rule_index,
467
+ rxn_id=eid,
468
+ role="product",
469
+ stoich=int(stoich),
470
+ )
471
+
472
+ def _update_pool_with_products(
473
+ self,
474
+ *,
475
+ products_std_all: List[str],
476
+ pool_keys: Set[str],
477
+ next_frontier: Set[str],
478
+ ) -> None:
479
+ for ps in set(products_std_all):
480
+ pid = self._add_species_node(ps)
481
+ if pid is None:
482
+ continue
483
+ pk = self.graph.nodes[pid]["smiles"]
484
+ if pk not in pool_keys:
485
+ pool_keys.add(pk)
486
+ next_frontier.add(pk)
487
+
488
+ @property
489
+ def species_nodes(self) -> List[int]:
490
+ return [n for n, d in self.graph.nodes(data=True) if d.get("kind") == "species"]
491
+
492
+ @property
493
+ def rxn_nodes(self) -> List[int]:
494
+ return [n for n, d in self.graph.nodes(data=True) if d.get("kind") == "rule"]
495
+
496
+ @property
497
+ def derivation_records(self) -> List[Dict[str, object]]:
498
+ return self.derivations.as_dicts()
499
+
500
+
501
+ def build_crn_from_smarts(
502
+ rules: List[str],
503
+ seeds: List[str],
504
+ *,
505
+ repeats: int = 50,
506
+ explicit_h: bool = False,
507
+ implicit_temp: bool = False,
508
+ strategy: Optional[str] = None,
509
+ keep_aam: bool = True,
510
+ parallel: bool = False,
511
+ max_workers: Optional[int] = None,
512
+ max_components: int = 3,
513
+ use_frontier: bool = True,
514
+ max_mixtures_per_rule_step: int = 50_000,
515
+ max_tasks_per_step: int = 200_000,
516
+ allow_self_mixtures: bool = False,
517
+ skip_no_change: bool = True,
518
+ allow_empty_side: bool = False,
519
+ dedup_delta: bool = True,
520
+ dedup_across_rules: bool = False,
521
+ ) -> nx.DiGraph:
522
+ crn = CRNExpand(
523
+ rules=rules,
524
+ repeats=repeats,
525
+ explicit_h=explicit_h,
526
+ implicit_temp=implicit_temp,
527
+ strategy=strategy,
528
+ keep_aam=keep_aam,
529
+ max_components=max_components,
530
+ use_frontier=use_frontier,
531
+ max_mixtures_per_rule_step=max_mixtures_per_rule_step,
532
+ max_tasks_per_step=max_tasks_per_step,
533
+ allow_self_mixtures=allow_self_mixtures,
534
+ skip_no_change=skip_no_change,
535
+ allow_empty_side=allow_empty_side,
536
+ dedup_delta=dedup_delta,
537
+ dedup_across_rules=dedup_across_rules,
538
+ )
539
+ return crn.build(seeds, parallel=parallel, max_workers=max_workers)
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict, List, Tuple
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class DerivationRecord:
9
+ """
10
+ Abstract direct derivation record.
11
+
12
+ This stores the reaction event as multisets of standardized species without
13
+ changing the public NetworkX CRN representation.
14
+ """
15
+
16
+ event_id: int
17
+ label: str
18
+ step: int
19
+ rule_index: int
20
+ reactants: Tuple[str, ...]
21
+ products: Tuple[str, ...]
22
+
23
+
24
+ @dataclass
25
+ class DerivationLog:
26
+ records: List[DerivationRecord] = field(default_factory=list)
27
+
28
+ def clear(self) -> None:
29
+ self.records.clear()
30
+
31
+ def append(
32
+ self,
33
+ *,
34
+ event_id: int,
35
+ label: str,
36
+ step: int,
37
+ rule_index: int,
38
+ reactants: Tuple[str, ...],
39
+ products: Tuple[str, ...],
40
+ ) -> None:
41
+ self.records.append(
42
+ DerivationRecord(
43
+ event_id=int(event_id),
44
+ label=str(label),
45
+ step=int(step),
46
+ rule_index=int(rule_index),
47
+ reactants=tuple(reactants),
48
+ products=tuple(products),
49
+ )
50
+ )
51
+
52
+ def as_dicts(self) -> List[Dict[str, object]]:
53
+ return [
54
+ {
55
+ "event_id": r.event_id,
56
+ "label": r.label,
57
+ "step": r.step,
58
+ "rule_index": r.rule_index,
59
+ "reactants": list(r.reactants),
60
+ "products": list(r.products),
61
+ }
62
+ for r in self.records
63
+ ]