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,114 @@
1
+ import re
2
+ from rdkit import Chem
3
+ from rdkit.Chem import SanitizeFlags
4
+ from typing import Tuple, Optional
5
+
6
+
7
+ class RadWC:
8
+ """Static utility for appending wildcard dummy atoms ``[*]`` with atom-map
9
+ indices to all radical centers **in the product block** of a reaction SMILES.
10
+
11
+ - Reactant and agent blocks are not modified.
12
+ - Only atoms in the product with unpaired electrons are considered.
13
+ - Each product radical gets a new ``[*:N]`` with a unique map number.
14
+
15
+ .. rubric:: Example
16
+
17
+ >>> rxn = '[CH2:1][OH:2]>>[CH2:1][O:2]'
18
+ >>> RadWC.transform(rxn)
19
+ '[CH2:1][OH:2]>>[CH2:1][O:2]'
20
+ >>> rxn2 = '[CH2:1][OH:2]>>[CH:1].[OH:2]'
21
+ >>> RadWC.transform(rxn2)
22
+ '[CH2:1][OH:2]>>[CH:1]([*:3]).[OH:2]'
23
+ """
24
+
25
+ @staticmethod
26
+ def transform(rxn_smiles: str, start_map: Optional[int] = None) -> str:
27
+ """Add [*] wildcards (with atom-map index) to every radical in the
28
+ product block of the input reaction SMILES.
29
+
30
+ :param rxn_smiles: Reaction SMILES, 2 or 3 blocks (R>>P or R>A>P).
31
+ :type rxn_smiles: str
32
+ :param start_map: Optional; first atom-map index for wildcards.
33
+ :type start_map: int or None
34
+ :return: Modified reaction SMILES with product wildcards.
35
+ :rtype: str
36
+ :raises ValueError: On parse error or invalid input.
37
+
38
+ .. rubric:: Example
39
+
40
+ >>> RadWC.transform('[CH2:1][OH:2]>>[CH:1].[OH:2]')
41
+ '[CH2:1][OH:2]>>[CH:1]([*:3]).[OH:2]'
42
+ """
43
+ react_blk, agents_blk, prod_blk = RadWC._split_reaction(rxn_smiles)
44
+ # Determine atom-map to use for wildcards
45
+ existing = [int(n) for n in re.findall(r":(\d+)", rxn_smiles)]
46
+ next_map = (
47
+ start_map if start_map is not None else (max(existing, default=0) + 1)
48
+ )
49
+
50
+ prod_frags = prod_blk.split(".") if prod_blk else []
51
+ new_prod_frags = []
52
+
53
+ keep_ops = SanitizeFlags.SANITIZE_ALL & ~SanitizeFlags.SANITIZE_ADJUSTHS
54
+
55
+ for smi in prod_frags:
56
+ if not smi:
57
+ continue
58
+ mol = Chem.MolFromSmiles(smi, sanitize=False)
59
+ if mol is None:
60
+ raise ValueError(f"Cannot parse product SMILES fragment: {smi}")
61
+ Chem.SanitizeMol(mol, sanitizeOps=keep_ops)
62
+ rw = Chem.RWMol(mol)
63
+ atoms = list(rw.GetAtoms())
64
+ changed = False
65
+ for atom in atoms:
66
+ rad = atom.GetNumRadicalElectrons()
67
+ if rad > 0:
68
+ for _ in range(rad):
69
+ dummy = Chem.Atom(0)
70
+ dummy.SetAtomMapNum(next_map)
71
+ dummy.SetNoImplicit(True)
72
+ rw.AddAtom(dummy)
73
+ rw.AddBond(
74
+ atom.GetIdx(), rw.GetNumAtoms() - 1, Chem.BondType.SINGLE
75
+ )
76
+ next_map += 1
77
+ changed = True
78
+ if changed:
79
+ Chem.SanitizeMol(rw.GetMol(), sanitizeOps=keep_ops)
80
+ new_prod_frags.append(
81
+ Chem.MolToSmiles(rw.GetMol(), isomericSmiles=True, allHsExplicit=True)
82
+ )
83
+
84
+ prod_str = ".".join(new_prod_frags)
85
+ if agents_blk is None:
86
+ return f"{react_blk}>>{prod_str}"
87
+ return f"{react_blk}>{agents_blk}>{prod_str}"
88
+
89
+ @staticmethod
90
+ def _split_reaction(rxn: str) -> Tuple[str, Optional[str], str]:
91
+ """Split a reaction SMILES into (reactant, agent or None, product).
92
+
93
+ :param rxn: Reaction SMILES string.
94
+ :type rxn: str
95
+ :return: (reactant, agent, product) tuple (agent may be None).
96
+ :rtype: Tuple[str, Optional[str], str]
97
+ :raises ValueError: If the SMILES does not contain 2 or 3 '>'s.
98
+ """
99
+ parts = rxn.split(">")
100
+ if len(parts) == 2:
101
+ return parts[0], None, parts[1]
102
+ if len(parts) == 3:
103
+ return parts[0], parts[1], parts[2]
104
+ raise ValueError("Reaction SMILES must contain 2 or 3 '>' symbols")
105
+
106
+ @staticmethod
107
+ def describe():
108
+ """
109
+ Print a description and minimal example.
110
+ """
111
+ print(RadWC.__doc__)
112
+
113
+ def __repr__(self):
114
+ return "<RadWC: static radical-wildcard utility for product block only>"
@@ -0,0 +1,576 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Set, Tuple, Union
4
+ from collections import Counter
5
+ import logging
6
+ import numbers
7
+
8
+ import networkx as nx
9
+ from networkx.algorithms import isomorphism
10
+
11
+ GraphType = Union[nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
12
+
13
+ __all__ = ["WCMatcher", "WildcardPatternMatcher"]
14
+
15
+
16
+ class WCMatcher:
17
+ """
18
+ Wildcard-aware pattern→host matcher with subgraph regions for wildcard nodes.
19
+
20
+ Semantics
21
+ ---------
22
+ • The graph containing nodes with ``attrs[element_key] == wildcard_element``
23
+ is treated as the **pattern**.
24
+ • Wildcard nodes (``element == "*"`` by default) are **removed** from the
25
+ pattern when computing the *core isomorphism*.
26
+ • Only the **core** pattern nodes (non-wildcards) are matched against the
27
+ host using :class:`networkx.algorithms.isomorphism.GraphMatcher`.
28
+ • Wildcard nodes are treated as “don’t care” substituents attached to core
29
+ atoms. Their **exact number and structure are ignored** – we only ensure
30
+ that the core scaffold is present in the host.
31
+ • After a core mapping is found, each wildcard node is associated with a
32
+ **host subgraph region**, obtained as neighbours of its mapped anchor
33
+ nodes in the host.
34
+
35
+ Attribute matching
36
+ ------------------
37
+ Node attributes (for keys listed in :data:`node_attrs`):
38
+
39
+ • ``str`` and ``bool``: must match **exactly**.
40
+ • ``int`` and ``float``: pattern value ≤ host value (lower-bound semantics).
41
+ • Special handling for a ``"neighbors"`` attribute if present:
42
+ - Pattern neighbour list may contain wildcard entries (e.g. ``"*"``
43
+ for any element).
44
+ - Concrete neighbour labels in the pattern act as **lower bounds** on
45
+ host counts (multiset inclusion).
46
+ - Wildcard neighbour labels impose no constraint.
47
+
48
+ Edge attributes (for keys listed in :data:`edge_attrs`):
49
+
50
+ • Same typed semantics as node attributes (str/bool exact, numeric ≤).
51
+
52
+ Typical usage
53
+ -------------
54
+ .. code-block:: python
55
+
56
+ import networkx as nx
57
+ from synkit.Graph.Matcher.wc_matcher import WCMatcher
58
+
59
+ # Host: C-C-C chain
60
+ host = nx.path_graph(3)
61
+ nx.set_node_attributes(host, "C", "element")
62
+
63
+ # Pattern: C-C-* (wildcard substituent on the second carbon)
64
+ pattern = nx.path_graph(3)
65
+ nx.set_node_attributes(pattern, "C", "element")
66
+ pattern.nodes[2]["element"] = "*" # wildcard node
67
+
68
+ matcher = WCMatcher(
69
+ pattern,
70
+ host,
71
+ wildcard_element="*",
72
+ element_key="element",
73
+ node_attrs=["element"],
74
+ edge_attrs=[],
75
+ ).fit()
76
+
77
+ if matcher.is_match:
78
+ print("Core mapping:", matcher.core_mapping_without_wildcard_regions)
79
+ print("Wildcard regions:", matcher.wildcard_subgraph_mapping)
80
+
81
+ :param G1: First input graph.
82
+ :type G1: GraphType
83
+ :param G2: Second input graph.
84
+ :type G2: GraphType
85
+ :param wildcard_element: Node attribute value that denotes a wildcard
86
+ in the pattern. Defaults to ``"*"``
87
+ :type wildcard_element: str
88
+ :param element_key: Name of the node attribute storing the element /
89
+ atom type. Defaults to ``"element"``.
90
+ :type element_key: str
91
+ :param node_attrs: Node attribute keys to be checked in addition to
92
+ :data:`element_key`. If ``"neighbors"`` is included, neighbour
93
+ lists are compared with lower-bound / wildcard semantics.
94
+ :type node_attrs: Sequence[str] | None
95
+ :param edge_attrs: Edge attribute keys to be checked with typed
96
+ semantics.
97
+ :type edge_attrs: Sequence[str] | None
98
+ :param node_match: Optional additional node predicate, combined with
99
+ the default matcher. Signature:
100
+ ``(host_attr: dict, pattern_attr: dict) -> bool``.
101
+ :type node_match: Callable[[Dict[str, Any], Dict[str, Any]], bool] | None
102
+ :param edge_match: Optional additional edge predicate, combined with
103
+ the default matcher. Signature:
104
+ ``(host_attr: dict, pattern_attr: dict) -> bool``.
105
+ :type edge_match: Callable[[Dict[str, Any], Dict[str, Any]], bool] | None
106
+ :param logger: Optional logger for diagnostics.
107
+ :type logger: logging.Logger | None
108
+ """
109
+
110
+ # ------------------------------------------------------------------ init / basic props
111
+
112
+ def __init__(
113
+ self,
114
+ G1: GraphType,
115
+ G2: GraphType,
116
+ *,
117
+ wildcard_element: str = "*",
118
+ element_key: str = "element",
119
+ node_attrs: Optional[Sequence[str]] = None,
120
+ edge_attrs: Optional[Sequence[str]] = None,
121
+ node_match: Optional[Callable[[Dict[str, Any], Dict[str, Any]], bool]] = None,
122
+ edge_match: Optional[Callable[[Dict[str, Any], Dict[str, Any]], bool]] = None,
123
+ logger: Optional[logging.Logger] = None,
124
+ ) -> None:
125
+ self._logger = logger or logging.getLogger(__name__)
126
+ self._element_key = element_key
127
+ self._wildcard_element = wildcard_element
128
+ self._user_node_match = node_match
129
+ self._user_edge_match = edge_match
130
+ self._node_attrs = tuple(node_attrs or [])
131
+ self._edge_attrs = tuple(edge_attrs or [])
132
+
133
+ self._host: GraphType
134
+ self._pattern: GraphType
135
+ self._pattern_is_G1: bool
136
+
137
+ self._host, self._pattern, self._pattern_is_G1 = self._choose_host_pattern(
138
+ G1, G2
139
+ )
140
+
141
+ # Core mapping: pattern(non-wc node) -> host node
142
+ self._core_mapping: Dict[Any, Any] = {}
143
+ self._is_match: bool = False
144
+
145
+ def __repr__(self) -> str:
146
+ """String representation for debugging.
147
+
148
+ :return: Short summary including match status and node counts.
149
+ :rtype: str
150
+ """
151
+ status = "matched" if self._is_match else "unmatched"
152
+ return (
153
+ f"<WCMatcher status={status} "
154
+ f"n_pattern={self._pattern.number_of_nodes()} "
155
+ f"n_host={self._host.number_of_nodes()}>"
156
+ )
157
+
158
+ # ------------------------------------------------------------------ public API
159
+
160
+ def fit(self) -> "WCMatcher":
161
+ """Run the wildcard-aware core isomorphism search.
162
+
163
+ The method computes the **core** subgraph isomorphism (ignoring
164
+ wildcard nodes) and stores the mapping internally. Use
165
+ :attr:`is_match` and :attr:`core_mapping_without_wildcard_regions`
166
+ to inspect the result.
167
+
168
+ :return: Self, to allow fluent chaining.
169
+ :rtype: WCMatcher
170
+ """
171
+ pattern_core = self._build_pattern_core()
172
+
173
+ # Quick size check – if core is larger than host, no match is possible.
174
+ if (
175
+ pattern_core.number_of_nodes() > self._host.number_of_nodes()
176
+ or pattern_core.number_of_edges() > self._host.number_of_edges()
177
+ ):
178
+ self._logger.debug(
179
+ "WCMatcher: pattern core larger than host (no match possible)."
180
+ )
181
+ self._core_mapping = {}
182
+ self._is_match = False
183
+ return self
184
+
185
+ gm = self._make_graph_matcher(pattern_core)
186
+
187
+ for host_to_pattern_core in gm.subgraph_isomorphisms_iter():
188
+ # networkx gives host→pattern mapping; we want pattern→host
189
+ core_to_host = {p: h for h, p in host_to_pattern_core.items()}
190
+ self._core_mapping = core_to_host
191
+ self._is_match = True
192
+ self._logger.debug("WCMatcher: core match found: %s", core_to_host)
193
+ return self
194
+
195
+ self._logger.debug("WCMatcher: no core mapping found.")
196
+ self._core_mapping = {}
197
+ self._is_match = False
198
+ return self
199
+
200
+ @property
201
+ def is_match(self) -> bool:
202
+ """Whether a wildcard-compatible **core** mapping was found.
203
+
204
+ :return: ``True`` if the core pattern matches a subgraph of the host.
205
+ :rtype: bool
206
+ """
207
+ return self._is_match
208
+
209
+ @property
210
+ def pattern_graph(self) -> GraphType:
211
+ """Graph that was treated as the **pattern** (may contain wildcards).
212
+
213
+ :return: Pattern graph.
214
+ :rtype: GraphType
215
+ """
216
+ return self._pattern
217
+
218
+ @property
219
+ def host_graph(self) -> GraphType:
220
+ """Graph that was treated as the **host**.
221
+
222
+ :return: Host graph.
223
+ :rtype: GraphType
224
+ """
225
+ return self._host
226
+
227
+ @property
228
+ def core_mapping_without_wildcard_regions(self) -> Dict[Any, Any]:
229
+ """Mapping from **non-wildcard pattern nodes → host nodes**.
230
+
231
+ Wildcard nodes in the pattern are ignored in this mapping. This is the
232
+ clean "core" mapping without any enlargement due to wildcard regions.
233
+
234
+ :return: Mapping from pattern-core nodes to host nodes.
235
+ :rtype: dict[Any, Any]
236
+ """
237
+ return dict(self._core_mapping)
238
+
239
+ @property
240
+ def wildcard_subgraph_mapping(self) -> Dict[Any, Set[Any]]:
241
+ """Mapping from each **wildcard pattern node** to a set of host nodes
242
+ forming its wildcard subgraph region.
243
+
244
+ Construction heuristic
245
+ ----------------------
246
+ For each wildcard node ``w`` in the pattern:
247
+
248
+ 1. Collect its **non-wildcard neighbour(s)** in the pattern.
249
+ 2. Map those neighbours to host anchors using the core mapping.
250
+ 3. For each anchor ``h`` in the host, add all neighbours of ``h``
251
+ that are **not already used** by any core mapping.
252
+
253
+ .. rubric:: Notes
254
+
255
+ • If there is no core mapping (``is_match == False``), the result is
256
+ an empty dict.
257
+ • If a wildcard's anchors cannot be mapped (e.g. missing in core),
258
+ its region is an empty set.
259
+
260
+ :return: Mapping ``wildcard_pattern_node → set(host_nodes_in_region)``.
261
+ :rtype: dict[Any, set[Any]]
262
+ """
263
+ if not self._is_match:
264
+ return {}
265
+
266
+ wc_nodes = [
267
+ n
268
+ for n, data in self._pattern.nodes(data=True)
269
+ if data.get(self._element_key) == self._wildcard_element
270
+ ]
271
+
272
+ if not wc_nodes:
273
+ return {}
274
+
275
+ core_hosts: Set[Any] = set(self._core_mapping.values())
276
+ regions: Dict[Any, Set[Any]] = {}
277
+
278
+ for w in wc_nodes:
279
+ neigh = list(self._pattern.neighbors(w))
280
+ anchors = [
281
+ p
282
+ for p in neigh
283
+ if self._pattern.nodes[p].get(self._element_key)
284
+ != self._wildcard_element
285
+ and p in self._core_mapping
286
+ ]
287
+
288
+ region: Set[Any] = set()
289
+ for p in anchors:
290
+ h_anchor = self._core_mapping[p]
291
+ for h_nbr in self._host.neighbors(h_anchor):
292
+ if h_nbr not in core_hosts:
293
+ region.add(h_nbr)
294
+
295
+ regions[w] = region
296
+
297
+ return regions
298
+
299
+ @property
300
+ def help(self) -> str:
301
+ """Human-readable summary of the matcher behaviour.
302
+
303
+ :return: Description string summarising semantics and attributes.
304
+ :rtype: str
305
+ """
306
+ return (
307
+ "WCMatcher (WildcardPatternMatcher):\n"
308
+ " • pattern = graph with wildcard nodes (element == wildcard_element)\n"
309
+ " • host = other graph (or larger if both/none have wildcards)\n"
310
+ " • semantics:\n"
311
+ " - core match ignores wildcard nodes\n"
312
+ " - node attrs: str/bool exact, numeric pattern ≤ host\n"
313
+ " - neighbours: concrete labels are lower bounds; '*' is wildcard\n"
314
+ " - wildcard_subgraph_mapping gives host regions per wildcard node"
315
+ )
316
+
317
+ # ------------------------------------------------------------------ internals: host/pattern selection
318
+
319
+ def _choose_host_pattern(
320
+ self, G1: GraphType, G2: GraphType
321
+ ) -> Tuple[GraphType, GraphType, bool]:
322
+ """Decide which graph is host and which is pattern.
323
+
324
+ Preference
325
+ ----------
326
+ 1. Graph containing wildcard nodes is the pattern.
327
+ 2. If both (or neither) contain wildcards, the **smaller** graph is
328
+ treated as the pattern.
329
+
330
+ :param G1: First input graph.
331
+ :type G1: GraphType
332
+ :param G2: Second input graph.
333
+ :type G2: GraphType
334
+ :return: Tuple ``(host, pattern, pattern_is_G1)``.
335
+ :rtype: tuple[GraphType, GraphType, bool]
336
+ """
337
+ has_wc1 = self._graph_has_wildcard(G1)
338
+ has_wc2 = self._graph_has_wildcard(G2)
339
+
340
+ if has_wc1 and not has_wc2:
341
+ return G2, G1, True
342
+ if has_wc2 and not has_wc1:
343
+ return G1, G2, False
344
+
345
+ if G1.number_of_nodes() <= G2.number_of_nodes():
346
+ return G2, G1, True
347
+ return G1, G2, False
348
+
349
+ def _graph_has_wildcard(self, G: GraphType) -> bool:
350
+ """Check whether a graph contains at least one wildcard node.
351
+
352
+ :param G: Graph to inspect.
353
+ :type G: GraphType
354
+ :return: ``True`` if a wildcard node is present.
355
+ :rtype: bool
356
+ """
357
+ key = self._element_key
358
+ wc = self._wildcard_element
359
+ for _, data in G.nodes(data=True):
360
+ if data.get(key) == wc:
361
+ return True
362
+ return False
363
+
364
+ # ------------------------------------------------------------------ internals: pattern core construction
365
+
366
+ def _build_pattern_core(self) -> GraphType:
367
+ """Build the core pattern graph (non-wildcard nodes only).
368
+
369
+ Wildcard nodes (with ``element == wildcard_element``) are removed,
370
+ and the induced subgraph is returned.
371
+
372
+ :return: Core pattern graph without wildcard nodes.
373
+ :rtype: GraphType
374
+ """
375
+ key = self._element_key
376
+ wc = self._wildcard_element
377
+
378
+ core_nodes: Set[Any] = {
379
+ n for n, data in self._pattern.nodes(data=True) if data.get(key) != wc
380
+ }
381
+
382
+ return self._pattern.subgraph(core_nodes).copy()
383
+
384
+ # ------------------------------------------------------------------ internals: typed attribute comparison
385
+
386
+ def _typed_leq(self, host_val: Any, pat_val: Any) -> bool:
387
+ """Typed comparison of attribute values.
388
+
389
+ Rules
390
+ -----
391
+ • If ``pat_val`` is ``None``: no constraint → ``True``.
392
+ • If both values are numeric (int/float): require ``host_val >= pat_val``.
393
+ • If both are ``str`` or both are ``bool``: require equality.
394
+ • Otherwise: fall back to equality.
395
+
396
+ :param host_val: Attribute value from the host graph.
397
+ :type host_val: Any
398
+ :param pat_val: Attribute value from the pattern graph.
399
+ :type pat_val: Any
400
+ :return: ``True`` if ``host_val`` satisfies the pattern constraint.
401
+ :rtype: bool
402
+ """
403
+ if pat_val is None:
404
+ return True
405
+
406
+ if isinstance(host_val, numbers.Number) and isinstance(pat_val, numbers.Number):
407
+ return host_val >= pat_val
408
+
409
+ if isinstance(host_val, str) and isinstance(pat_val, str):
410
+ return host_val == pat_val
411
+ if isinstance(host_val, bool) and isinstance(pat_val, bool):
412
+ return host_val == pat_val
413
+
414
+ return host_val == pat_val
415
+
416
+ def _neighbors_compatible(
417
+ self,
418
+ host_neigh: Optional[Iterable[Any]],
419
+ pat_neigh: Optional[Iterable[Any]],
420
+ ) -> bool:
421
+ """Compare neighbour "element" lists with wildcard & lower-bound semantics.
422
+
423
+ • Pattern neighbour list may contain wildcard entries
424
+ (``self._wildcard_element``).
425
+ • Concrete labels in the pattern act as **lower bounds** on host counts:
426
+ ``count_host[label] >= count_pattern[label]``.
427
+ • Wildcards in the pattern impose no constraint on the host.
428
+
429
+ :param host_neigh: Neighbour labels in the host (e.g. list of element
430
+ symbols).
431
+ :type host_neigh: Iterable[Any] | None
432
+ :param pat_neigh: Neighbour labels in the pattern.
433
+ :type pat_neigh: Iterable[Any] | None
434
+ :return: ``True`` if host neighbours satisfy the pattern constraints.
435
+ :rtype: bool
436
+ """
437
+ if pat_neigh is None:
438
+ return True
439
+
440
+ wc = self._wildcard_element
441
+ host_neigh = list(host_neigh or [])
442
+ pat_neigh = list(pat_neigh)
443
+
444
+ pat_counts = Counter(lbl for lbl in pat_neigh if lbl != wc)
445
+ host_counts = Counter(host_neigh)
446
+
447
+ for lbl, p_cnt in pat_counts.items():
448
+ if host_counts[lbl] < p_cnt:
449
+ return False
450
+
451
+ return True
452
+
453
+ # ------------------------------------------------------------------ internals: attribute-based node / edge checks
454
+
455
+ def _node_attrs_ok(
456
+ self,
457
+ host_attr: Dict[str, Any],
458
+ pat_attr: Dict[str, Any],
459
+ *,
460
+ key: str,
461
+ wc: str,
462
+ ) -> bool:
463
+ """Check node-level attributes including element & neighbours.
464
+
465
+ :param host_attr: Host node attribute dictionary.
466
+ :type host_attr: dict[str, Any]
467
+ :param pat_attr: Pattern node attribute dictionary.
468
+ :type pat_attr: dict[str, Any]
469
+ :param key: Element attribute key (e.g. ``"element"``).
470
+ :type key: str
471
+ :param wc: Wildcard element value.
472
+ :type wc: str
473
+ :return: ``True`` if host node satisfies pattern node constraints.
474
+ :rtype: bool
475
+ """
476
+ host_el = host_attr.get(key)
477
+ pat_el = pat_attr.get(key)
478
+ if pat_el is not None and pat_el != wc:
479
+ if host_el != pat_el:
480
+ return False
481
+
482
+ for name in self._node_attrs:
483
+ if name == "neighbors":
484
+ if not self._neighbors_compatible(
485
+ host_attr.get(name), pat_attr.get(name)
486
+ ):
487
+ return False
488
+ else:
489
+ if not self._typed_leq(host_attr.get(name), pat_attr.get(name)):
490
+ return False
491
+
492
+ return True
493
+
494
+ def _edge_attrs_ok(
495
+ self,
496
+ host_attr: Dict[str, Any],
497
+ pat_attr: Dict[str, Any],
498
+ ) -> bool:
499
+ """Check edge-level attributes using typed semantics.
500
+
501
+ :param host_attr: Host edge attribute dictionary.
502
+ :type host_attr: dict[str, Any]
503
+ :param pat_attr: Pattern edge attribute dictionary.
504
+ :type pat_attr: dict[str, Any]
505
+ :return: ``True`` if host edge satisfies pattern edge constraints.
506
+ :rtype: bool
507
+ """
508
+ for name in self._edge_attrs:
509
+ if not self._typed_leq(host_attr.get(name), pat_attr.get(name)):
510
+ return False
511
+ return True
512
+
513
+ # ------------------------------------------------------------------ internals: GraphMatcher construction
514
+
515
+ def _make_graph_matcher(self, pattern_core: GraphType) -> Any:
516
+ """Build a NetworkX GraphMatcher for host vs. pattern_core.
517
+
518
+ Default behaviour
519
+ -----------------
520
+ • Node match:
521
+ - :data:`element_key`: exact, unless pattern element is
522
+ :data:`wildcard_element`.
523
+ - For keys in :attr:`_node_attrs`:
524
+ * If key == ``"neighbors"``: use neighbour lower-bound semantics.
525
+ * Else: typed comparison via :meth:`_typed_leq`.
526
+ - Then apply optional :data:`node_match` predicate if given.
527
+ • Edge match:
528
+ - For keys in :attr:`_edge_attrs`: typed comparison via
529
+ :meth:`_typed_leq`.
530
+ - Then optional :data:`edge_match` predicate if given.
531
+
532
+ :param pattern_core: Induced subgraph of the pattern on non-wildcard
533
+ nodes.
534
+ :type pattern_core: GraphType
535
+ :return: Instance of :class:`GraphMatcher` or
536
+ :class:`MultiGraphMatcher` depending on the host type.
537
+ :rtype: Any
538
+ """
539
+ key = self._element_key
540
+ wc = self._wildcard_element
541
+ user_node_match = self._user_node_match
542
+ user_edge_match = self._user_edge_match
543
+
544
+ def default_node_match(
545
+ host_attr: Dict[str, Any], pat_attr: Dict[str, Any]
546
+ ) -> bool:
547
+ if not self._node_attrs_ok(host_attr, pat_attr, key=key, wc=wc):
548
+ return False
549
+ if user_node_match is not None and not user_node_match(host_attr, pat_attr):
550
+ return False
551
+ return True
552
+
553
+ def default_edge_match(
554
+ host_attr: Dict[str, Any], pat_attr: Dict[str, Any]
555
+ ) -> bool:
556
+ if not self._edge_attrs_ok(host_attr, pat_attr):
557
+ return False
558
+ if user_edge_match is not None and not user_edge_match(host_attr, pat_attr):
559
+ return False
560
+ return True
561
+
562
+ if isinstance(self._host, (nx.MultiGraph, nx.MultiDiGraph)):
563
+ matcher_cls = isomorphism.MultiGraphMatcher
564
+ else:
565
+ matcher_cls = isomorphism.GraphMatcher
566
+
567
+ return matcher_cls(
568
+ self._host,
569
+ pattern_core,
570
+ node_match=default_node_match,
571
+ edge_match=default_edge_match,
572
+ )
573
+
574
+
575
+ # Backwards-compatible alias
576
+ WildcardPatternMatcher = WCMatcher