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,1152 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Any, Hashable, Iterable, Mapping, Optional, Sequence, Tuple
6
+ import logging
7
+
8
+ import networkx as nx
9
+
10
+ from .labels import build_edge_labels, build_node_labels
11
+ from .layout import compute_layout
12
+ from .palette import ColorPalette, get_palette
13
+ from .validation import CRNGraphInfo, validate_crn_graph
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class CRNStyle:
20
+ """
21
+ Visual style configuration for CRN plots.
22
+
23
+ The defaults are tuned for muted publication-style figures with straight
24
+ edges, no label boxes, moderate alpha, and restrained line widths.
25
+
26
+ :param figsize: Figure size in inches.
27
+ :type figsize: Tuple[float, float]
28
+ :param species_node_shape: Matplotlib marker shape for species nodes.
29
+ :type species_node_shape: str
30
+ :param rule_node_shape: Matplotlib marker shape for rule nodes.
31
+ :type rule_node_shape: str
32
+ :param species_node_size: Node size for species nodes.
33
+ :type species_node_size: int
34
+ :param rule_node_size: Node size for rule nodes.
35
+ :type rule_node_size: int
36
+ :param edge_width: Base width for regular edges.
37
+ :type edge_width: float
38
+ :param highlight_edge_width: Width for highlighted edges.
39
+ :type highlight_edge_width: float
40
+ :param reactant_style: Line style for reactant edges.
41
+ :type reactant_style: str
42
+ :param product_style: Line style for product edges.
43
+ :type product_style: str
44
+ :param other_style: Line style for untyped edges.
45
+ :type other_style: str
46
+ :param arrows: Whether to draw arrows on edges.
47
+ :type arrows: bool
48
+ :param arrowstyle: Matplotlib arrow style string.
49
+ :type arrowstyle: str
50
+ :param arrowsize: Arrow size for directed edges.
51
+ :type arrowsize: int
52
+ :param font_size: Default node label font size.
53
+ :type font_size: int
54
+ :param alpha: General alpha value retained for backward compatibility.
55
+ :type alpha: float
56
+ :param margins: Plot margins passed to the axes.
57
+ :type margins: float
58
+ :param show_label_boxes: Whether node labels should be drawn with a bbox.
59
+ :type show_label_boxes: bool
60
+ :param label_box_alpha: Alpha for label boxes.
61
+ :type label_box_alpha: float
62
+ :param curved_edges: Whether to draw curved edges.
63
+ :type curved_edges: bool
64
+ :param curve_radius: Radius used when curved edges are enabled.
65
+ :type curve_radius: float
66
+ :param scale_edge_width_by_stoich: Whether edge widths should scale by
67
+ stoichiometry.
68
+ :type scale_edge_width_by_stoich: bool
69
+ :param stoich_width_factor: Increment added per stoichiometric unit above 1.
70
+ :type stoich_width_factor: float
71
+ :param linewidths: Line width for normal node outlines.
72
+ :type linewidths: float
73
+ :param highlight_linewidths: Line width for highlighted node outlines.
74
+ :type highlight_linewidths: float
75
+ :param edge_alpha: Alpha value for edges.
76
+ :type edge_alpha: float
77
+ :param node_alpha: Alpha value for nodes.
78
+ :type node_alpha: float
79
+ :param show_node_outline: Whether node outlines should be visible.
80
+ :type show_node_outline: bool
81
+ :param label_fontweight: Font weight used for node labels.
82
+ :type label_fontweight: str
83
+ :param auto_reduce_labels_when_dense: Whether to reduce label font size for
84
+ dense graphs.
85
+ :type auto_reduce_labels_when_dense: bool
86
+ :param dense_node_threshold: Node threshold for classifying a graph as dense.
87
+ :type dense_node_threshold: int
88
+ :param dense_edge_threshold: Edge threshold for classifying a graph as dense.
89
+ :type dense_edge_threshold: int
90
+ :param dense_font_size: Reduced label font size used for dense graphs.
91
+ :type dense_font_size: int
92
+ """
93
+
94
+ figsize: Tuple[float, float] = (12.0, 7.0)
95
+
96
+ species_node_shape: str = "o"
97
+ rule_node_shape: str = "s"
98
+
99
+ species_node_size: int = 760
100
+ rule_node_size: int = 540
101
+
102
+ edge_width: float = 1.2
103
+ highlight_edge_width: float = 2.1
104
+
105
+ reactant_style: str = "dashed"
106
+ product_style: str = "solid"
107
+ other_style: str = "solid"
108
+
109
+ arrows: bool = True
110
+ arrowstyle: str = "-|>"
111
+ arrowsize: int = 12
112
+
113
+ font_size: int = 8
114
+ alpha: float = 0.98
115
+ margins: float = 0.12
116
+
117
+ show_label_boxes: bool = False
118
+ label_box_alpha: float = 0.0
119
+
120
+ curved_edges: bool = False
121
+ curve_radius: float = 0.0
122
+
123
+ scale_edge_width_by_stoich: bool = True
124
+ stoich_width_factor: float = 0.4
125
+
126
+ linewidths: float = 1.0
127
+ highlight_linewidths: float = 1.6
128
+
129
+ edge_alpha: float = 0.9
130
+ node_alpha: float = 1.0
131
+
132
+ show_node_outline: bool = True
133
+ label_fontweight: str = "regular"
134
+
135
+ auto_reduce_labels_when_dense: bool = True
136
+ dense_node_threshold: int = 45
137
+ dense_edge_threshold: int = 70
138
+ dense_font_size: int = 7
139
+
140
+
141
+ @dataclass
142
+ class CRNVis:
143
+ """
144
+ Visualization helper for chemical reaction networks.
145
+
146
+ :param graph: Directed CRN graph to visualize.
147
+ :type graph: nx.DiGraph
148
+ :param layout: Layout name passed to :func:`compute_layout`.
149
+ :type layout: str
150
+ :param species_label: Node attribute used for species labels.
151
+ :type species_label: str
152
+ :param rule_label: Node attribute used for rule labels.
153
+ :type rule_label: str
154
+ :param show_species_labels: Whether species labels should be shown.
155
+ :type show_species_labels: bool
156
+ :param show_rule_labels: Whether rule labels should be shown.
157
+ :type show_rule_labels: bool
158
+ :param font_size: Optional label font size override.
159
+ :type font_size: Optional[int]
160
+ :param max_label_chars: Optional maximum number of characters per label.
161
+ :type max_label_chars: Optional[int]
162
+ :param wrap_label_at: Optional wrapping width for labels.
163
+ :type wrap_label_at: Optional[int]
164
+ :param style: Style configuration object.
165
+ :type style: CRNStyle
166
+ :param palette: Palette instance or palette name.
167
+ :type palette: ColorPalette | str
168
+ :param palette_overrides: Optional keyword overrides applied to the palette.
169
+ :type palette_overrides: Optional[dict[str, str]]
170
+ :param rule_color_mode: Rule coloring mode.
171
+ :type rule_color_mode: str
172
+ :param rule_color_attr: Node attribute used when rule coloring mode is
173
+ ``"attribute"``.
174
+ :type rule_color_attr: Optional[str]
175
+ :param rule_cmap: Matplotlib colormap name used for rule categorical colors.
176
+ :type rule_cmap: str
177
+ :param species_color_mode: Species coloring mode.
178
+ :type species_color_mode: str
179
+ :param species_color_attr: Node attribute used when species coloring mode is
180
+ ``"attribute"``.
181
+ :type species_color_attr: Optional[str]
182
+ :param species_cmap: Matplotlib colormap name used for species categorical
183
+ colors.
184
+ :type species_cmap: str
185
+ :param node_color_overrides: Explicit per-node color overrides.
186
+ :type node_color_overrides: Optional[Mapping[Hashable, str]]
187
+ :param node_spacing: Spacing passed to supported layouts.
188
+ :type node_spacing: float
189
+ :param layer_spacing: Layer spacing passed to supported layouts.
190
+ :type layer_spacing: float
191
+ :param orientation: Orientation passed to supported layouts.
192
+ :type orientation: str
193
+ :param seed: Random seed passed to stochastic layouts.
194
+ :type seed: int
195
+ :param strict: Whether graph validation should be strict.
196
+ :type strict: bool
197
+ """
198
+
199
+ graph: nx.DiGraph
200
+ layout: str = "auto"
201
+
202
+ species_label: str = "label"
203
+ rule_label: str = "label"
204
+
205
+ show_species_labels: bool = True
206
+ show_rule_labels: bool = True
207
+
208
+ font_size: Optional[int] = None
209
+ max_label_chars: Optional[int] = 28
210
+ wrap_label_at: Optional[int] = None
211
+
212
+ style: CRNStyle = field(default_factory=CRNStyle)
213
+ palette: ColorPalette | str = "paper_sage"
214
+ palette_overrides: Optional[dict[str, str]] = None
215
+
216
+ rule_color_mode: str = "palette"
217
+ rule_color_attr: Optional[str] = None
218
+ rule_cmap: str = "Greys"
219
+
220
+ species_color_mode: str = "palette"
221
+ species_color_attr: Optional[str] = None
222
+ species_cmap: str = "Greys"
223
+
224
+ node_color_overrides: Optional[Mapping[Hashable, str]] = None
225
+
226
+ node_spacing: float = 1.35
227
+ layer_spacing: float = 2.5
228
+ orientation: str = "vertical"
229
+ seed: int = 0
230
+
231
+ strict: bool = True
232
+
233
+ _info: CRNGraphInfo = field(init=False, repr=False)
234
+
235
+ def __post_init__(self) -> None:
236
+ """Resolve palette configuration and validate the CRN graph.
237
+
238
+ :return: ``None``.
239
+ :rtype: None
240
+ """
241
+ if isinstance(self.palette, str):
242
+ overrides = self.palette_overrides or {}
243
+ self.palette = get_palette(self.palette, **overrides)
244
+ elif self.palette_overrides:
245
+ self.palette = self.palette.with_overrides(**self.palette_overrides)
246
+
247
+ self._info = validate_crn_graph(self.graph, strict=self.strict)
248
+
249
+ @property
250
+ def species_nodes(self) -> list[Hashable]:
251
+ """Return validated species nodes.
252
+
253
+ :return: Species node identifiers.
254
+ :rtype: list[Hashable]
255
+ """
256
+ return self._info.species_nodes
257
+
258
+ @property
259
+ def rule_nodes(self) -> list[Hashable]:
260
+ """Return validated rule nodes.
261
+
262
+ :return: Rule node identifiers.
263
+ :rtype: list[Hashable]
264
+ """
265
+ return self._info.rule_nodes
266
+
267
+ @property
268
+ def is_dag(self) -> bool:
269
+ """Return whether the graph is acyclic.
270
+
271
+ :return: ``True`` if the graph is a DAG, else ``False``.
272
+ :rtype: bool
273
+ """
274
+ return self._info.is_dag
275
+
276
+ def positions(self) -> dict[Hashable, tuple[float, float]]:
277
+ """Compute node positions for the current graph and layout settings.
278
+
279
+ :return: Mapping from node identifier to ``(x, y)`` coordinates.
280
+ :rtype: dict[Hashable, tuple[float, float]]
281
+ """
282
+ return compute_layout(
283
+ self.graph,
284
+ species_nodes=self.species_nodes,
285
+ rule_nodes=self.rule_nodes,
286
+ layout=self.layout,
287
+ node_spacing=self.node_spacing,
288
+ layer_spacing=self.layer_spacing,
289
+ seed=self.seed,
290
+ orientation=self.orientation,
291
+ )
292
+
293
+ def node_labels(self) -> dict[Hashable, str]:
294
+ """Build node labels for the current graph.
295
+
296
+ :return: Mapping from node identifier to rendered label text.
297
+ :rtype: dict[Hashable, str]
298
+ """
299
+ return build_node_labels(
300
+ self.graph,
301
+ species_nodes=self.species_nodes,
302
+ rule_nodes=self.rule_nodes,
303
+ species_label=self.species_label,
304
+ rule_label=self.rule_label,
305
+ show_species_labels=self.show_species_labels,
306
+ show_rule_labels=self.show_rule_labels,
307
+ max_chars=self.max_label_chars,
308
+ wrap_at=self.wrap_label_at,
309
+ )
310
+
311
+ def edge_labels(
312
+ self, *, mode: str = "none"
313
+ ) -> dict[tuple[Hashable, Hashable], str]:
314
+ """Build edge labels for the current graph.
315
+
316
+ :param mode: Labeling mode passed to :func:`build_edge_labels`.
317
+ :type mode: str
318
+ :return: Mapping from edge to rendered label text.
319
+ :rtype: dict[tuple[Hashable, Hashable], str]
320
+ """
321
+ return build_edge_labels(self.graph, mode=mode)
322
+
323
+ def strongly_connected_species(self) -> list[set[Hashable]]:
324
+ """Return strongly connected components containing at least one species node.
325
+
326
+ :return: Species-containing strongly connected components.
327
+ :rtype: list[set[Hashable]]
328
+ """
329
+ sccs: list[set[Hashable]] = []
330
+ for comp in nx.strongly_connected_components(self.graph):
331
+ if len(comp) <= 1:
332
+ continue
333
+ if any(self.graph.nodes[n].get("kind") == "species" for n in comp):
334
+ sccs.append(set(comp))
335
+ return sccs
336
+
337
+ def dense_graph(self) -> bool:
338
+ """Return whether the graph should be treated as dense.
339
+
340
+ :return: ``True`` if the graph exceeds node or edge thresholds.
341
+ :rtype: bool
342
+ """
343
+ return (
344
+ self.graph.number_of_nodes() >= self.style.dense_node_threshold
345
+ or self.graph.number_of_edges() >= self.style.dense_edge_threshold
346
+ )
347
+
348
+ def _partition_edges(
349
+ self,
350
+ ) -> tuple[
351
+ list[tuple[Hashable, Hashable]],
352
+ list[tuple[Hashable, Hashable]],
353
+ list[tuple[Hashable, Hashable]],
354
+ ]:
355
+ """Partition edges into reactant, product, and other groups.
356
+
357
+ :return: Three edge lists in the order reactant, product, other.
358
+ :rtype: tuple[list[tuple[Hashable, Hashable]], list[tuple[Hashable, Hashable]], list[tuple[Hashable, Hashable]]]
359
+ """
360
+ reactant_edges: list[tuple[Hashable, Hashable]] = []
361
+ product_edges: list[tuple[Hashable, Hashable]] = []
362
+ other_edges: list[tuple[Hashable, Hashable]] = []
363
+
364
+ for u, v, d in self.graph.edges(data=True):
365
+ role = d.get("role")
366
+ if role == "reactant":
367
+ reactant_edges.append((u, v))
368
+ elif role == "product":
369
+ product_edges.append((u, v))
370
+ else:
371
+ other_edges.append((u, v))
372
+ return reactant_edges, product_edges, other_edges
373
+
374
+ @staticmethod
375
+ def _normalize_nodes(nodes: Optional[Iterable[Hashable]]) -> set[Hashable]:
376
+ """Normalize an optional node iterable to a set.
377
+
378
+ :param nodes: Optional node iterable.
379
+ :type nodes: Optional[Iterable[Hashable]]
380
+ :return: Set of node identifiers.
381
+ :rtype: set[Hashable]
382
+ """
383
+ return set() if nodes is None else set(nodes)
384
+
385
+ @staticmethod
386
+ def _normalize_edges(
387
+ edges: Optional[Iterable[Tuple[Hashable, Hashable]]],
388
+ ) -> set[Tuple[Hashable, Hashable]]:
389
+ """Normalize an optional edge iterable to a set.
390
+
391
+ :param edges: Optional edge iterable.
392
+ :type edges: Optional[Iterable[Tuple[Hashable, Hashable]]]
393
+ :return: Set of edge tuples.
394
+ :rtype: set[Tuple[Hashable, Hashable]]
395
+ """
396
+ return set() if edges is None else set(edges)
397
+
398
+ def _edge_widths(
399
+ self,
400
+ edgelist: Sequence[Tuple[Hashable, Hashable]],
401
+ ) -> list[float]:
402
+ """Compute per-edge widths for the given edge list.
403
+
404
+ :param edgelist: Edges to evaluate.
405
+ :type edgelist: Sequence[Tuple[Hashable, Hashable]]
406
+ :return: Edge widths matching the order of ``edgelist``.
407
+ :rtype: list[float]
408
+ """
409
+ widths: list[float] = []
410
+ for u, v in edgelist:
411
+ base = self.style.edge_width
412
+ if self.style.scale_edge_width_by_stoich:
413
+ stoich = self.graph.edges[u, v].get("stoich", 1)
414
+ try:
415
+ stoich_val = float(stoich)
416
+ except Exception:
417
+ stoich_val = 1.0
418
+ base = (
419
+ base + max(0.0, stoich_val - 1.0) * self.style.stoich_width_factor
420
+ )
421
+ widths.append(base)
422
+ return widths
423
+
424
+ def _connectionstyle(self) -> str:
425
+ """Return the NetworkX/Matplotlib connection style string.
426
+
427
+ :return: Connection style string.
428
+ :rtype: str
429
+ """
430
+ if not self.style.curved_edges:
431
+ return "arc3,rad=0.0"
432
+ return f"arc3,rad={self.style.curve_radius}"
433
+
434
+ def _categorical_colors(self, values: list[Any], cmap_name: str) -> dict[Any, Any]:
435
+ """Map categorical values to colors from a colormap.
436
+
437
+ :param values: Values to color.
438
+ :type values: list[Any]
439
+ :param cmap_name: Matplotlib colormap name.
440
+ :type cmap_name: str
441
+ :return: Mapping from unique values to colors.
442
+ :rtype: dict[Any, Any]
443
+ """
444
+ import matplotlib.pyplot as plt
445
+
446
+ unique = list(dict.fromkeys(values))
447
+ cmap = plt.get_cmap(cmap_name, max(len(unique), 1))
448
+ return {val: cmap(i) for i, val in enumerate(unique)}
449
+
450
+ def _node_facecolors(self, nodes: Sequence[Hashable], *, kind: str) -> list[Any]:
451
+ """Compute face colors for the given node list.
452
+
453
+ :param nodes: Nodes to color.
454
+ :type nodes: Sequence[Hashable]
455
+ :param kind: Node kind, either ``"species"`` or ``"rule"``.
456
+ :type kind: str
457
+ :return: Face colors matching the order of ``nodes``.
458
+ :rtype: list[Any]
459
+ """
460
+ if self.node_color_overrides:
461
+ colors: list[Optional[str]] = []
462
+ for n in nodes:
463
+ colors.append(self.node_color_overrides.get(n))
464
+ else:
465
+ colors = [None] * len(nodes)
466
+
467
+ if kind == "species":
468
+ base = self.palette.species_fill
469
+ mode = self.species_color_mode
470
+ attr = self.species_color_attr
471
+ cmap_name = self.species_cmap
472
+ else:
473
+ base = self.palette.rule_fill
474
+ mode = self.rule_color_mode
475
+ attr = self.rule_color_attr
476
+ cmap_name = self.rule_cmap
477
+
478
+ if mode == "palette":
479
+ return [c if c is not None else base for c in colors]
480
+
481
+ if mode == "attribute":
482
+ if not attr:
483
+ raise ValueError(
484
+ f"{kind}_color_attr must be provided when "
485
+ f"{kind}_color_mode='attribute'."
486
+ )
487
+ values = [self.graph.nodes[n].get(attr, "<missing>") for n in nodes]
488
+ lut = self._categorical_colors(values, cmap_name)
489
+ return [
490
+ c if c is not None else lut[self.graph.nodes[n].get(attr, "<missing>")]
491
+ for c, n in zip(colors, nodes)
492
+ ]
493
+
494
+ if kind == "rule" and mode in {"rule_index", "step"}:
495
+ key = "rule_index" if mode == "rule_index" else "step"
496
+ values = [self.graph.nodes[n].get(key, "<missing>") for n in nodes]
497
+ lut = self._categorical_colors(values, cmap_name)
498
+ return [
499
+ c if c is not None else lut[self.graph.nodes[n].get(key, "<missing>")]
500
+ for c, n in zip(colors, nodes)
501
+ ]
502
+
503
+ if kind == "species" and mode == "component":
504
+ comp_map: dict[Hashable, int] = {}
505
+ und = self.graph.to_undirected()
506
+ for comp_idx, comp in enumerate(nx.connected_components(und)):
507
+ for n in comp:
508
+ comp_map[n] = comp_idx
509
+ values = [comp_map.get(n, -1) for n in nodes]
510
+ lut = self._categorical_colors(values, cmap_name)
511
+ return [
512
+ c if c is not None else lut[comp_map.get(n, -1)]
513
+ for c, n in zip(colors, nodes)
514
+ ]
515
+
516
+ raise ValueError(
517
+ f"Unsupported {kind}_color_mode={mode!r}. "
518
+ "Use 'palette'. Rules also support 'rule_index', 'step', or "
519
+ "'attribute'. Species also support 'component' or 'attribute'."
520
+ )
521
+
522
+ def subgraph(self, nodes: Iterable[Hashable]) -> "CRNVis":
523
+ """Return a new :class:`CRNVis` instance restricted to the given nodes.
524
+
525
+ :param nodes: Nodes to keep in the induced subgraph.
526
+ :type nodes: Iterable[Hashable]
527
+ :return: New visualization helper for the induced subgraph.
528
+ :rtype: CRNVis
529
+ """
530
+ sub = self.graph.subgraph(list(nodes)).copy()
531
+ return CRNVis(
532
+ sub,
533
+ layout=self.layout,
534
+ species_label=self.species_label,
535
+ rule_label=self.rule_label,
536
+ show_species_labels=self.show_species_labels,
537
+ show_rule_labels=self.show_rule_labels,
538
+ font_size=self.font_size,
539
+ max_label_chars=self.max_label_chars,
540
+ wrap_label_at=self.wrap_label_at,
541
+ style=self.style,
542
+ palette=self.palette,
543
+ rule_color_mode=self.rule_color_mode,
544
+ rule_color_attr=self.rule_color_attr,
545
+ rule_cmap=self.rule_cmap,
546
+ species_color_mode=self.species_color_mode,
547
+ species_color_attr=self.species_color_attr,
548
+ species_cmap=self.species_cmap,
549
+ node_color_overrides=self.node_color_overrides,
550
+ node_spacing=self.node_spacing,
551
+ layer_spacing=self.layer_spacing,
552
+ orientation=self.orientation,
553
+ seed=self.seed,
554
+ strict=self.strict,
555
+ )
556
+
557
+ def _resolve_positions(
558
+ self,
559
+ *,
560
+ auto_align_dense: bool,
561
+ ) -> dict[Hashable, tuple[float, float]]:
562
+ """Compute positions, optionally switching to automatic layout for dense graphs.
563
+
564
+ :param auto_align_dense: Whether to temporarily switch to ``"auto"``
565
+ layout for dense graphs.
566
+ :type auto_align_dense: bool
567
+ :return: Mapping from node identifier to ``(x, y)`` coordinates.
568
+ :rtype: dict[Hashable, tuple[float, float]]
569
+ """
570
+ orig_layout = self.layout
571
+ if auto_align_dense and self.layout != "auto" and self.dense_graph():
572
+ self.layout = "auto"
573
+ pos = self.positions()
574
+ self.layout = orig_layout
575
+ return pos
576
+
577
+ def _resolve_highlights(
578
+ self,
579
+ *,
580
+ highlight_nodes: Optional[Iterable[Hashable]],
581
+ highlight_edges: Optional[Iterable[Tuple[Hashable, Hashable]]],
582
+ highlight_cycles: bool,
583
+ ) -> tuple[set[Hashable], set[tuple[Hashable, Hashable]]]:
584
+ """Resolve highlighted nodes and edges.
585
+
586
+ :param highlight_nodes: Explicit nodes to highlight.
587
+ :type highlight_nodes: Optional[Iterable[Hashable]]
588
+ :param highlight_edges: Explicit edges to highlight.
589
+ :type highlight_edges: Optional[Iterable[Tuple[Hashable, Hashable]]]
590
+ :param highlight_cycles: Whether to also highlight cyclic components.
591
+ :type highlight_cycles: bool
592
+ :return: Highlighted nodes and highlighted edges.
593
+ :rtype: tuple[set[Hashable], set[tuple[Hashable, Hashable]]]
594
+ """
595
+ highlight_node_set = self._normalize_nodes(highlight_nodes)
596
+ highlight_edge_set = self._normalize_edges(highlight_edges)
597
+
598
+ if highlight_cycles:
599
+ for comp in self.strongly_connected_species():
600
+ highlight_node_set.update(comp)
601
+ for u, v in self.graph.edges():
602
+ if u in comp and v in comp:
603
+ highlight_edge_set.add((u, v))
604
+
605
+ return highlight_node_set, highlight_edge_set
606
+
607
+ def _partition_node_groups(
608
+ self,
609
+ highlight_node_set: set[Hashable],
610
+ ) -> tuple[list[Hashable], list[Hashable], list[Hashable], list[Hashable]]:
611
+ """Partition species and rule nodes into normal and highlighted groups.
612
+
613
+ :param highlight_node_set: Nodes marked for highlighting.
614
+ :type highlight_node_set: set[Hashable]
615
+ :return: Species normal, rule normal, species highlighted, rule highlighted.
616
+ :rtype: tuple[list[Hashable], list[Hashable], list[Hashable], list[Hashable]]
617
+ """
618
+ species_normal = [n for n in self.species_nodes if n not in highlight_node_set]
619
+ rule_normal = [n for n in self.rule_nodes if n not in highlight_node_set]
620
+ species_highlight = [n for n in self.species_nodes if n in highlight_node_set]
621
+ rule_highlight = [n for n in self.rule_nodes if n in highlight_node_set]
622
+ return species_normal, rule_normal, species_highlight, rule_highlight
623
+
624
+ def _partition_highlighted_edges(
625
+ self,
626
+ highlight_edge_set: set[tuple[Hashable, Hashable]],
627
+ ) -> tuple[
628
+ list[tuple[Hashable, Hashable]],
629
+ list[tuple[Hashable, Hashable]],
630
+ list[tuple[Hashable, Hashable]],
631
+ list[tuple[Hashable, Hashable]],
632
+ ]:
633
+ """Partition edges into normal and highlighted groups.
634
+
635
+ :param highlight_edge_set: Edges marked for highlighting.
636
+ :type highlight_edge_set: set[tuple[Hashable, Hashable]]
637
+ :return: Reactant normal, product normal, other normal, highlighted edges.
638
+ :rtype: tuple[list[tuple[Hashable, Hashable]], list[tuple[Hashable, Hashable]],
639
+ list[tuple[Hashable, Hashable]], list[tuple[Hashable, Hashable]]]
640
+ """
641
+ reactant_edges, product_edges, other_edges = self._partition_edges()
642
+ reactant_normal = [e for e in reactant_edges if e not in highlight_edge_set]
643
+ product_normal = [e for e in product_edges if e not in highlight_edge_set]
644
+ other_normal = [e for e in other_edges if e not in highlight_edge_set]
645
+ highlighted_edges = list(highlight_edge_set)
646
+ return reactant_normal, product_normal, other_normal, highlighted_edges
647
+
648
+ def _node_edgecolors(self, *, kind: str) -> str:
649
+ """Return the node outline color for the given kind.
650
+
651
+ :param kind: Node kind, either ``"species"`` or ``"rule"``.
652
+ :type kind: str
653
+ :return: Edge color string.
654
+ :rtype: str
655
+ """
656
+ if not self.style.show_node_outline:
657
+ return "none"
658
+ return (
659
+ self.palette.species_edge if kind == "species" else self.palette.rule_edge
660
+ )
661
+
662
+ def _draw_node_group(
663
+ self,
664
+ ax: Any,
665
+ pos: Mapping[Hashable, tuple[float, float]],
666
+ *,
667
+ nodes: Sequence[Hashable],
668
+ kind: str,
669
+ node_color: Sequence[Any],
670
+ highlighted: bool,
671
+ ) -> None:
672
+ """Draw a single node group.
673
+
674
+ :param ax: Matplotlib axes.
675
+ :type ax: Any
676
+ :param pos: Node positions.
677
+ :type pos: Mapping[Hashable, tuple[float, float]]
678
+ :param nodes: Nodes to draw.
679
+ :type nodes: Sequence[Hashable]
680
+ :param kind: Node kind, either ``"species"`` or ``"rule"``.
681
+ :type kind: str
682
+ :param node_color: Face colors for the nodes.
683
+ :type node_color: Sequence[Any]
684
+ :param highlighted: Whether highlight linewidths should be used.
685
+ :type highlighted: bool
686
+ :return: ``None``.
687
+ :rtype: None
688
+ """
689
+ if not nodes:
690
+ return
691
+
692
+ is_species = kind == "species"
693
+ nx.draw_networkx_nodes(
694
+ self.graph,
695
+ pos,
696
+ nodelist=list(nodes),
697
+ node_shape=(
698
+ self.style.species_node_shape
699
+ if is_species
700
+ else self.style.rule_node_shape
701
+ ),
702
+ node_size=(
703
+ self.style.species_node_size
704
+ if is_species
705
+ else self.style.rule_node_size
706
+ ),
707
+ node_color=list(node_color),
708
+ edgecolors=self._node_edgecolors(kind=kind),
709
+ linewidths=(
710
+ self.style.highlight_linewidths
711
+ if highlighted
712
+ else self.style.linewidths
713
+ ),
714
+ alpha=self.style.node_alpha,
715
+ ax=ax,
716
+ )
717
+
718
+ def _common_edge_draw_kwargs(self, ax: Any) -> dict[str, Any]:
719
+ """Return common edge drawing keyword arguments.
720
+
721
+ :param ax: Matplotlib axes.
722
+ :type ax: Any
723
+ :return: Shared edge drawing keyword arguments.
724
+ :rtype: dict[str, Any]
725
+ """
726
+ return dict(
727
+ arrows=self.style.arrows,
728
+ arrowstyle=self.style.arrowstyle,
729
+ arrowsize=self.style.arrowsize,
730
+ alpha=self.style.edge_alpha,
731
+ ax=ax,
732
+ connectionstyle=self._connectionstyle(),
733
+ )
734
+
735
+ def _draw_edge_group(
736
+ self,
737
+ ax: Any,
738
+ pos: Mapping[Hashable, tuple[float, float]],
739
+ *,
740
+ edgelist: Sequence[tuple[Hashable, Hashable]],
741
+ style: str,
742
+ edge_color: Any,
743
+ width: float | Sequence[float],
744
+ ) -> None:
745
+ """Draw a single edge group.
746
+
747
+ :param ax: Matplotlib axes.
748
+ :type ax: Any
749
+ :param pos: Node positions.
750
+ :type pos: Mapping[Hashable, tuple[float, float]]
751
+ :param edgelist: Edges to draw.
752
+ :type edgelist: Sequence[tuple[Hashable, Hashable]]
753
+ :param style: Line style.
754
+ :type style: str
755
+ :param edge_color: Edge color.
756
+ :type edge_color: Any
757
+ :param width: Edge width or per-edge widths.
758
+ :type width: float | Sequence[float]
759
+ :return: ``None``.
760
+ :rtype: None
761
+ """
762
+ if not edgelist:
763
+ return
764
+
765
+ nx.draw_networkx_edges(
766
+ self.graph,
767
+ pos,
768
+ edgelist=list(edgelist),
769
+ style=style,
770
+ edge_color=edge_color,
771
+ width=width,
772
+ **self._common_edge_draw_kwargs(ax),
773
+ )
774
+
775
+ def _effective_font_size(self) -> int:
776
+ """Return the effective node label font size.
777
+
778
+ :return: Effective font size after applying dense-graph adjustment.
779
+ :rtype: int
780
+ """
781
+ effective_font = (
782
+ self.style.font_size if self.font_size is None else self.font_size
783
+ )
784
+ if self.style.auto_reduce_labels_when_dense and self.dense_graph():
785
+ effective_font = min(effective_font, self.style.dense_font_size)
786
+ return effective_font
787
+
788
+ def _label_draw_kwargs(self, ax: Any) -> dict[str, Any]:
789
+ """Return node label drawing keyword arguments.
790
+
791
+ :param ax: Matplotlib axes.
792
+ :type ax: Any
793
+ :return: Label drawing keyword arguments.
794
+ :rtype: dict[str, Any]
795
+ """
796
+ text_kwargs: dict[str, Any] = dict(
797
+ font_size=self._effective_font_size(),
798
+ font_color=self.palette.label_text,
799
+ font_weight=self.style.label_fontweight,
800
+ ax=ax,
801
+ )
802
+
803
+ if self.style.show_label_boxes:
804
+ text_kwargs["bbox"] = dict(
805
+ facecolor=self.palette.background,
806
+ edgecolor="none",
807
+ alpha=self.style.label_box_alpha,
808
+ boxstyle="round,pad=0.12",
809
+ )
810
+ return text_kwargs
811
+
812
+ def _draw_labels(
813
+ self,
814
+ ax: Any,
815
+ pos: Mapping[Hashable, tuple[float, float]],
816
+ labels: Mapping[Hashable, str],
817
+ ) -> None:
818
+ """Draw node labels.
819
+
820
+ :param ax: Matplotlib axes.
821
+ :type ax: Any
822
+ :param pos: Node positions.
823
+ :type pos: Mapping[Hashable, tuple[float, float]]
824
+ :param labels: Node labels.
825
+ :type labels: Mapping[Hashable, str]
826
+ :return: ``None``.
827
+ :rtype: None
828
+ """
829
+ nx.draw_networkx_labels(
830
+ self.graph,
831
+ pos,
832
+ labels=labels,
833
+ **self._label_draw_kwargs(ax),
834
+ )
835
+
836
+ def _draw_edge_labels(
837
+ self,
838
+ ax: Any,
839
+ pos: Mapping[Hashable, tuple[float, float]],
840
+ *,
841
+ edge_label_mode: str,
842
+ ) -> None:
843
+ """Draw edge labels if requested.
844
+
845
+ :param ax: Matplotlib axes.
846
+ :type ax: Any
847
+ :param pos: Node positions.
848
+ :type pos: Mapping[Hashable, tuple[float, float]]
849
+ :param edge_label_mode: Edge label mode.
850
+ :type edge_label_mode: str
851
+ :return: ``None``.
852
+ :rtype: None
853
+ """
854
+ if edge_label_mode == "none":
855
+ return
856
+
857
+ edge_labels = self.edge_labels(mode=edge_label_mode)
858
+ if not edge_labels:
859
+ return
860
+
861
+ nx.draw_networkx_edge_labels(
862
+ self.graph,
863
+ pos,
864
+ edge_labels=edge_labels,
865
+ font_size=max(5, self._effective_font_size() - 1),
866
+ font_color=self.palette.node_index_text,
867
+ bbox=None,
868
+ ax=ax,
869
+ )
870
+
871
+ def _set_title(self, ax: Any, title: Optional[str]) -> None:
872
+ """Set the plot title if provided.
873
+
874
+ :param ax: Matplotlib axes.
875
+ :type ax: Any
876
+ :param title: Optional title.
877
+ :type title: Optional[str]
878
+ :return: ``None``.
879
+ :rtype: None
880
+ """
881
+ if not title:
882
+ return
883
+ suffix = "" if self.is_dag else " (cyclic CRN)"
884
+ ax.set_title(f"{title}{suffix}", color=self.palette.title_text)
885
+
886
+ def _draw_legend(self, ax: Any) -> None:
887
+ """Draw the default legend.
888
+
889
+ :param ax: Matplotlib axes.
890
+ :type ax: Any
891
+ :return: ``None``.
892
+ :rtype: None
893
+ """
894
+ from matplotlib.lines import Line2D
895
+
896
+ handles = [
897
+ Line2D(
898
+ [0],
899
+ [0],
900
+ marker=self.style.species_node_shape,
901
+ color="w",
902
+ label="Species",
903
+ markerfacecolor=self.palette.species_fill,
904
+ markeredgecolor=self.palette.species_edge,
905
+ markersize=8,
906
+ ),
907
+ Line2D(
908
+ [0],
909
+ [0],
910
+ marker=self.style.rule_node_shape,
911
+ color="w",
912
+ label="Rule",
913
+ markerfacecolor=self.palette.rule_fill,
914
+ markeredgecolor=self.palette.rule_edge,
915
+ markersize=8,
916
+ ),
917
+ Line2D(
918
+ [0],
919
+ [0],
920
+ color=self.palette.reactant_edge,
921
+ linestyle=self.style.reactant_style,
922
+ label="Reactant edge",
923
+ ),
924
+ Line2D(
925
+ [0],
926
+ [0],
927
+ color=self.palette.product_edge,
928
+ linestyle=self.style.product_style,
929
+ label="Product edge",
930
+ ),
931
+ ]
932
+ leg = ax.legend(handles=handles, loc="best", frameon=False)
933
+ for txt in leg.get_texts():
934
+ txt.set_color(self.palette.legend_text)
935
+
936
+ def _finalize_axes(
937
+ self,
938
+ fig: Any,
939
+ ax: Any,
940
+ *,
941
+ hide_axis: bool,
942
+ ) -> None:
943
+ """Finalize axes appearance.
944
+
945
+ :param fig: Matplotlib figure.
946
+ :type fig: Any
947
+ :param ax: Matplotlib axes.
948
+ :type ax: Any
949
+ :param hide_axis: Whether to hide the axes.
950
+ :type hide_axis: bool
951
+ :return: ``None``.
952
+ :rtype: None
953
+ """
954
+ ax.margins(self.style.margins)
955
+ if hide_axis:
956
+ ax.set_axis_off()
957
+ fig.tight_layout()
958
+
959
+ def draw(
960
+ self,
961
+ ax: Any = None,
962
+ *,
963
+ title: Optional[str] = None,
964
+ show: bool = False,
965
+ with_legend: bool = True,
966
+ edge_label_mode: str = "none",
967
+ highlight_nodes: Optional[Iterable[Hashable]] = None,
968
+ highlight_edges: Optional[Iterable[Tuple[Hashable, Hashable]]] = None,
969
+ highlight_cycles: bool = False,
970
+ hide_axis: bool = True,
971
+ auto_align_dense: bool = False,
972
+ ) -> tuple[Any, Any, dict[Hashable, tuple[float, float]]]:
973
+ """Draw the CRN.
974
+
975
+ :param ax: Existing Matplotlib axes. If ``None``, a new figure and axes
976
+ are created.
977
+ :type ax: Any
978
+ :param title: Optional plot title.
979
+ :type title: Optional[str]
980
+ :param show: Whether to call :func:`matplotlib.pyplot.show`.
981
+ :type show: bool
982
+ :param with_legend: Whether to draw the default legend.
983
+ :type with_legend: bool
984
+ :param edge_label_mode: Edge label mode passed to :meth:`edge_labels`.
985
+ :type edge_label_mode: str
986
+ :param highlight_nodes: Optional nodes to highlight.
987
+ :type highlight_nodes: Optional[Iterable[Hashable]]
988
+ :param highlight_edges: Optional edges to highlight.
989
+ :type highlight_edges: Optional[Iterable[Tuple[Hashable, Hashable]]]
990
+ :param highlight_cycles: Whether to highlight species-containing strongly
991
+ connected components.
992
+ :type highlight_cycles: bool
993
+ :param hide_axis: Whether to hide axes decoration.
994
+ :type hide_axis: bool
995
+ :param auto_align_dense: Whether to temporarily switch to automatic
996
+ layout for dense graphs.
997
+ :type auto_align_dense: bool
998
+ :return: Figure, axes, and node positions.
999
+ :rtype: tuple[Any, Any, dict[Hashable, tuple[float, float]]]
1000
+ """
1001
+ import matplotlib.pyplot as plt
1002
+
1003
+ pos = self._resolve_positions(auto_align_dense=auto_align_dense)
1004
+ labels = self.node_labels()
1005
+
1006
+ if ax is None:
1007
+ fig, ax = plt.subplots(figsize=self.style.figsize)
1008
+ else:
1009
+ fig = ax.figure
1010
+
1011
+ fig.patch.set_facecolor(self.palette.background)
1012
+ ax.set_facecolor(self.palette.background)
1013
+
1014
+ highlight_node_set, highlight_edge_set = self._resolve_highlights(
1015
+ highlight_nodes=highlight_nodes,
1016
+ highlight_edges=highlight_edges,
1017
+ highlight_cycles=highlight_cycles,
1018
+ )
1019
+
1020
+ species_normal, rule_normal, species_highlight, rule_highlight = (
1021
+ self._partition_node_groups(highlight_node_set)
1022
+ )
1023
+ reactant_normal, product_normal, other_normal, highlighted_edges = (
1024
+ self._partition_highlighted_edges(highlight_edge_set)
1025
+ )
1026
+
1027
+ self._draw_node_group(
1028
+ ax,
1029
+ pos,
1030
+ nodes=species_normal,
1031
+ kind="species",
1032
+ node_color=self._node_facecolors(species_normal, kind="species"),
1033
+ highlighted=False,
1034
+ )
1035
+ self._draw_node_group(
1036
+ ax,
1037
+ pos,
1038
+ nodes=rule_normal,
1039
+ kind="rule",
1040
+ node_color=self._node_facecolors(rule_normal, kind="rule"),
1041
+ highlighted=False,
1042
+ )
1043
+ self._draw_node_group(
1044
+ ax,
1045
+ pos,
1046
+ nodes=species_highlight,
1047
+ kind="species",
1048
+ node_color=[self.palette.highlight_node] * len(species_highlight),
1049
+ highlighted=True,
1050
+ )
1051
+ self._draw_node_group(
1052
+ ax,
1053
+ pos,
1054
+ nodes=rule_highlight,
1055
+ kind="rule",
1056
+ node_color=[self.palette.highlight_node] * len(rule_highlight),
1057
+ highlighted=True,
1058
+ )
1059
+
1060
+ self._draw_edge_group(
1061
+ ax,
1062
+ pos,
1063
+ edgelist=reactant_normal,
1064
+ style=self.style.reactant_style,
1065
+ edge_color=self.palette.reactant_edge,
1066
+ width=self._edge_widths(reactant_normal),
1067
+ )
1068
+ self._draw_edge_group(
1069
+ ax,
1070
+ pos,
1071
+ edgelist=product_normal,
1072
+ style=self.style.product_style,
1073
+ edge_color=self.palette.product_edge,
1074
+ width=self._edge_widths(product_normal),
1075
+ )
1076
+ self._draw_edge_group(
1077
+ ax,
1078
+ pos,
1079
+ edgelist=other_normal,
1080
+ style=self.style.other_style,
1081
+ edge_color=self.palette.other_edge,
1082
+ width=self._edge_widths(other_normal),
1083
+ )
1084
+ self._draw_edge_group(
1085
+ ax,
1086
+ pos,
1087
+ edgelist=highlighted_edges,
1088
+ style="solid",
1089
+ edge_color=self.palette.highlight_edge,
1090
+ width=self.style.highlight_edge_width,
1091
+ )
1092
+
1093
+ self._draw_labels(ax, pos, labels)
1094
+ self._draw_edge_labels(ax, pos, edge_label_mode=edge_label_mode)
1095
+ self._set_title(ax, title)
1096
+
1097
+ if with_legend:
1098
+ self._draw_legend(ax)
1099
+
1100
+ self._finalize_axes(fig, ax, hide_axis=hide_axis)
1101
+
1102
+ if show:
1103
+ plt.show()
1104
+
1105
+ return fig, ax, pos
1106
+
1107
+ def save(
1108
+ self,
1109
+ path: str | Path,
1110
+ *,
1111
+ dpi: int = 300,
1112
+ bbox_inches: str = "tight",
1113
+ **draw_kwargs: Any,
1114
+ ) -> Path:
1115
+ """Draw and save the CRN figure.
1116
+
1117
+ :param path: Output file path.
1118
+ :type path: str | Path
1119
+ :param dpi: Figure DPI.
1120
+ :type dpi: int
1121
+ :param bbox_inches: Bounding box mode passed to ``savefig``.
1122
+ :type bbox_inches: str
1123
+ :param draw_kwargs: Additional keyword arguments passed to :meth:`draw`.
1124
+ :type draw_kwargs: Any
1125
+ :return: Saved output path.
1126
+ :rtype: Path
1127
+ """
1128
+ path = Path(path)
1129
+ fig, _, _ = self.draw(**draw_kwargs)
1130
+ fig.savefig(
1131
+ path,
1132
+ dpi=dpi,
1133
+ bbox_inches=bbox_inches,
1134
+ facecolor=fig.get_facecolor(),
1135
+ )
1136
+ return path
1137
+
1138
+
1139
+ def draw_crn(
1140
+ graph: nx.DiGraph,
1141
+ **kwargs: Any,
1142
+ ) -> tuple[Any, Any, dict[Hashable, tuple[float, float]]]:
1143
+ """Convenience wrapper around :class:`CRNVis`.
1144
+
1145
+ :param graph: Directed CRN graph to visualize.
1146
+ :type graph: nx.DiGraph
1147
+ :param kwargs: Keyword arguments forwarded to :class:`CRNVis`.
1148
+ :type kwargs: Any
1149
+ :return: Figure, axes, and node positions.
1150
+ :rtype: tuple[Any, Any, dict[Hashable, tuple[float, float]]]
1151
+ """
1152
+ return CRNVis(graph=graph, **kwargs).draw()