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,288 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from typing import Any
5
+
6
+ import networkx as nx
7
+ from networkx.algorithms.isomorphism import GraphMatcher
8
+
9
+ from .catalog import default_registry
10
+ from .model import FunctionalGroupMatch, FunctionalGroupPattern, FunctionalGroupRegistry
11
+ from .ring_system import AromaticRingSystemDetector
12
+
13
+
14
+ def _node_match(host: dict[str, Any], pattern: dict[str, Any]) -> bool:
15
+ element = pattern.get("element")
16
+ if element is not None:
17
+ allowed = element if isinstance(element, tuple) else (element,)
18
+ if host.get("element") not in allowed:
19
+ return False
20
+
21
+ for attr in ("aromatic", "charge", "radical", "in_ring"):
22
+ if attr in pattern and host.get(attr) != pattern[attr]:
23
+ return False
24
+
25
+ hcount = host.get("hcount", 0)
26
+ if "hcount_min" in pattern and hcount < pattern["hcount_min"]:
27
+ return False
28
+ if "hcount_max" in pattern and hcount > pattern["hcount_max"]:
29
+ return False
30
+ return True
31
+
32
+
33
+ def _edge_match(host: dict[str, Any], pattern: dict[str, Any]) -> bool:
34
+ if "order" in pattern and host.get("order") != pattern["order"]:
35
+ return False
36
+ if "aromatic" in pattern and host.get("aromatic") != pattern["aromatic"]:
37
+ return False
38
+ return True
39
+
40
+
41
+ class FunctionalGroupDetector:
42
+ """Detect functional groups from an input molecular ``nx.Graph``."""
43
+
44
+ def __init__(self, registry: FunctionalGroupRegistry | None = None) -> None:
45
+ self.registry = default_registry() if registry is None else registry
46
+ self.profile_counts: Counter[str] = Counter()
47
+
48
+ def raw_matches(
49
+ self,
50
+ graph: nx.Graph,
51
+ *,
52
+ include_internal: bool = False,
53
+ ) -> list[FunctionalGroupMatch]:
54
+ """Return raw public matches before hierarchy resolution."""
55
+ matches: list[FunctionalGroupMatch] = []
56
+ matched_names: set[str] = set()
57
+ for pattern in self.registry.execution_order():
58
+ if pattern.requires and not any(
59
+ required in matched_names for required in pattern.requires
60
+ ):
61
+ self.profile_counts[f"skip:{pattern.name}"] += 1
62
+ continue
63
+ self.profile_counts[f"attempt:{pattern.name}"] += 1
64
+ if pattern.recognizer is not None:
65
+ found = pattern.recognizer(graph, pattern)
66
+ matches.extend(found)
67
+ if found:
68
+ matched_names.add(pattern.name)
69
+ continue
70
+ if pattern.anchor_node is not None:
71
+ anchor_data = pattern.graph.nodes[pattern.anchor_node]
72
+ anchor_candidates = [
73
+ node
74
+ for node, host_data in graph.nodes(data=True)
75
+ if _node_match(host_data, anchor_data)
76
+ ]
77
+ if not anchor_candidates:
78
+ continue
79
+ matcher = GraphMatcher(
80
+ graph,
81
+ pattern.graph,
82
+ node_match=_node_match,
83
+ edge_match=_edge_match,
84
+ )
85
+ seen: set[tuple[int, ...]] = set()
86
+ for host_to_pattern in matcher.subgraph_monomorphisms_iter():
87
+ mapping = {
88
+ pattern_node: host_node
89
+ for host_node, pattern_node in host_to_pattern.items()
90
+ }
91
+ if pattern.validator is not None and not pattern.validator(
92
+ graph, mapping
93
+ ):
94
+ continue
95
+ group_nodes = tuple(
96
+ sorted(mapping[node] for node in pattern.group_nodes)
97
+ )
98
+ if group_nodes in seen:
99
+ continue
100
+ seen.add(group_nodes)
101
+ matches.append(
102
+ FunctionalGroupMatch(
103
+ name=pattern.name,
104
+ group_nodes=group_nodes,
105
+ mapping=mapping,
106
+ pattern=pattern,
107
+ )
108
+ )
109
+ if any(match.name == pattern.name for match in matches):
110
+ matched_names.add(pattern.name)
111
+ matches.extend(self._heteroaromatic_matches(graph))
112
+ if include_internal:
113
+ return matches
114
+ return [match for match in matches if match.pattern.public]
115
+
116
+ def matches(self, graph: nx.Graph) -> list[FunctionalGroupMatch]:
117
+ """Return hierarchy-resolved matches."""
118
+ raw = self.raw_matches(graph)
119
+ raw.sort(
120
+ key=lambda match: (
121
+ match.pattern.priority,
122
+ len(match.group_nodes),
123
+ match.name,
124
+ match.group_nodes,
125
+ ),
126
+ reverse=True,
127
+ )
128
+
129
+ accepted: list[FunctionalGroupMatch] = []
130
+ for candidate in raw:
131
+ if any(self._suppressed_by(candidate, chosen) for chosen in accepted):
132
+ continue
133
+ accepted.append(candidate)
134
+ return sorted(accepted, key=lambda match: (match.group_nodes, match.name))
135
+
136
+ def detect(self, graph: nx.Graph) -> list[tuple[str, tuple[int, ...]]]:
137
+ """Return simple ``(name, node_ids)`` functional-group labels."""
138
+ return [(match.name, match.group_nodes) for match in self.matches(graph)]
139
+
140
+ def _suppressed_by(
141
+ self,
142
+ candidate: FunctionalGroupMatch,
143
+ chosen: FunctionalGroupMatch,
144
+ ) -> bool:
145
+ if not self.registry.is_ancestor(candidate.name, chosen.name):
146
+ if candidate.name not in chosen.pattern.suppresses:
147
+ return False
148
+ return set(candidate.group_nodes).issubset(chosen.group_nodes)
149
+
150
+ @staticmethod
151
+ def _heteroaromatic_matches(graph: nx.Graph) -> list[FunctionalGroupMatch]:
152
+ matches: list[FunctionalGroupMatch] = []
153
+ for system in AromaticRingSystemDetector.detect(graph):
154
+ if not system.hetero_nodes:
155
+ continue
156
+ pattern_graph = nx.Graph()
157
+ pattern = FunctionalGroupPattern(
158
+ name="heteroaromatic_ring",
159
+ graph=pattern_graph,
160
+ group_nodes=(),
161
+ suppresses=("amine",),
162
+ priority=60,
163
+ )
164
+ matches.append(
165
+ FunctionalGroupMatch(
166
+ name="heteroaromatic_ring",
167
+ group_nodes=system.nodes,
168
+ mapping={},
169
+ pattern=pattern,
170
+ )
171
+ )
172
+ for (
173
+ classifier_name,
174
+ group_nodes,
175
+ ) in FunctionalGroupDetector._classify_ring_system(
176
+ graph,
177
+ system,
178
+ ):
179
+ named_pattern = FunctionalGroupPattern(
180
+ name=classifier_name,
181
+ graph=nx.Graph(),
182
+ group_nodes=(),
183
+ priority=70,
184
+ )
185
+ matches.append(
186
+ FunctionalGroupMatch(
187
+ name=classifier_name,
188
+ group_nodes=group_nodes,
189
+ mapping={},
190
+ pattern=named_pattern,
191
+ )
192
+ )
193
+ return matches
194
+
195
+ @staticmethod
196
+ def _classify_ring_system(
197
+ graph: nx.Graph, system
198
+ ) -> list[tuple[str, tuple[int, ...]]]:
199
+ labels: list[tuple[str, tuple[int, ...]]] = []
200
+ for ring in system.subrings:
201
+ ring_size = len(ring.nodes)
202
+ counts = ring.element_counts
203
+ sequence = ring.hetero_sequence
204
+ if counts == {"C": 5, "N": 1} and ring_size == 6:
205
+ labels.append(("pyridine", ring.nodes))
206
+ elif counts == {"C": 4, "N": 2} and ring_size == 6:
207
+ labels.append(("diazine", ring.nodes))
208
+ elif counts == {"C": 4, "N": 1} and ring_size == 5:
209
+ labels.append(("pyrrole", ring.nodes))
210
+ elif counts == {"C": 4, "O": 1} and ring_size == 5:
211
+ labels.append(("furan", ring.nodes))
212
+ elif counts == {"C": 4, "S": 1} and ring_size == 5:
213
+ labels.append(("thiophene", ring.nodes))
214
+ elif counts == {"C": 3, "N": 2} and ring_size == 5:
215
+ if sequence == ("C", "C", "C", "N", "N"):
216
+ labels.append(("pyrazole", ring.nodes))
217
+ elif sequence == ("C", "C", "N", "C", "N"):
218
+ labels.append(("imidazole", ring.nodes))
219
+ elif counts == {"C": 3, "N": 1, "S": 1} and ring_size == 5:
220
+ if sequence == ("C", "C", "C", "N", "S"):
221
+ labels.append(("isothiazole", ring.nodes))
222
+ elif sequence == ("C", "C", "N", "C", "S"):
223
+ labels.append(("thiazole", ring.nodes))
224
+ elif counts == {"C": 3, "N": 1, "O": 1} and ring_size == 5:
225
+ if sequence == ("C", "C", "C", "N", "O"):
226
+ labels.append(("isoxazole", ring.nodes))
227
+ elif sequence == ("C", "C", "N", "C", "O"):
228
+ labels.append(("oxazole", ring.nodes))
229
+ elif counts == {"C": 2, "N": 3} and ring_size == 5:
230
+ labels.append(("triazole", ring.nodes))
231
+ elif counts == {"C": 2, "N": 2, "O": 1} and ring_size == 5:
232
+ labels.append(("oxadiazole", ring.nodes))
233
+ elif counts == {"C": 1, "N": 4} and ring_size == 5:
234
+ labels.append(("tetrazole", ring.nodes))
235
+ elif counts == {"C": 2, "N": 2, "S": 1} and ring_size == 5:
236
+ labels.append(("thiadiazole", ring.nodes))
237
+ elif counts == {"C": 3, "N": 3} and ring_size == 6:
238
+ labels.append(("triazine", ring.nodes))
239
+ labels.extend(FunctionalGroupDetector._classify_fused_ring_system(system))
240
+ return labels
241
+
242
+ @staticmethod
243
+ def _classify_fused_ring_system(system) -> list[tuple[str, tuple[int, ...]]]:
244
+ if not system.is_fused or system.ring_sizes != (5, 6):
245
+ return []
246
+ sequences = {ring.hetero_sequence for ring in system.subrings}
247
+ if (
248
+ system.element_counts == {"C": 8, "N": 1}
249
+ and (
250
+ "C",
251
+ "C",
252
+ "C",
253
+ "C",
254
+ "N",
255
+ )
256
+ in sequences
257
+ ):
258
+ return [("indole", system.nodes)]
259
+ if system.element_counts == {"C": 7, "N": 2}:
260
+ if ("C", "C", "N", "C", "N") in sequences:
261
+ return [("benzimidazole", system.nodes)]
262
+ if ("C", "C", "C", "N", "N") in sequences:
263
+ return [("indazole", system.nodes)]
264
+ if (
265
+ system.element_counts == {"C": 7, "N": 1, "O": 1}
266
+ and (
267
+ "C",
268
+ "C",
269
+ "N",
270
+ "C",
271
+ "O",
272
+ )
273
+ in sequences
274
+ ):
275
+ return [("benzoxazole", system.nodes)]
276
+ if (
277
+ system.element_counts == {"C": 7, "N": 1, "S": 1}
278
+ and (
279
+ "C",
280
+ "C",
281
+ "N",
282
+ "C",
283
+ "S",
284
+ )
285
+ in sequences
286
+ ):
287
+ return [("benzothiazole", system.nodes)]
288
+ return []
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Callable, Iterable
5
+
6
+ import networkx as nx
7
+
8
+ Mapping = dict[int, int]
9
+ Validator = Callable[[nx.Graph, Mapping], bool]
10
+ Recognizer = Callable[
11
+ [nx.Graph, "FunctionalGroupPattern"], list["FunctionalGroupMatch"]
12
+ ]
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class FunctionalGroupPattern:
17
+ """Graph-native functional-group definition."""
18
+
19
+ name: str
20
+ graph: nx.Graph
21
+ group_nodes: tuple[int, ...]
22
+ parents: tuple[str, ...] = ()
23
+ suppresses: tuple[str, ...] = ()
24
+ requires: tuple[str, ...] = ()
25
+ anchor_node: int | None = None
26
+ priority: int = 0
27
+ validator: Validator | None = None
28
+ recognizer: Recognizer | None = None
29
+ public: bool = True
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class FunctionalGroupMatch:
34
+ """One matched functional group in a host graph."""
35
+
36
+ name: str
37
+ group_nodes: tuple[int, ...]
38
+ mapping: Mapping
39
+ pattern: FunctionalGroupPattern
40
+
41
+
42
+ @dataclass
43
+ class FunctionalGroupRegistry:
44
+ """Container for functional-group patterns and hierarchy metadata."""
45
+
46
+ patterns: list[FunctionalGroupPattern] = field(default_factory=list)
47
+
48
+ def add(self, pattern: FunctionalGroupPattern) -> None:
49
+ self.patterns.append(pattern)
50
+
51
+ def extend(self, patterns: Iterable[FunctionalGroupPattern]) -> None:
52
+ self.patterns.extend(patterns)
53
+
54
+ def by_name(self, name: str) -> FunctionalGroupPattern:
55
+ for pattern in self.patterns:
56
+ if pattern.name == name:
57
+ return pattern
58
+ raise KeyError(name)
59
+
60
+ def is_ancestor(self, ancestor: str, child: str) -> bool:
61
+ """Return whether ``ancestor`` is an ancestor of ``child``."""
62
+ seen: set[str] = set()
63
+ stack = [child]
64
+ while stack:
65
+ current = stack.pop()
66
+ if current in seen:
67
+ continue
68
+ seen.add(current)
69
+ try:
70
+ parents = self.by_name(current).parents
71
+ except KeyError:
72
+ parents = ()
73
+ for parent in parents:
74
+ if parent == ancestor:
75
+ return True
76
+ stack.append(parent)
77
+ return False
78
+
79
+ def execution_order(self) -> list[FunctionalGroupPattern]:
80
+ """Return patterns in prerequisite-respecting order."""
81
+ by_name = {pattern.name: pattern for pattern in self.patterns}
82
+ visited: set[str] = set()
83
+ ordered: list[FunctionalGroupPattern] = []
84
+
85
+ def visit(name: str) -> None:
86
+ if name in visited:
87
+ return
88
+ visited.add(name)
89
+ pattern = by_name[name]
90
+ for required in pattern.requires:
91
+ if required in by_name:
92
+ visit(required)
93
+ ordered.append(pattern)
94
+
95
+ for pattern in self.patterns:
96
+ visit(pattern.name)
97
+ return ordered
@@ -0,0 +1,274 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ import networkx as nx
6
+
7
+ from .model import FunctionalGroupPattern, Mapping
8
+ from .model import FunctionalGroupMatch
9
+
10
+
11
+ def _graph(
12
+ nodes: Iterable[tuple[int, dict]],
13
+ edges: Iterable[tuple[int, int, dict]],
14
+ ) -> nx.Graph:
15
+ graph = nx.Graph()
16
+ graph.add_nodes_from(nodes)
17
+ graph.add_edges_from(edges)
18
+ return graph
19
+
20
+
21
+ def _single_heavy_neighbors(
22
+ graph: nx.Graph, node: int, *, exclude: set[int]
23
+ ) -> list[int]:
24
+ return [
25
+ neighbor
26
+ for neighbor in graph.neighbors(node)
27
+ if neighbor not in exclude and graph.nodes[neighbor].get("element") != "H"
28
+ ]
29
+
30
+
31
+ def _alcohol_carbon_heavy_degree(expected: int):
32
+ def validator(graph: nx.Graph, mapping: Mapping) -> bool:
33
+ carbon, oxygen = mapping[1], mapping[2]
34
+ if not _alcohol_like(graph, mapping):
35
+ return False
36
+ return len(_single_heavy_neighbors(graph, carbon, exclude={oxygen})) == expected
37
+
38
+ return validator
39
+
40
+
41
+ def _alcohol_like(graph: nx.Graph, mapping: Mapping) -> bool:
42
+ carbon, oxygen = mapping[1], mapping[2]
43
+ if graph.nodes[carbon].get("aromatic"):
44
+ return False
45
+ if graph.nodes[oxygen].get("hcount", 0) < 1:
46
+ return False
47
+ return all(
48
+ graph.edges[carbon, neighbor].get("order") == 1.0
49
+ for neighbor in graph.neighbors(carbon)
50
+ )
51
+
52
+
53
+ def _aldehyde(graph: nx.Graph, mapping: Mapping) -> bool:
54
+ carbon, oxygen = mapping[1], mapping[2]
55
+ if graph.nodes[carbon].get("hcount", 0) < 1:
56
+ return False
57
+ others = _single_heavy_neighbors(graph, carbon, exclude={oxygen})
58
+ return all(graph.nodes[node].get("element") == "C" for node in others)
59
+
60
+
61
+ def _amine(graph: nx.Graph, mapping: Mapping) -> bool:
62
+ nitrogen = mapping[1]
63
+ if graph.nodes[nitrogen].get("aromatic"):
64
+ return False
65
+ return all(
66
+ graph.edges[nitrogen, neighbor].get("order") == 1.0
67
+ for neighbor in graph.neighbors(nitrogen)
68
+ )
69
+
70
+
71
+ def _phenol(graph: nx.Graph, mapping: Mapping) -> bool:
72
+ carbon, oxygen = mapping[1], mapping[2]
73
+ return (
74
+ graph.nodes[carbon].get("aromatic") is True
75
+ and graph.nodes[oxygen].get("hcount", 0) >= 1
76
+ )
77
+
78
+
79
+ def _enol(graph: nx.Graph, mapping: Mapping) -> bool:
80
+ return graph.nodes[mapping[3]].get("hcount", 0) >= 1
81
+
82
+
83
+ def _epoxide(graph: nx.Graph, mapping: Mapping) -> bool:
84
+ return all(graph.nodes[mapping[node]].get("in_ring") for node in (1, 2, 3))
85
+
86
+
87
+ def _phosphite(graph: nx.Graph, mapping: Mapping) -> bool:
88
+ phosphorus = mapping[1]
89
+ return all(
90
+ not (
91
+ graph.nodes[neighbor].get("element") == "O"
92
+ and graph.edges[phosphorus, neighbor].get("order") == 2.0
93
+ )
94
+ for neighbor in graph.neighbors(phosphorus)
95
+ )
96
+
97
+
98
+ def _azide(graph: nx.Graph, mapping: Mapping) -> bool:
99
+ middle, terminal = mapping[2], mapping[3]
100
+ return (
101
+ graph.nodes[middle].get("charge") == 1
102
+ and graph.nodes[terminal].get("charge") == -1
103
+ )
104
+
105
+
106
+ def _hydrazone(graph: nx.Graph, mapping: Mapping) -> bool:
107
+ imine_nitrogen = mapping[2]
108
+ hydrazine_nitrogen = mapping[3]
109
+ return not (
110
+ graph.nodes[imine_nitrogen].get("charge") == 1
111
+ and graph.nodes[hydrazine_nitrogen].get("charge") == -1
112
+ )
113
+
114
+
115
+ def _amidine(graph: nx.Graph, mapping: Mapping) -> bool:
116
+ carbon = mapping[1]
117
+ imine_nitrogen = mapping[2]
118
+ amino_nitrogen = mapping[3]
119
+ if any(
120
+ graph.nodes[neighbor].get("element") == "O"
121
+ for neighbor in graph.neighbors(imine_nitrogen)
122
+ if neighbor != carbon
123
+ ):
124
+ return False
125
+ if any(
126
+ graph.nodes[neighbor].get("element") == "O"
127
+ for neighbor in graph.neighbors(amino_nitrogen)
128
+ if neighbor != carbon
129
+ ):
130
+ return False
131
+ return True
132
+
133
+
134
+ def _imine(graph: nx.Graph, mapping: Mapping) -> bool:
135
+ carbon = mapping[1]
136
+ nitrogen = mapping[2]
137
+ if graph.nodes[carbon].get("aromatic") or graph.nodes[nitrogen].get("aromatic"):
138
+ return False
139
+ carbon_neighbors = {
140
+ graph.nodes[neighbor].get("element")
141
+ for neighbor in graph.neighbors(carbon)
142
+ if neighbor != nitrogen
143
+ }
144
+ nitrogen_neighbors = {
145
+ graph.nodes[neighbor].get("element")
146
+ for neighbor in graph.neighbors(nitrogen)
147
+ if neighbor != carbon
148
+ }
149
+ if carbon_neighbors & {"O", "S", "N"}:
150
+ return False
151
+ if nitrogen_neighbors & {"O", "N"}:
152
+ return False
153
+ return True
154
+
155
+
156
+ def _aniline(graph: nx.Graph, mapping: Mapping) -> bool:
157
+ return graph.nodes[mapping[1]].get("aromatic") is True and _amine(
158
+ graph, {1: mapping[2]}
159
+ )
160
+
161
+
162
+ def _aryl_halide(graph: nx.Graph, mapping: Mapping) -> bool:
163
+ return graph.nodes[mapping[1]].get("aromatic") is True
164
+
165
+
166
+ def _oxygen_has_h(graph: nx.Graph, node: int) -> bool:
167
+ return graph.nodes[node].get("hcount", 0) >= 1
168
+
169
+
170
+ def _carbon_substituent_count(graph: nx.Graph, carbon: int, excluded: set[int]) -> int:
171
+ return len(_single_heavy_neighbors(graph, carbon, exclude=excluded))
172
+
173
+
174
+ def _aromatic_ring_nodes(graph: nx.Graph, mapping: Mapping) -> set[int]:
175
+ return {mapping[node] for node in mapping}
176
+
177
+
178
+ def _all_aromatic(graph: nx.Graph, nodes: set[int]) -> bool:
179
+ return all(graph.nodes[node].get("aromatic") for node in nodes)
180
+
181
+
182
+ def _single_node_recognizer(element: str):
183
+ def recognize(
184
+ graph: nx.Graph, pattern: FunctionalGroupPattern
185
+ ) -> list[FunctionalGroupMatch]:
186
+ matches: list[FunctionalGroupMatch] = []
187
+ for node, data in graph.nodes(data=True):
188
+ if data.get("element") != element:
189
+ continue
190
+ mapping = {1: node}
191
+ if pattern.validator is not None and not pattern.validator(graph, mapping):
192
+ continue
193
+ matches.append(
194
+ FunctionalGroupMatch(
195
+ name=pattern.name,
196
+ group_nodes=(node,),
197
+ mapping=mapping,
198
+ pattern=pattern,
199
+ )
200
+ )
201
+ return matches
202
+
203
+ return recognize
204
+
205
+
206
+ def _two_node_bond_recognizer(
207
+ left_element: str,
208
+ right_elements: tuple[str, ...],
209
+ order: float,
210
+ ):
211
+ def recognize(
212
+ graph: nx.Graph, pattern: FunctionalGroupPattern
213
+ ) -> list[FunctionalGroupMatch]:
214
+ matches: list[FunctionalGroupMatch] = []
215
+ seen: set[tuple[int, ...]] = set()
216
+ for left, right, data in graph.edges(data=True):
217
+ if data.get("order") != order:
218
+ continue
219
+ pairs = ((left, right), (right, left))
220
+ for first, second in pairs:
221
+ if graph.nodes[first].get("element") != left_element:
222
+ continue
223
+ if graph.nodes[second].get("element") not in right_elements:
224
+ continue
225
+ mapping = {1: first, 2: second}
226
+ if pattern.validator is not None and not pattern.validator(
227
+ graph, mapping
228
+ ):
229
+ continue
230
+ group_nodes = tuple(
231
+ sorted(mapping[node] for node in pattern.group_nodes)
232
+ )
233
+ if group_nodes in seen:
234
+ continue
235
+ seen.add(group_nodes)
236
+ matches.append(
237
+ FunctionalGroupMatch(
238
+ name=pattern.name,
239
+ group_nodes=group_nodes,
240
+ mapping=mapping,
241
+ pattern=pattern,
242
+ )
243
+ )
244
+ return matches
245
+
246
+ return recognize
247
+
248
+
249
+ def _symmetric_two_node_bond_recognizer(element: str, order: float):
250
+ def recognize(
251
+ graph: nx.Graph, pattern: FunctionalGroupPattern
252
+ ) -> list[FunctionalGroupMatch]:
253
+ matches: list[FunctionalGroupMatch] = []
254
+ for left, right, data in graph.edges(data=True):
255
+ if data.get("order") != order:
256
+ continue
257
+ if graph.nodes[left].get("element") != element:
258
+ continue
259
+ if graph.nodes[right].get("element") != element:
260
+ continue
261
+ mapping = {1: left, 2: right}
262
+ if pattern.validator is not None and not pattern.validator(graph, mapping):
263
+ continue
264
+ matches.append(
265
+ FunctionalGroupMatch(
266
+ name=pattern.name,
267
+ group_nodes=tuple(sorted((left, right))),
268
+ mapping=mapping,
269
+ pattern=pattern,
270
+ )
271
+ )
272
+ return matches
273
+
274
+ return recognize