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,700 @@
1
+ """Explicit two-arm interfaces for verified graph fusion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field, replace
6
+ from enum import Enum
7
+ from typing import Any, Hashable, Mapping, Sequence
8
+
9
+ import networkx as nx
10
+
11
+ from synkit.Graph.Morphism import (
12
+ GraphMorphism,
13
+ NodeStateKind,
14
+ WildcardConstraint,
15
+ adapt_legacy_node_state,
16
+ )
17
+ from synkit.Graph.Morphism import StereoEffect
18
+
19
+ from .identity import FUSION_NODE_IDENTITY_KEYS, FUSION_WL_ITERATIONS
20
+
21
+ # A proof-bearing interface must compare the same chemical/Lewis state used by
22
+ # exact candidate identity. Matcher attributes remain independently
23
+ # configurable and may be deliberately coarser for candidate discovery.
24
+ # ``hcount`` is excluded at the interface because an overlap may resolve an
25
+ # implicit hydrogen against an explicit completion port. The completed graph
26
+ # identity still checks the effective hydrogen inventory after substitution.
27
+ DEFAULT_INTERFACE_NODE_KEYS = tuple(
28
+ key for key in FUSION_NODE_IDENTITY_KEYS if key != "hcount"
29
+ )
30
+ DEFAULT_INTERFACE_EDGE_KEYS = ("order",)
31
+
32
+
33
+ class FusionInterfaceIssueCode(str, Enum):
34
+ """Stable failures raised before graph construction."""
35
+
36
+ SOURCE_MISMATCH = "FUSION_INTERFACE_SOURCE_MISMATCH"
37
+ TARGET_MISMATCH = "FUSION_INTERFACE_TARGET_MISMATCH"
38
+ NODE_CONFLICT = "FUSION_INTERFACE_NODE_CONFLICT"
39
+ EDGE_CONFLICT = "FUSION_INTERFACE_EDGE_CONFLICT"
40
+ WILDCARD_CONFLICT = "FUSION_INTERFACE_WILDCARD_CONFLICT"
41
+ OWNER_OUTSIDE_INTERFACE = "FUSION_INTERFACE_OWNER_OUTSIDE_INTERFACE"
42
+ INVALID_MAPPING = "FUSION_INTERFACE_INVALID_MAPPING"
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class FusionInterfaceIssue:
47
+ code: FusionInterfaceIssueCode
48
+ message: str
49
+ context: Mapping[str, Any] = field(default_factory=dict)
50
+
51
+ def to_dict(self) -> dict[str, Any]:
52
+ return {
53
+ "code": self.code.value,
54
+ "message": self.message,
55
+ "context": dict(self.context),
56
+ }
57
+
58
+
59
+ class FusionInterfaceError(ValueError):
60
+ """Raised when a proposed common interface is not compatible."""
61
+
62
+ def __init__(self, *issues: FusionInterfaceIssue):
63
+ self.issues = tuple(issues)
64
+ super().__init__("; ".join(issue.message for issue in self.issues))
65
+
66
+
67
+ def _stable_value(value: Any) -> Any:
68
+ if isinstance(value, Mapping):
69
+ return tuple(
70
+ sorted(
71
+ ((repr(key), _stable_value(item)) for key, item in value.items()),
72
+ key=repr,
73
+ )
74
+ )
75
+ if isinstance(value, (list, tuple, set, frozenset)):
76
+ items = tuple(_stable_value(item) for item in value)
77
+ return (
78
+ tuple(sorted(items, key=repr))
79
+ if isinstance(value, (set, frozenset))
80
+ else items
81
+ )
82
+ return value
83
+
84
+
85
+ def _attribute_signature(
86
+ attributes: Mapping[str, Any], keys: Sequence[str]
87
+ ) -> tuple[Any, ...]:
88
+ return tuple((key, _stable_value(attributes.get(key))) for key in keys)
89
+
90
+
91
+ def _wildcard_values(wildcard_element: Any) -> tuple[Any, ...]:
92
+ scalar = (
93
+ wildcard_element[0]
94
+ if isinstance(wildcard_element, (tuple, list))
95
+ else wildcard_element
96
+ )
97
+ return (wildcard_element, scalar)
98
+
99
+
100
+ def _constraint_for_interface(
101
+ graph: nx.Graph,
102
+ node: Hashable,
103
+ inverse_arm: Mapping[Hashable, Hashable],
104
+ *,
105
+ element_key: str,
106
+ role_key: str,
107
+ wildcard_element: Any,
108
+ ) -> WildcardConstraint | None:
109
+ state = adapt_legacy_node_state(
110
+ graph.nodes[node],
111
+ element_key=element_key,
112
+ role_key=role_key,
113
+ wildcard_values=_wildcard_values(wildcard_element),
114
+ )
115
+ if state.kind is not NodeStateKind.WILDCARD:
116
+ return None
117
+ constraint = state.constraint
118
+ if constraint is None:
119
+ return None
120
+ if constraint.owner is not None and constraint.owner not in inverse_arm:
121
+ raise FusionInterfaceError(
122
+ FusionInterfaceIssue(
123
+ FusionInterfaceIssueCode.OWNER_OUTSIDE_INTERFACE,
124
+ "A wildcard owner is absent from the proposed interface.",
125
+ {"node": repr(node), "owner": repr(constraint.owner)},
126
+ )
127
+ )
128
+ return constraint.relabel_owner(inverse_arm)
129
+
130
+
131
+ def _validated_mapping_pairs(
132
+ forward_graph: nx.Graph,
133
+ backward_graph: nx.Graph,
134
+ mapping: Mapping[Hashable, Hashable],
135
+ ) -> tuple[tuple[Hashable, Hashable], ...]:
136
+ pairs = tuple(
137
+ sorted(mapping.items(), key=lambda item: (repr(item[0]), repr(item[1])))
138
+ )
139
+ if not pairs:
140
+ raise FusionInterfaceError(
141
+ FusionInterfaceIssue(
142
+ FusionInterfaceIssueCode.INVALID_MAPPING,
143
+ "A fusion interface cannot be empty.",
144
+ )
145
+ )
146
+ if len({left for left, _ in pairs}) != len(pairs) or len(
147
+ {right for _, right in pairs}
148
+ ) != len(pairs):
149
+ raise FusionInterfaceError(
150
+ FusionInterfaceIssue(
151
+ FusionInterfaceIssueCode.INVALID_MAPPING,
152
+ "Interface mappings must be injective.",
153
+ )
154
+ )
155
+ if any(
156
+ left not in forward_graph or right not in backward_graph
157
+ for left, right in pairs
158
+ ):
159
+ raise FusionInterfaceError(
160
+ FusionInterfaceIssue(
161
+ FusionInterfaceIssueCode.TARGET_MISMATCH,
162
+ "Interface images must exist in both source graphs.",
163
+ )
164
+ )
165
+ return pairs
166
+
167
+
168
+ def _compatible_node_label(
169
+ forward_attrs: Mapping[str, Any],
170
+ backward_attrs: Mapping[str, Any],
171
+ *,
172
+ node_keys: Sequence[str],
173
+ element_key: str,
174
+ wildcard_values: tuple[Any, ...],
175
+ forward_node: Hashable,
176
+ backward_node: Hashable,
177
+ allow_hcount_resolution: bool,
178
+ ) -> tuple[Any, ...]:
179
+ forward_wildcard = forward_attrs.get(element_key) in wildcard_values
180
+ backward_wildcard = backward_attrs.get(element_key) in wildcard_values
181
+ if forward_wildcard or backward_wildcard:
182
+ concrete = backward_attrs if forward_wildcard else forward_attrs
183
+ return _attribute_signature(concrete, node_keys)
184
+ left_label = _attribute_signature(forward_attrs, node_keys)
185
+ right_label = _attribute_signature(backward_attrs, node_keys)
186
+ forward_hcount = _effective_hcount(forward_attrs)
187
+ backward_hcount = _effective_hcount(backward_attrs)
188
+ hcount_conflict = (
189
+ not allow_hcount_resolution
190
+ and forward_hcount is not None
191
+ and backward_hcount is not None
192
+ and forward_hcount != backward_hcount
193
+ )
194
+ if left_label != right_label or hcount_conflict:
195
+ raise FusionInterfaceError(
196
+ FusionInterfaceIssue(
197
+ FusionInterfaceIssueCode.NODE_CONFLICT,
198
+ "Mapped concrete nodes disagree on interface attributes.",
199
+ {
200
+ "forward_node": repr(forward_node),
201
+ "backward_node": repr(backward_node),
202
+ "forward_hcount": forward_hcount,
203
+ "backward_hcount": backward_hcount,
204
+ },
205
+ )
206
+ )
207
+ return left_label
208
+
209
+
210
+ def _endpoint_scalar(value: Any) -> Any:
211
+ if isinstance(value, (tuple, list)) and len(value) == 2 and value[0] == value[1]:
212
+ return value[0]
213
+ return value
214
+
215
+
216
+ def _effective_hcount(attributes: Mapping[str, Any]) -> Any:
217
+ """Normalize implicit H and explicit-H neighbor bookkeeping."""
218
+ hcount = attributes.get("hcount")
219
+ neighbours = attributes.get("neighbors")
220
+ if isinstance(hcount, (tuple, list)) and len(hcount) == 2:
221
+ if isinstance(neighbours, (tuple, list)) and len(neighbours) == 2:
222
+ return tuple(
223
+ hcount[index]
224
+ + sum(item == "H" for item in (neighbours[index] or ()))
225
+ for index in range(2)
226
+ )
227
+ return tuple(hcount)
228
+ if isinstance(hcount, int) and isinstance(neighbours, (tuple, list)):
229
+ return hcount + sum(item == "H" for item in neighbours)
230
+ return hcount
231
+
232
+
233
+ def _owns_completion_port(
234
+ graph: nx.Graph,
235
+ node: Hashable,
236
+ *,
237
+ element_key: str,
238
+ role_key: str,
239
+ wildcard_values: tuple[Any, ...],
240
+ ) -> bool:
241
+ completion_roles = {
242
+ "attachment_port",
243
+ "radical_completion",
244
+ "hydrogen_completion",
245
+ }
246
+ return any(
247
+ graph.nodes[neighbour].get(element_key) in wildcard_values
248
+ and graph.nodes[neighbour].get(role_key) in completion_roles
249
+ and graph.nodes[neighbour].get("owner") == node
250
+ for neighbour in graph.neighbors(node)
251
+ )
252
+
253
+
254
+ def _constraint_accepts_concrete(
255
+ constraint: WildcardConstraint,
256
+ attributes: Mapping[str, Any],
257
+ *,
258
+ graph: nx.Graph,
259
+ node: Hashable,
260
+ arm: Mapping[Hashable, Hashable],
261
+ element_key: str,
262
+ ) -> bool:
263
+ """Evaluate a concrete resolution through the canonical constraint API."""
264
+ concrete: dict[str, Any] = {
265
+ "element": _endpoint_scalar(attributes.get(element_key)),
266
+ "charge": _endpoint_scalar(attributes.get("charge", 0)),
267
+ "radical": _endpoint_scalar(attributes.get("radical", 0)),
268
+ "side": _endpoint_scalar(attributes.get("side")),
269
+ "stereo_slot": _endpoint_scalar(attributes.get("stereo_slot")),
270
+ "virtual_kind": _endpoint_scalar(attributes.get("virtual_kind")),
271
+ "mapped_identity": _endpoint_scalar(attributes.get("mapped_identity")),
272
+ "materialization": _endpoint_scalar(attributes.get("materialization")),
273
+ # Resolving one wildcard consumes one unit of its capacity/resource.
274
+ "capacity": 1,
275
+ "resource_usage": 1,
276
+ }
277
+ if constraint.owner is not None:
278
+ concrete_owner = arm.get(constraint.owner)
279
+ if concrete_owner is not None and graph.has_edge(node, concrete_owner):
280
+ concrete["owner"] = constraint.owner
281
+ edge = graph.edges[node, concrete_owner]
282
+ concrete["bond_order"] = _endpoint_scalar(edge.get("order"))
283
+ else:
284
+ concrete["owner"] = None
285
+ return constraint.satisfies(concrete)
286
+
287
+
288
+ def _common_interface_edges(
289
+ forward_graph: nx.Graph,
290
+ backward_graph: nx.Graph,
291
+ forward_arm: Mapping[Hashable, Hashable],
292
+ backward_arm: Mapping[Hashable, Hashable],
293
+ edge_keys: Sequence[str],
294
+ ) -> tuple[tuple[Hashable, Hashable, tuple[Any, ...]], ...]:
295
+ edges: list[tuple[Hashable, Hashable, tuple[Any, ...]]] = []
296
+ inverse_forward = {target: source for source, target in forward_arm.items()}
297
+ for f_left, f_right in forward_graph.edges:
298
+ if f_left not in inverse_forward or f_right not in inverse_forward:
299
+ continue
300
+ left, right = inverse_forward[f_left], inverse_forward[f_right]
301
+ b_left, b_right = backward_arm[left], backward_arm[right]
302
+ if not backward_graph.has_edge(b_left, b_right):
303
+ continue
304
+ f_label = _attribute_signature(forward_graph.edges[f_left, f_right], edge_keys)
305
+ b_label = _attribute_signature(backward_graph.edges[b_left, b_right], edge_keys)
306
+ if f_label != b_label:
307
+ raise FusionInterfaceError(
308
+ FusionInterfaceIssue(
309
+ FusionInterfaceIssueCode.EDGE_CONFLICT,
310
+ "Mapped interface edges disagree on bond attributes.",
311
+ {"edge": (repr(left), repr(right))},
312
+ )
313
+ )
314
+ edges.append((left, right, f_label))
315
+ return tuple(sorted(edges, key=repr))
316
+
317
+
318
+ @dataclass(frozen=True)
319
+ class FusionInterface:
320
+ """A common graph ``K`` with injective arms into two source graphs."""
321
+
322
+ forward_morphism: GraphMorphism
323
+ backward_morphism: GraphMorphism
324
+ node_labels: tuple[tuple[Hashable, tuple[Any, ...]], ...] = ()
325
+ edges: tuple[tuple[Hashable, Hashable, tuple[Any, ...]], ...] = ()
326
+ stereo_effects: tuple[tuple[str, str, str, StereoEffect], ...] = ()
327
+
328
+ def __post_init__(self) -> None:
329
+ forward = self.forward_morphism
330
+ backward = self.backward_morphism
331
+ if (
332
+ forward.source != backward.source
333
+ or forward.source_nodes != backward.source_nodes
334
+ ):
335
+ raise FusionInterfaceError(
336
+ FusionInterfaceIssue(
337
+ FusionInterfaceIssueCode.SOURCE_MISMATCH,
338
+ "Both fusion arms must have the same interface source.",
339
+ )
340
+ )
341
+ labels = tuple(sorted(tuple(self.node_labels), key=lambda item: repr(item[0])))
342
+ if labels and {node for node, _ in labels} != set(forward.source_nodes):
343
+ raise FusionInterfaceError(
344
+ FusionInterfaceIssue(
345
+ FusionInterfaceIssueCode.INVALID_MAPPING,
346
+ "Interface node labels must be total on K.",
347
+ )
348
+ )
349
+ normalized_edges = tuple(
350
+ sorted(
351
+ (
352
+ (
353
+ (left, right, label)
354
+ if repr(left) <= repr(right)
355
+ else (right, left, label)
356
+ )
357
+ for left, right, label in self.edges
358
+ ),
359
+ key=repr,
360
+ )
361
+ )
362
+ if any(
363
+ left not in forward.source_nodes or right not in forward.source_nodes
364
+ for left, right, _ in normalized_edges
365
+ ):
366
+ raise FusionInterfaceError(
367
+ FusionInterfaceIssue(
368
+ FusionInterfaceIssueCode.INVALID_MAPPING,
369
+ "Interface edges must have endpoints in K.",
370
+ )
371
+ )
372
+ object.__setattr__(self, "node_labels", labels)
373
+ object.__setattr__(self, "edges", normalized_edges)
374
+ effects = tuple(
375
+ sorted(
376
+ (
377
+ (
378
+ side,
379
+ layer,
380
+ descriptor_id,
381
+ (
382
+ effect
383
+ if isinstance(effect, StereoEffect)
384
+ else StereoEffect(effect)
385
+ ),
386
+ )
387
+ for side, layer, descriptor_id, effect in self.stereo_effects
388
+ ),
389
+ key=repr,
390
+ )
391
+ )
392
+ if any(side not in {"forward", "backward"} for side, _, _, _ in effects):
393
+ raise ValueError("Stereo effects require side 'forward' or 'backward'.")
394
+ object.__setattr__(self, "stereo_effects", effects)
395
+
396
+ f_theta = forward.substitutions
397
+ b_theta = backward.substitutions
398
+ for node in forward.source_nodes:
399
+ left = f_theta.get(node)
400
+ right = b_theta.get(node)
401
+ if (
402
+ left is not None
403
+ and right is not None
404
+ and not left.intersect(right).valid
405
+ ):
406
+ raise FusionInterfaceError(
407
+ FusionInterfaceIssue(
408
+ FusionInterfaceIssueCode.WILDCARD_CONFLICT,
409
+ "Wildcard substitutions contradict across the interface.",
410
+ {"interface_node": repr(node)},
411
+ )
412
+ )
413
+
414
+ edge_pairs = {frozenset((left, right)) for left, right, _ in normalized_edges}
415
+ for node, constraint in self.substitutions.items():
416
+ if (
417
+ constraint.owner is not None
418
+ and frozenset((node, constraint.owner)) not in edge_pairs
419
+ ):
420
+ raise FusionInterfaceError(
421
+ FusionInterfaceIssue(
422
+ FusionInterfaceIssueCode.OWNER_OUTSIDE_INTERFACE,
423
+ "Wildcard owner incidence is absent from K.",
424
+ {
425
+ "interface_node": repr(node),
426
+ "owner": repr(constraint.owner),
427
+ },
428
+ )
429
+ )
430
+
431
+ @property
432
+ def interface_nodes(self) -> frozenset[Hashable]:
433
+ return self.forward_morphism.source_nodes
434
+
435
+ @property
436
+ def substitutions(self) -> dict[Hashable, WildcardConstraint]:
437
+ """Return the normalized cross-arm substitution environment."""
438
+ result: dict[Hashable, WildcardConstraint] = {}
439
+ forward = self.forward_morphism.substitutions
440
+ backward = self.backward_morphism.substitutions
441
+ for node in self.interface_nodes:
442
+ left = forward.get(node)
443
+ right = backward.get(node)
444
+ if left is None and right is None:
445
+ continue
446
+ if left is None:
447
+ result[node] = right # type: ignore[assignment]
448
+ elif right is None:
449
+ result[node] = left
450
+ else:
451
+ unified = left.intersect(right)
452
+ if unified.constraint is not None:
453
+ result[node] = unified.constraint
454
+ return result
455
+
456
+ def to_dict(self) -> dict[str, Any]:
457
+ """Return the interface and both morphism arms as proof data."""
458
+
459
+ def morphism_payload(morphism: GraphMorphism) -> dict[str, Any]:
460
+ return {
461
+ "source": repr(morphism.source),
462
+ "target": repr(morphism.target),
463
+ "mapping": [(repr(left), repr(right)) for left, right in morphism.f],
464
+ "theta": [
465
+ {
466
+ "node": repr(node),
467
+ "constraint": constraint.normalized(),
468
+ }
469
+ for node, constraint in morphism.theta
470
+ ],
471
+ "canonical_signature": morphism.canonical_signature(),
472
+ }
473
+
474
+ return {
475
+ "nodes": [repr(node) for node in sorted(self.interface_nodes, key=repr)],
476
+ "node_labels": [(repr(node), label) for node, label in self.node_labels],
477
+ "edges": [
478
+ (repr(left), repr(right), label) for left, right, label in self.edges
479
+ ],
480
+ "stereo_effects": [
481
+ {
482
+ "side": side,
483
+ "layer": layer,
484
+ "descriptor_id": descriptor_id,
485
+ "effect": effect.value,
486
+ }
487
+ for side, layer, descriptor_id, effect in self.stereo_effects
488
+ ],
489
+ "forward_morphism": morphism_payload(self.forward_morphism),
490
+ "backward_morphism": morphism_payload(self.backward_morphism),
491
+ "canonical_signature": self.canonical_signature(),
492
+ }
493
+
494
+ def canonical_signature(self) -> tuple[Any, ...]:
495
+ """Return a relabeling-invariant signature of K and both arms."""
496
+ graph = nx.Graph()
497
+ labels = dict(self.node_labels)
498
+ substitutions = self.substitutions
499
+ for node in self.interface_nodes:
500
+ constraint = substitutions.get(node)
501
+ graph.add_node(
502
+ node,
503
+ signature=repr(
504
+ (
505
+ labels.get(node, ()),
506
+ constraint.normalized() if constraint is not None else None,
507
+ )
508
+ ),
509
+ )
510
+ for left, right, label in self.edges:
511
+ graph.add_edge(left, right, signature=repr(label))
512
+ digest = nx.weisfeiler_lehman_graph_hash(
513
+ graph,
514
+ node_attr="signature",
515
+ edge_attr="signature",
516
+ iterations=FUSION_WL_ITERATIONS,
517
+ digest_size=16,
518
+ )
519
+ arms = tuple(
520
+ sorted(
521
+ (
522
+ self.forward_morphism.canonical_signature(),
523
+ self.backward_morphism.canonical_signature(),
524
+ ),
525
+ key=repr,
526
+ )
527
+ )
528
+ effects = tuple(
529
+ (side, layer, descriptor_id, effect.value)
530
+ for side, layer, descriptor_id, effect in self.stereo_effects
531
+ )
532
+ return (digest, arms, effects)
533
+
534
+ def with_stereo_effects(
535
+ self,
536
+ effects: Mapping[tuple[str, str, str], StereoEffect | str],
537
+ ) -> "FusionInterface":
538
+ """Return a copy carrying explicit rule-authoritative stereo effects."""
539
+ return replace(
540
+ self,
541
+ stereo_effects=tuple(
542
+ (side, layer, descriptor_id, StereoEffect(effect))
543
+ for (side, layer, descriptor_id), effect in effects.items()
544
+ ),
545
+ )
546
+
547
+ @classmethod
548
+ def from_mapping(
549
+ cls,
550
+ forward_graph: nx.Graph,
551
+ backward_graph: nx.Graph,
552
+ mapping: Mapping[Hashable, Hashable],
553
+ *,
554
+ node_keys: Sequence[str] = DEFAULT_INTERFACE_NODE_KEYS,
555
+ edge_keys: Sequence[str] = DEFAULT_INTERFACE_EDGE_KEYS,
556
+ element_key: str = "element",
557
+ role_key: str = "wildcard_role",
558
+ wildcard_element: Any = "*",
559
+ ) -> "FusionInterface":
560
+ """Validate a proposed forward-to-backward overlap and build K."""
561
+ pairs = _validated_mapping_pairs(forward_graph, backward_graph, mapping)
562
+
563
+ interface_nodes = frozenset(range(len(pairs)))
564
+ forward_arm = {index: left for index, (left, _) in enumerate(pairs)}
565
+ backward_arm = {index: right for index, (_, right) in enumerate(pairs)}
566
+ inverse_forward = {value: key for key, value in forward_arm.items()}
567
+ inverse_backward = {value: key for key, value in backward_arm.items()}
568
+ forward_theta: dict[Hashable, WildcardConstraint] = {}
569
+ backward_theta: dict[Hashable, WildcardConstraint] = {}
570
+ labels: list[tuple[Hashable, tuple[Any, ...]]] = []
571
+ wildcard_values = _wildcard_values(wildcard_element)
572
+
573
+ for interface_node, (forward_node, backward_node) in enumerate(pairs):
574
+ forward_attrs = forward_graph.nodes[forward_node]
575
+ backward_attrs = backward_graph.nodes[backward_node]
576
+ forward_wildcard = forward_attrs.get(element_key) in wildcard_values
577
+ backward_wildcard = backward_attrs.get(element_key) in wildcard_values
578
+ labels.append(
579
+ (
580
+ interface_node,
581
+ _compatible_node_label(
582
+ forward_attrs,
583
+ backward_attrs,
584
+ node_keys=node_keys,
585
+ element_key=element_key,
586
+ wildcard_values=wildcard_values,
587
+ forward_node=forward_node,
588
+ backward_node=backward_node,
589
+ allow_hcount_resolution=(
590
+ _owns_completion_port(
591
+ forward_graph,
592
+ forward_node,
593
+ element_key=element_key,
594
+ role_key=role_key,
595
+ wildcard_values=wildcard_values,
596
+ )
597
+ or _owns_completion_port(
598
+ backward_graph,
599
+ backward_node,
600
+ element_key=element_key,
601
+ role_key=role_key,
602
+ wildcard_values=wildcard_values,
603
+ )
604
+ ),
605
+ ),
606
+ )
607
+ )
608
+
609
+ if forward_wildcard:
610
+ constraint = _constraint_for_interface(
611
+ forward_graph,
612
+ forward_node,
613
+ inverse_forward,
614
+ element_key=element_key,
615
+ role_key=role_key,
616
+ wildcard_element=wildcard_element,
617
+ )
618
+ if constraint is not None:
619
+ forward_theta[interface_node] = constraint
620
+ if backward_wildcard:
621
+ constraint = _constraint_for_interface(
622
+ backward_graph,
623
+ backward_node,
624
+ inverse_backward,
625
+ element_key=element_key,
626
+ role_key=role_key,
627
+ wildcard_element=wildcard_element,
628
+ )
629
+ if constraint is not None:
630
+ backward_theta[interface_node] = constraint
631
+ if forward_wildcard and not backward_wildcard:
632
+ constraint = forward_theta.get(interface_node)
633
+ if constraint is not None and not _constraint_accepts_concrete(
634
+ constraint,
635
+ backward_attrs,
636
+ graph=backward_graph,
637
+ node=backward_node,
638
+ arm=backward_arm,
639
+ element_key=element_key,
640
+ ):
641
+ raise FusionInterfaceError(
642
+ FusionInterfaceIssue(
643
+ FusionInterfaceIssueCode.WILDCARD_CONFLICT,
644
+ "Backward concrete state violates the forward wildcard domain.",
645
+ {"interface_node": repr(interface_node)},
646
+ )
647
+ )
648
+ if backward_wildcard and not forward_wildcard:
649
+ constraint = backward_theta.get(interface_node)
650
+ if constraint is not None and not _constraint_accepts_concrete(
651
+ constraint,
652
+ forward_attrs,
653
+ graph=forward_graph,
654
+ node=forward_node,
655
+ arm=forward_arm,
656
+ element_key=element_key,
657
+ ):
658
+ raise FusionInterfaceError(
659
+ FusionInterfaceIssue(
660
+ FusionInterfaceIssueCode.WILDCARD_CONFLICT,
661
+ "Forward concrete state violates the backward wildcard domain.",
662
+ {"interface_node": repr(interface_node)},
663
+ )
664
+ )
665
+
666
+ edges = _common_interface_edges(
667
+ forward_graph,
668
+ backward_graph,
669
+ forward_arm,
670
+ backward_arm,
671
+ edge_keys,
672
+ )
673
+
674
+ forward = GraphMorphism(
675
+ "K",
676
+ "forward",
677
+ interface_nodes,
678
+ frozenset(forward_graph.nodes),
679
+ forward_arm,
680
+ forward_theta,
681
+ )
682
+ backward = GraphMorphism(
683
+ "K",
684
+ "backward",
685
+ interface_nodes,
686
+ frozenset(backward_graph.nodes),
687
+ backward_arm,
688
+ backward_theta,
689
+ )
690
+ return cls(forward, backward, tuple(labels), edges)
691
+
692
+
693
+ __all__ = [
694
+ "DEFAULT_INTERFACE_EDGE_KEYS",
695
+ "DEFAULT_INTERFACE_NODE_KEYS",
696
+ "FusionInterface",
697
+ "FusionInterfaceError",
698
+ "FusionInterfaceIssue",
699
+ "FusionInterfaceIssueCode",
700
+ ]