synkit 1.4.0__tar.gz → 1.4.1b1__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 (302) hide show
  1. {synkit-1.4.0 → synkit-1.4.1b1}/.gitignore +1 -0
  2. {synkit-1.4.0 → synkit-1.4.1b1}/PKG-INFO +1 -1
  3. {synkit-1.4.0 → synkit-1.4.1b1}/pyproject.toml +1 -1
  4. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/its_expand.py +55 -48
  5. synkit-1.4.1b1/synkit/Graph/Mech/__init__.py +26 -0
  6. synkit-1.4.1b1/synkit/Graph/Mech/electron_accounting.py +309 -0
  7. synkit-1.4.1b1/synkit/Graph/Mech/lwg_editor.py +403 -0
  8. synkit-1.4.1b1/synkit/Graph/Mech/lwg_ops.py +313 -0
  9. synkit-1.4.1b1/synkit/Vis/__init__.py +89 -0
  10. synkit-1.4.1b1/synkit/Vis/crn/__init__.py +5 -0
  11. synkit-1.4.0/synkit/Vis/crn_vis.py → synkit-1.4.1b1/synkit/Vis/crn/visualizer.py +1 -1
  12. synkit-1.4.1b1/synkit/Vis/epd/__init__.py +20 -0
  13. synkit-1.4.1b1/synkit/Vis/epd/arrows.py +425 -0
  14. synkit-1.4.1b1/synkit/Vis/epd/chem.py +197 -0
  15. synkit-1.4.1b1/synkit/Vis/epd/constants.py +83 -0
  16. synkit-1.4.1b1/synkit/Vis/epd/layout.py +283 -0
  17. synkit-1.4.1b1/synkit/Vis/epd/mapping.py +51 -0
  18. synkit-1.4.1b1/synkit/Vis/epd/models.py +107 -0
  19. synkit-1.4.1b1/synkit/Vis/epd/render.py +528 -0
  20. synkit-1.4.1b1/synkit/Vis/epd/utils.py +108 -0
  21. synkit-1.4.1b1/synkit/Vis/epd/visualizer.py +826 -0
  22. synkit-1.4.1b1/synkit/Vis/its/__init__.py +19 -0
  23. synkit-1.4.0/synkit/Vis/its_drawer.py → synkit-1.4.1b1/synkit/Vis/its/drawer.py +3 -3
  24. synkit-1.4.1b1/synkit/Vis/molecule/__init__.py +31 -0
  25. synkit-1.4.0/synkit/Vis/reaction_drawer.py → synkit-1.4.1b1/synkit/Vis/molecule/reaction.py +1 -1
  26. synkit-1.4.1b1/synkit/Vis/mtg/__init__.py +17 -0
  27. synkit-1.4.0/synkit/Vis/mtg_drawer.py → synkit-1.4.1b1/synkit/Vis/mtg/drawer.py +1 -1
  28. synkit-1.4.1b1/synkit/Vis/reaction/__init__.py +19 -0
  29. synkit-1.4.0/synkit/Vis/rxn_vis.py → synkit-1.4.1b1/synkit/Vis/reaction/rxn.py +1 -1
  30. synkit-1.4.1b1/synkit/Vis/space/__init__.py +17 -0
  31. synkit-1.4.0/synkit/Vis/chemical_space.py → synkit-1.4.1b1/synkit/Vis/space/chemical.py +0 -3
  32. synkit-1.4.0/synkit/Graph/Mech/electron_accounting.py +0 -64
  33. synkit-1.4.0/synkit/Utils/__init__.py +0 -0
  34. synkit-1.4.0/synkit/Vis/__init__.py +0 -50
  35. synkit-1.4.0/synkit/Vis/vis_synedu/Vis/__init__.py +0 -7
  36. synkit-1.4.0/synkit/Vis/vis_synedu/Vis/dpo.py +0 -862
  37. synkit-1.4.0/synkit/Vis/vis_synedu/rxn_vis.py +0 -382
  38. synkit-1.4.0/synkit/Vis/vis_synedu/vis.py +0 -501
  39. {synkit-1.4.0 → synkit-1.4.1b1}/LICENSE +0 -0
  40. {synkit-1.4.0 → synkit-1.4.1b1}/README.md +0 -0
  41. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/DAG/__init__.py +0 -0
  42. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/DAG/crn.py +0 -0
  43. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/DAG/mod_crn.py +0 -0
  44. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/DAG/syncrn.py +0 -0
  45. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/__init__.py +0 -0
  46. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/abstract.py +0 -0
  47. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/arity.py +0 -0
  48. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/builder.py +0 -0
  49. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/derivation.py +0 -0
  50. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/flattener.py +0 -0
  51. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/keys.py +0 -0
  52. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/mixtures.py +0 -0
  53. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/smiles.py +0 -0
  54. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/state.py +0 -0
  55. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/strategy.py +0 -0
  56. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Construct/worker.py +0 -0
  57. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Pathway/__init__.py +0 -0
  58. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Pathway/_adapter.py +0 -0
  59. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Pathway/pathfinder.py +0 -0
  60. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Pathway/reachability.py +0 -0
  61. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Pathway/realizability.py +0 -0
  62. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Petrinet/__init__.py +0 -0
  63. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Petrinet/analyzer.py +0 -0
  64. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Petrinet/net.py +0 -0
  65. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Petrinet/persistence.py +0 -0
  66. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Petrinet/semiflows.py +0 -0
  67. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Petrinet/structure.py +0 -0
  68. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Props/__init__.py +0 -0
  69. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Props/dynamics.py +0 -0
  70. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Props/helper.py +0 -0
  71. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Props/stoich.py +0 -0
  72. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Props/thermo.py +0 -0
  73. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Query/__init__.py +0 -0
  74. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Query/kegg_api.py +0 -0
  75. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Query/kegg_extract.py +0 -0
  76. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Query/kegg_impute.py +0 -0
  77. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Query/kegg_parse.py +0 -0
  78. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Structure/__init__.py +0 -0
  79. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Structure/reaction.py +0 -0
  80. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Structure/rule.py +0 -0
  81. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Structure/species.py +0 -0
  82. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Structure/syncrn.py +0 -0
  83. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/__init__.py +0 -0
  84. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/_common.py +0 -0
  85. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/_ir.py +0 -0
  86. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/automorphism.py +0 -0
  87. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/canon.py +0 -0
  88. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/isomorphism.py +0 -0
  89. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/symmetry.py +0 -0
  90. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Symmetry/wl_canon.py +0 -0
  91. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Visualize/__init__.py +0 -0
  92. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Visualize/crn_vis.py +0 -0
  93. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Visualize/labels.py +0 -0
  94. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Visualize/layout.py +0 -0
  95. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Visualize/palette.py +0 -0
  96. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Visualize/validation.py +0 -0
  97. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/Visualize/vis.py +0 -0
  98. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/__init__.py +0 -0
  99. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Structure/__init__.py +0 -0
  100. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Structure/backend.py +0 -0
  101. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Structure/conversion.py +0 -0
  102. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Structure/hyperedge.py +0 -0
  103. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Structure/hypergraph.py +0 -0
  104. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Structure/rxn.py +0 -0
  105. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Symmetry/__init__.py +0 -0
  106. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Symmetry/automorphism.py +0 -0
  107. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Symmetry/canon.py +0 -0
  108. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/Symmetry/wl_canon.py +0 -0
  109. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/__init__.py +0 -0
  110. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/configs/__init__.py +0 -0
  111. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/configs/loader.py +0 -0
  112. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/configs/models.py +0 -0
  113. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/constants.py +0 -0
  114. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/crn_formula.py +0 -0
  115. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/deficiency.py +0 -0
  116. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/enumerator.py +0 -0
  117. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/exceptions.py +0 -0
  118. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/explorer.py +0 -0
  119. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/helpers.py +0 -0
  120. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/injectivity.py +0 -0
  121. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/motif.py +0 -0
  122. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/network.py +0 -0
  123. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/pathway.py +0 -0
  124. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/properties.py +0 -0
  125. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/reaction.py +0 -0
  126. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/utils.py +0 -0
  127. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/CRN/dev_crn/viz.py +0 -0
  128. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Cluster/__init__.py +0 -0
  129. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Cluster/butina.py +0 -0
  130. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Fingerprint/__init__.py +0 -0
  131. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
  132. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
  133. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Fingerprint/transformation_fp.py +0 -0
  134. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Molecule/__init__.py +0 -0
  135. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Molecule/atom_features.py +0 -0
  136. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Molecule/descriptors.py +0 -0
  137. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Molecule/formula.py +0 -0
  138. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Molecule/graph_annotator.py +0 -0
  139. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Molecule/standardize.py +0 -0
  140. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Molecule/valence.py +0 -0
  141. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/Mapper/__init__.py +0 -0
  142. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/Mapper/wl_mapper.py +0 -0
  143. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/__init__.py +0 -0
  144. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/aam_validator.py +0 -0
  145. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/balance_check.py +0 -0
  146. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
  147. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/cleaning.py +0 -0
  148. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/deionize.py +0 -0
  149. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/fix_aam.py +0 -0
  150. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/neutralize.py +0 -0
  151. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/radical_wildcard.py +0 -0
  152. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/standardize.py +0 -0
  153. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/Reaction/tautomerize.py +0 -0
  154. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/__init__.py +0 -0
  155. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Chem/utils.py +0 -0
  156. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Data/__init__.py +0 -0
  157. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Data/gen_partial_aam.py +0 -0
  158. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Canon/__init__.py +0 -0
  159. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Canon/canon_algs.py +0 -0
  160. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Canon/canon_graph.py +0 -0
  161. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Canon/nauty.py +0 -0
  162. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Context/__init__.py +0 -0
  163. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Context/hier_context.py +0 -0
  164. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Context/radius_expand.py +0 -0
  165. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/FG/__init__.py +0 -0
  166. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/FG/api.py +0 -0
  167. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/FG/audit.py +0 -0
  168. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/FG/catalog.py +0 -0
  169. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/FG/detector.py +0 -0
  170. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/FG/model.py +0 -0
  171. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/FG/ring_system.py +0 -0
  172. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/Descriptors/topology.py +0 -0
  173. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
  174. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +0 -0
  175. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/__init__.py +0 -0
  176. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/graph_descriptors.py +0 -0
  177. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/graph_fps.py +0 -0
  178. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/graph_signature.py +0 -0
  179. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/hash_fps.py +0 -0
  180. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/morgan_fps.py +0 -0
  181. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/path_fps.py +0 -0
  182. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Feature/wl_hash.py +0 -0
  183. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Hyrogen/__init__.py +0 -0
  184. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Hyrogen/_misc.py +0 -0
  185. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
  186. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Hyrogen/hextend.py +0 -0
  187. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/__init__.py +0 -0
  188. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/its_builder.py +0 -0
  189. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/its_construction.py +0 -0
  190. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/its_decompose.py +0 -0
  191. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/its_destruction.py +0 -0
  192. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/its_relabel.py +0 -0
  193. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/its_reverter.py +0 -0
  194. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/normalize_aam.py +0 -0
  195. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/partial_its.py +0 -0
  196. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/ITS/rc_extractor.py +0 -0
  197. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/MTG/__init__.py +0 -0
  198. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/MTG/mcs_matcher.py +0 -0
  199. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/MTG/mtg.py +0 -0
  200. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/MTG/mtg_explore.py +0 -0
  201. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/MTG/utils.py +0 -0
  202. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/__init__.py +0 -0
  203. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/approx_mcs.py +0 -0
  204. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/auto_est.py +0 -0
  205. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/automorphism.py +0 -0
  206. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/batch_cluster.py +0 -0
  207. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/dedup_matches.py +0 -0
  208. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/graph_cluster.py +0 -0
  209. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/graph_matcher.py +0 -0
  210. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/graph_morphism.py +0 -0
  211. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/mcs_matcher.py +0 -0
  212. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/multi_turbo_iso.py +0 -0
  213. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/orbit.py +0 -0
  214. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/partial_matcher.py +0 -0
  215. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/sing.py +0 -0
  216. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/subgraph_matcher.py +0 -0
  217. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/turbo_iso.py +0 -0
  218. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Matcher/wl_sel.py +0 -0
  219. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Mech/conversion.py +0 -0
  220. {synkit-1.4.0/synkit/Graph/Mech → synkit-1.4.1b1/synkit/Graph/Wildcard}/__init__.py +0 -0
  221. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Wildcard/fuse_graph.py +0 -0
  222. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Wildcard/graph_wc.py +0 -0
  223. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Wildcard/its_merge.py +0 -0
  224. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Wildcard/radwc.py +0 -0
  225. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Wildcard/wc_matcher.py +0 -0
  226. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/Wildcard/wildcard.py +0 -0
  227. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/__init__.py +0 -0
  228. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/canon_graph.py +0 -0
  229. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/syn_graph.py +0 -0
  230. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Graph/utils.py +0 -0
  231. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/__init__.py +0 -0
  232. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/chem_converter.py +0 -0
  233. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/combinatorial/__init__.py +0 -0
  234. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/combinatorial/gml_to_graph.py +0 -0
  235. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/combinatorial/graph_to_gml.py +0 -0
  236. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/combinatorial/graph_to_smarts.py +0 -0
  237. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/combinatorial/smarts_expander.py +0 -0
  238. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/combinatorial/smarts_generalizer.py +0 -0
  239. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/combinatorial/smarts_to_graph.py +0 -0
  240. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/data_io.py +0 -0
  241. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/data_process.py +0 -0
  242. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/debug.py +0 -0
  243. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/dg_to_gml.py +0 -0
  244. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/gml_to_nx.py +0 -0
  245. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/graph_to_mol.py +0 -0
  246. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/mol_to_graph.py +0 -0
  247. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/nx_to_gml.py +0 -0
  248. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/IO/smiles_to_id.py +0 -0
  249. {synkit-1.4.0/synkit/Graph/Wildcard → synkit-1.4.1b1/synkit/Rule/Apply}/__init__.py +0 -0
  250. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Apply/reactor_rule.py +0 -0
  251. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Apply/retro_reactor.py +0 -0
  252. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Apply/rule_matcher.py +0 -0
  253. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Apply/rule_rbl.py +0 -0
  254. {synkit-1.4.0/synkit/Rule/Apply → synkit-1.4.1b1/synkit/Rule/Compose}/__init__.py +0 -0
  255. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Compose/compose_rule.py +0 -0
  256. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Compose/rule_compose.py +0 -0
  257. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Compose/rule_mapping.py +0 -0
  258. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Compose/seq_comp.py +0 -0
  259. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Compose/valence_constrain.py +0 -0
  260. {synkit-1.4.0/synkit/Rule/Compose → synkit-1.4.1b1/synkit/Rule/Modify}/__init__.py +0 -0
  261. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Modify/implict_rule.py +0 -0
  262. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Modify/longest_path.py +0 -0
  263. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Modify/molecule_rule.py +0 -0
  264. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Modify/prune_templates.py +0 -0
  265. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Modify/rule_utils.py +0 -0
  266. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/Modify/strip_rule.py +0 -0
  267. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/__init__.py +0 -0
  268. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Rule/syn_rule.py +0 -0
  269. {synkit-1.4.0/synkit/Rule/Modify → synkit-1.4.1b1/synkit/Synthesis/MSR}/__init__.py +0 -0
  270. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/MSR/multi_steps.py +0 -0
  271. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/MSR/path_finder.py +0 -0
  272. {synkit-1.4.0/synkit/Synthesis/MSR → synkit-1.4.1b1/synkit/Synthesis/Metrics}/__init__.py +0 -0
  273. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Metrics/_base.py +0 -0
  274. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Metrics/_plot.py +0 -0
  275. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Metrics/_ranking.py +0 -0
  276. {synkit-1.4.0/synkit/Synthesis/Metrics → synkit-1.4.1b1/synkit/Synthesis/Reactor}/__init__.py +0 -0
  277. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/batch_reactor.py +0 -0
  278. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/benchmark.py +0 -0
  279. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/imba_engine.py +0 -0
  280. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
  281. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
  282. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/partial_engine.py +0 -0
  283. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/post_syn.py +0 -0
  284. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/rbl_engine.py +0 -0
  285. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/rule_filter.py +0 -0
  286. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
  287. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/strategy.py +0 -0
  288. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/Reactor/syn_reactor.py +0 -0
  289. {synkit-1.4.0/synkit/Synthesis/Reactor → synkit-1.4.1b1/synkit/Synthesis}/__init__.py +0 -0
  290. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Synthesis/reactor_utils.py +0 -0
  291. {synkit-1.4.0/synkit/Synthesis → synkit-1.4.1b1/synkit/Utils}/__init__.py +0 -0
  292. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Utils/utils.py +0 -0
  293. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Vis/graph_visualizer.py +0 -0
  294. /synkit-1.4.0/synkit/Vis/molecule_drawer.py → /synkit-1.4.1b1/synkit/Vis/molecule/drawer.py +0 -0
  295. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Vis/pdf_writer.py +0 -0
  296. /synkit-1.4.0/synkit/Vis/rule_vis.py → /synkit-1.4.1b1/synkit/Vis/reaction/rule.py +0 -0
  297. {synkit-1.4.0/synkit/Vis → synkit-1.4.1b1/synkit/Vis/space}/embedding.py +0 -0
  298. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Vis/visual_drawer.py +0 -0
  299. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/Vis/visual_model.py +0 -0
  300. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/__init__.py +0 -0
  301. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/examples.py +0 -0
  302. {synkit-1.4.0 → synkit-1.4.1b1}/synkit/version.py +0 -0
@@ -37,3 +37,4 @@ test_syn.py
37
37
  .gitignore
38
38
  measure_candidate_stages.py
39
39
  run_valid_bug_cases.py
40
+ debug/*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synkit
3
- Version: 1.4.0
3
+ Version: 1.4.1b1
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "synkit"
7
- version = "1.4.0"
7
+ version = "1.4.1b1"
8
8
  description = "Utility for reaction modeling using graph grammar"
9
9
  readme = "README.md"
10
10
  long-description = { file = "CHANGELOG.md" }
@@ -100,31 +100,19 @@ class ITSExpand:
100
100
  )
101
101
 
102
102
  @staticmethod
103
- def _validate_atom_maps_within_range(
104
- atom_maps: list[int],
105
- n_nodes: int,
106
- ) -> None:
107
- """Validate that atom-map numbers can be used as contiguous node IDs.
108
-
109
- In the side graph, we want final node IDs to remain exactly ``1..N``.
110
- Therefore, a mapped atom can only be moved to its atom-map number if
111
- that number is within ``1..N``.
103
+ def _validate_positive_atom_maps(atom_maps: list[int]) -> None:
104
+ """Validate that atom-map numbers can be used as node IDs.
112
105
 
113
106
  :param atom_maps: Nonzero atom-map numbers.
114
107
  :type atom_maps: list[int]
115
- :param n_nodes: Number of nodes in the side graph.
116
- :type n_nodes: int
117
- :raises ValueError: If any atom-map number is outside ``1..N``.
108
+ :raises ValueError: If any atom-map number is not positive.
118
109
  """
119
- bad_targets = [
120
- atom_map for atom_map in atom_maps if atom_map < 1 or atom_map > n_nodes
121
- ]
110
+ bad_targets = [atom_map for atom_map in atom_maps if atom_map < 1]
122
111
 
123
112
  if bad_targets:
124
113
  raise ValueError(
125
- "Cannot keep side graph node ids contiguous from 1..N while "
126
- f"also using atom_map as node id. The following atom maps are "
127
- f"outside 1..{n_nodes}: {bad_targets}"
114
+ "Cannot use non-positive atom_map values as node ids. "
115
+ f"Invalid atom maps: {bad_targets}"
128
116
  )
129
117
 
130
118
  @staticmethod
@@ -153,17 +141,33 @@ class ITSExpand:
153
141
 
154
142
  return mapping, used_ids
155
143
 
144
+ @staticmethod
145
+ def _next_free_positive_id(used_ids: set[int], start: int = 1) -> int:
146
+ """Return the smallest positive integer not present in ``used_ids``.
147
+
148
+ :param used_ids: Node IDs already occupied by mapped atoms.
149
+ :type used_ids: set[int]
150
+ :param start: First candidate ID.
151
+ :type start: int
152
+ :returns: Available positive node ID.
153
+ :rtype: int
154
+ """
155
+ candidate = max(1, start)
156
+ while candidate in used_ids:
157
+ candidate += 1
158
+ return candidate
159
+
156
160
  @staticmethod
157
161
  def _assign_unmapped_nodes(
158
162
  graph,
159
163
  mapping: dict,
160
164
  used_ids: set[int],
161
165
  ) -> dict:
162
- """Assign unmapped atoms while preserving contiguous node IDs.
166
+ """Assign unmapped atoms while avoiding mapped atom-map IDs.
163
167
 
164
168
  Unmapped atoms keep their original node ID when possible. If an
165
- unmapped atom's node ID conflicts with a mapped atom's target ID, it is
166
- moved into one of the remaining free IDs inside ``1..N``.
169
+ unmapped atom's node ID conflicts with a mapped atom-map target, it is
170
+ moved into the smallest unused positive ID.
167
171
 
168
172
  :param graph: Molecular side graph.
169
173
  :type graph: networkx.Graph
@@ -174,9 +178,7 @@ class ITSExpand:
174
178
  :returns: Complete old-node to new-node mapping.
175
179
  :rtype: dict
176
180
  """
177
- n_nodes = graph.number_of_nodes()
178
- free_ids = set(range(1, n_nodes + 1)) - used_ids
179
- pending_unmapped = []
181
+ next_candidate = 1
180
182
 
181
183
  for node, data in graph.nodes(data=True):
182
184
  atom_map = ITSExpand._atom_map(data)
@@ -184,36 +186,39 @@ class ITSExpand:
184
186
  if atom_map != 0:
185
187
  continue
186
188
 
187
- if isinstance(node, int) and node in free_ids:
189
+ if isinstance(node, int) and node > 0 and node not in used_ids:
188
190
  mapping[node] = node
189
- free_ids.remove(node)
190
- else:
191
- pending_unmapped.append(node)
191
+ used_ids.add(node)
192
+ next_candidate = max(next_candidate, node + 1)
193
+ continue
192
194
 
193
- for old_node, new_node in zip(pending_unmapped, sorted(free_ids)):
194
- mapping[old_node] = new_node
195
+ new_node = ITSExpand._next_free_positive_id(used_ids, next_candidate)
196
+ mapping[node] = new_node
197
+ used_ids.add(new_node)
198
+ next_candidate = new_node + 1
195
199
 
196
200
  return mapping
197
201
 
198
202
  @staticmethod
199
- def _validate_contiguous_mapping(mapping: dict, n_nodes: int) -> None:
200
- """Validate that a mapping produces exactly node IDs ``1..N``.
203
+ def _validate_complete_unique_mapping(mapping: dict, n_nodes: int) -> None:
204
+ """Validate that every source node maps to a unique target node.
201
205
 
202
206
  :param mapping: Old-node to new-node mapping.
203
207
  :type mapping: dict
204
208
  :param n_nodes: Number of nodes in the graph.
205
209
  :type n_nodes: int
206
- :raises RuntimeError: If the mapped node IDs are not exactly ``1..N``.
210
+ :raises RuntimeError: If the mapping is incomplete or has collisions.
207
211
  """
208
- expected_ids = set(range(1, n_nodes + 1))
209
- actual_ids = set(mapping.values())
212
+ if len(mapping) != n_nodes:
213
+ raise RuntimeError(
214
+ f"Reindexing failed. Expected {n_nodes} mapped nodes; "
215
+ f"got {len(mapping)}."
216
+ )
210
217
 
211
- if actual_ids != expected_ids:
212
- missing = sorted(expected_ids - actual_ids)
213
- extra = sorted(actual_ids - expected_ids)
218
+ if len(set(mapping.values())) != n_nodes:
214
219
  raise RuntimeError(
215
- f"Reindexing failed. Missing node ids: {missing}; "
216
- f"extra node ids: {extra}"
220
+ "Reindexing failed. Multiple source nodes map to the same "
221
+ "target node id."
217
222
  )
218
223
 
219
224
  @staticmethod
@@ -224,25 +229,25 @@ class ITSExpand:
224
229
 
225
230
  1. Every atom with ``atom_map != 0`` is assigned to node ID
226
231
  ``atom_map``.
227
- 2. The final node IDs are exactly contiguous from ``1..N``.
232
+ 2. Every unmapped atom is assigned a unique positive node ID that does
233
+ not collide with preserved atom-map IDs.
228
234
 
229
235
  :param graph: Molecular side graph.
230
236
  :type graph: networkx.Graph
231
237
  :returns: Old-node to new-node mapping.
232
238
  :rtype: dict
233
- :raises ValueError: If atom-map values are duplicated or incompatible
234
- with contiguous node IDs.
239
+ :raises ValueError: If atom-map values are duplicated or non-positive.
235
240
  """
236
241
  n_nodes = graph.number_of_nodes()
237
242
  atom_maps = ITSExpand._nonzero_atom_maps(graph)
238
243
 
239
244
  ITSExpand._validate_unique_atom_maps(atom_maps)
240
- ITSExpand._validate_atom_maps_within_range(atom_maps, n_nodes)
245
+ ITSExpand._validate_positive_atom_maps(atom_maps)
241
246
 
242
247
  mapping, used_ids = ITSExpand._assign_mapped_nodes(graph)
243
248
  mapping = ITSExpand._assign_unmapped_nodes(graph, mapping, used_ids)
244
249
 
245
- ITSExpand._validate_contiguous_mapping(mapping, n_nodes)
250
+ ITSExpand._validate_complete_unique_mapping(mapping, n_nodes)
246
251
 
247
252
  return mapping
248
253
 
@@ -317,7 +322,9 @@ class ITSExpand:
317
322
  def reindex_side_graph_by_atom_map(graph):
318
323
  """Reindex a side graph so mapped atoms use ``atom_map`` as node ID.
319
324
 
320
- The returned graph keeps node IDs contiguous from ``1..N``.
325
+ The returned graph keeps mapped atom node IDs equal to their atom-map
326
+ values. Unmapped atom node IDs remain compact where possible, but may
327
+ become sparse to avoid collisions with preserved atom maps.
321
328
 
322
329
  This is useful because the reaction-center graph produced by
323
330
  ``ITSConstruction().ITSGraph(...)`` uses atom-map numbers as node IDs,
@@ -344,10 +351,10 @@ class ITSExpand:
344
351
 
345
352
  :param graph: Molecular side graph.
346
353
  :type graph: networkx.Graph
347
- :returns: Reindexed side graph with contiguous node IDs.
354
+ :returns: Reindexed side graph with preserved mapped node IDs.
348
355
  :rtype: networkx.Graph
349
356
  :raises ValueError: If atom-map numbers cannot be safely used as node
350
- IDs while preserving ``1..N`` indexing.
357
+ IDs.
351
358
  """
352
359
  mapping = ITSExpand._build_side_graph_reindex_mapping(graph)
353
360
  return ITSExpand._rebuild_graph_with_mapping(graph, mapping)
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from synkit.Graph.Mech.lwg_editor import LWGEditResult, LWGEditor, LWGStepReport
7
+
8
+ __all__ = ["LWGEditor", "LWGEditResult", "LWGStepReport"]
9
+
10
+
11
+ def __getattr__(name: str):
12
+ if name in __all__:
13
+ from synkit.Graph.Mech.lwg_editor import (
14
+ LWGEditResult,
15
+ LWGEditor,
16
+ LWGStepReport,
17
+ )
18
+
19
+ exports = {
20
+ "LWGEditor": LWGEditor,
21
+ "LWGEditResult": LWGEditResult,
22
+ "LWGStepReport": LWGStepReport,
23
+ }
24
+ return exports[name]
25
+
26
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,309 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ import networkx as nx
7
+ from rdkit import Chem
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ChargeRefresh:
12
+ """VE/NBE/B charge refresh report for one atom map.
13
+
14
+ :param atom_map: Refreshed atom map.
15
+ :type atom_map: int
16
+ :param node: NetworkX node corresponding to ``atom_map``.
17
+ :type node: Any
18
+ :param previous_charge: Charge before refresh.
19
+ :type previous_charge: int | float
20
+ :param refreshed_charge: Charge computed by ``VE - NBE - B``.
21
+ :type refreshed_charge: int | float
22
+ :param valence_electrons: Atom valence electron count used as ``VE``.
23
+ :type valence_electrons: float
24
+ :param nonbonding_electrons: Nonbonding electron count used as ``NBE``.
25
+ :type nonbonding_electrons: float
26
+ :param bond_electrons: Bond electron count used as ``B``.
27
+ :type bond_electrons: float
28
+ """
29
+
30
+ atom_map: int
31
+ node: Any
32
+ previous_charge: int | float
33
+ refreshed_charge: int | float
34
+ valence_electrons: float
35
+ nonbonding_electrons: float
36
+ bond_electrons: float
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class ChargeEdit:
41
+ """Incremental local charge edit for one atom map.
42
+
43
+ :param atom_map: Edited atom map.
44
+ :type atom_map: int
45
+ :param node: NetworkX node corresponding to ``atom_map``.
46
+ :type node: Any
47
+ :param delta: Applied formal-charge delta.
48
+ :type delta: int | float
49
+ :param previous_charge: Charge before the edit.
50
+ :type previous_charge: int | float
51
+ :param new_charge: Charge after the edit.
52
+ :type new_charge: int | float
53
+ """
54
+
55
+ atom_map: int
56
+ node: Any
57
+ delta: int | float
58
+ previous_charge: int | float
59
+ new_charge: int | float
60
+
61
+
62
+ def bond_order_sum(graph: nx.Graph, node: Any) -> float:
63
+ """Return the sigma-plus-pi bond-order sum around one node.
64
+
65
+ :param graph: Lewis graph.
66
+ :type graph: nx.Graph
67
+ :param node: Node whose incident bond orders should be summed.
68
+ :type node: Any
69
+ :returns: Sum of incident ``sigma_order + pi_order`` values.
70
+ :rtype: float
71
+ """
72
+ total = 0.0
73
+ for _, _, data in graph.edges(node, data=True):
74
+ total += float(data.get("sigma_order", 0.0)) + float(data.get("pi_order", 0.0))
75
+ return total
76
+
77
+
78
+ def recompute_charge(graph: nx.Graph, node: Any) -> int | float:
79
+ """Recompute formal charge from stored electron-state fields.
80
+
81
+ This diagnostic helper uses ``Formal Charge = VE - NBE - B``. The
82
+ :class:`synkit.Graph.Mech.lwg_editor.LWGEditor` production edit path uses
83
+ local charge deltas instead.
84
+
85
+ :param graph: Lewis graph.
86
+ :type graph: nx.Graph
87
+ :param node: Node whose charge should be recomputed.
88
+ :type node: Any
89
+ :returns: Recomputed formal charge.
90
+ :rtype: int | float
91
+ """
92
+ attrs = graph.nodes[node]
93
+ charge = (
94
+ float(attrs["valence_electrons"])
95
+ - nonbonding_electron_count(graph, node)
96
+ - bond_electron_count(graph, node)
97
+ )
98
+ return int(charge) if charge.is_integer() else charge
99
+
100
+
101
+ def nonbonding_electron_count(graph: nx.Graph, node: Any) -> float:
102
+ """Return NBE for one atom from stored lone-pair/radical fields.
103
+
104
+ :param graph: Lewis graph.
105
+ :type graph: nx.Graph
106
+ :param node: Node whose nonbonding electrons should be counted.
107
+ :type node: Any
108
+ :returns: ``2 * lone_pairs + radical``.
109
+ :rtype: float
110
+ """
111
+ attrs = graph.nodes[node]
112
+ return 2 * float(attrs.get("lone_pairs", 0)) + float(attrs.get("radical", 0))
113
+
114
+
115
+ def bond_electron_count(graph: nx.Graph, node: Any) -> float:
116
+ """Return B for one atom, including implicit hydrogen bonds.
117
+
118
+ :param graph: Lewis graph.
119
+ :type graph: nx.Graph
120
+ :param node: Node whose bonding electrons should be counted.
121
+ :type node: Any
122
+ :returns: ``hcount`` plus incident sigma/pi bond-order sum.
123
+ :rtype: float
124
+ """
125
+ attrs = graph.nodes[node]
126
+ return float(attrs.get("hcount", 0)) + bond_order_sum(graph, node)
127
+
128
+
129
+ def atom_map_to_node(graph: nx.Graph) -> dict[int, Any]:
130
+ """Build an atom-map to node lookup, ignoring unmapped atoms.
131
+
132
+ :param graph: Graph with node ``atom_map`` attributes.
133
+ :type graph: nx.Graph
134
+ :returns: Mapping from atom-map number to graph node.
135
+ :rtype: dict[int, Any]
136
+ :raises ValueError: If a nonzero atom map appears on multiple nodes.
137
+ """
138
+ lookup: dict[int, Any] = {}
139
+ duplicates: dict[int, list[Any]] = {}
140
+
141
+ for node, attrs in graph.nodes(data=True):
142
+ atom_map = attrs.get("atom_map", node)
143
+ if atom_map in (None, 0, "0"):
144
+ continue
145
+
146
+ atom_map_int = int(atom_map)
147
+ if atom_map_int in lookup:
148
+ duplicates.setdefault(atom_map_int, [lookup[atom_map_int]]).append(node)
149
+ continue
150
+ lookup[atom_map_int] = node
151
+
152
+ if duplicates:
153
+ raise ValueError(f"Duplicate atom maps in graph: {duplicates}")
154
+
155
+ return lookup
156
+
157
+
158
+ def refresh_changed_atom_charge(
159
+ graph: nx.Graph,
160
+ atom_maps: list[int] | tuple[int, ...] | set[int],
161
+ ) -> list[ChargeRefresh]:
162
+ """Refresh formal charges only for selected atom maps.
163
+
164
+ Uses ``Formal Charge = VE - NBE - B`` where ``B`` includes implicit
165
+ hydrogens stored as ``hcount`` plus incident sigma/pi order.
166
+
167
+ :param graph: Lewis graph to mutate.
168
+ :type graph: nx.Graph
169
+ :param atom_maps: Atom maps whose ``charge`` field should be recomputed.
170
+ :type atom_maps: list[int] | tuple[int, ...] | set[int]
171
+ :returns: Per-atom refresh reports.
172
+ :rtype: list[ChargeRefresh]
173
+ :raises ValueError: If any atom map is missing or lacks
174
+ ``valence_electrons``.
175
+ """
176
+ lookup = atom_map_to_node(graph)
177
+ reports: list[ChargeRefresh] = []
178
+
179
+ for atom_map in sorted({int(value) for value in atom_maps}):
180
+ if atom_map not in lookup:
181
+ raise ValueError(f"Atom map {atom_map} is missing from graph.")
182
+
183
+ node = lookup[atom_map]
184
+ attrs = graph.nodes[node]
185
+
186
+ if "valence_electrons" not in attrs:
187
+ raise ValueError(f"Atom map {atom_map} has no valence_electrons field.")
188
+
189
+ previous_charge = attrs.get("charge", 0)
190
+ refreshed_charge = recompute_charge(graph, node)
191
+ attrs["charge"] = refreshed_charge
192
+ attrs["bond_order_sum"] = bond_order_sum(graph, node)
193
+ attrs["recomputed_charge"] = refreshed_charge
194
+ attrs["charge_mismatch"] = False
195
+
196
+ reports.append(
197
+ ChargeRefresh(
198
+ atom_map=atom_map,
199
+ node=node,
200
+ previous_charge=previous_charge,
201
+ refreshed_charge=refreshed_charge,
202
+ valence_electrons=float(attrs["valence_electrons"]),
203
+ nonbonding_electrons=nonbonding_electron_count(graph, node),
204
+ bond_electrons=bond_electron_count(graph, node),
205
+ )
206
+ )
207
+
208
+ return reports
209
+
210
+
211
+ def change_atom_charge(
212
+ graph: nx.Graph,
213
+ atom_maps: list[int] | tuple[int, ...] | set[int],
214
+ *,
215
+ delta: int | float,
216
+ ) -> list[ChargeEdit]:
217
+ """Apply a local formal-charge delta to selected atom maps.
218
+
219
+ This is the charge update primitive used by
220
+ :class:`synkit.Graph.Mech.lwg_editor.LWGEditor`.
221
+
222
+ :param graph: Lewis graph to mutate.
223
+ :type graph: nx.Graph
224
+ :param atom_maps: Atom maps whose formal charge should be edited.
225
+ :type atom_maps: list[int] | tuple[int, ...] | set[int]
226
+ :param delta: Formal-charge delta to add to each selected atom.
227
+ :type delta: int | float
228
+ :returns: Per-atom charge edit reports.
229
+ :rtype: list[ChargeEdit]
230
+ :raises ValueError: If any atom map is missing.
231
+ """
232
+ lookup = atom_map_to_node(graph)
233
+ reports: list[ChargeEdit] = []
234
+
235
+ for atom_map in [int(value) for value in atom_maps]:
236
+ if atom_map not in lookup:
237
+ raise ValueError(f"Atom map {atom_map} is missing from graph.")
238
+
239
+ node = lookup[atom_map]
240
+ attrs = graph.nodes[node]
241
+ previous_charge = attrs.get("charge", 0)
242
+ new_charge = previous_charge + delta
243
+ if isinstance(new_charge, float) and new_charge.is_integer():
244
+ new_charge = int(new_charge)
245
+
246
+ attrs["charge"] = new_charge
247
+ reports.append(
248
+ ChargeEdit(
249
+ atom_map=atom_map,
250
+ node=node,
251
+ delta=delta,
252
+ previous_charge=previous_charge,
253
+ new_charge=new_charge,
254
+ )
255
+ )
256
+
257
+ return reports
258
+
259
+
260
+ def refresh_electron_fields(graph: nx.Graph, *, in_place: bool = False) -> nx.Graph:
261
+ """Refresh derived electron bookkeeping on a molecular graph.
262
+
263
+ The graph is expected to store scalar ``sigma_order`` and ``pi_order`` edge
264
+ fields plus node-level electron state. Presentation-facing ``order`` is not
265
+ rewritten here; RDKit reconstruction remains responsible for aromatic
266
+ re-perception at the product boundary.
267
+
268
+ :param graph: Lewis graph to refresh.
269
+ :type graph: nx.Graph
270
+ :param in_place: If ``True``, mutate ``graph`` directly.
271
+ :type in_place: bool
272
+ :returns: Graph with refreshed ``kekule_order``, ``bond_order_sum``,
273
+ ``recomputed_charge``, and ``charge_mismatch`` fields.
274
+ :rtype: nx.Graph
275
+ """
276
+ target = graph if in_place else graph.copy()
277
+
278
+ for _, _, data in target.edges(data=True):
279
+ sigma = float(data.get("sigma_order", 0.0))
280
+ pi = float(data.get("pi_order", 0.0))
281
+ data["kekule_order"] = sigma + pi
282
+
283
+ for node, attrs in target.nodes(data=True):
284
+ attrs["bond_order_sum"] = bond_order_sum(target, node)
285
+ if "valence_electrons" not in attrs:
286
+ continue
287
+ attrs["recomputed_charge"] = recompute_charge(target, node)
288
+ represented_charge = float(attrs.get("charge", 0))
289
+ attrs["charge_mismatch"] = represented_charge != attrs["recomputed_charge"]
290
+
291
+ return target
292
+
293
+
294
+ def graph_to_sanitized_kekule_mol(graph: nx.Graph) -> Chem.Mol:
295
+ """Reconstruct a product from ``kekule_order`` and let RDKit sanitize it.
296
+
297
+ :param graph: Lewis graph with ``kekule_order`` edge fields.
298
+ :type graph: nx.Graph
299
+ :returns: Sanitized RDKit molecule.
300
+ :rtype: Chem.Mol
301
+ """
302
+ from synkit.IO.graph_to_mol import GraphToMol
303
+
304
+ refreshed = refresh_electron_fields(graph)
305
+ return GraphToMol(edge_attributes={"order": "kekule_order"}).graph_to_mol(
306
+ refreshed,
307
+ sanitize=True,
308
+ use_h_count=True,
309
+ )