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,928 @@
1
+ """Occurrence-aware MTG histories with lossless process reconstruction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import Counter
6
+ from dataclasses import dataclass, field
7
+ from enum import Enum
8
+ from typing import Any, Hashable, Iterable, Mapping
9
+
10
+ import networkx as nx
11
+
12
+ from synkit.Graph.Morphism import (
13
+ LLGError,
14
+ LabelSchema,
15
+ LewisLabelledGraph,
16
+ derive_electron_labeled_graph,
17
+ is_electron_schema,
18
+ )
19
+ from synkit.Rule.Apply import DPOError, RuleSpan
20
+
21
+ from .process import (
22
+ ChoiceWitness,
23
+ IndependenceWitness,
24
+ MaterialOccurrence,
25
+ OccurrenceProcess,
26
+ OccurrenceProcessFamily,
27
+ ProcessAlternative,
28
+ ProcessError,
29
+ RuleOccurrence,
30
+ )
31
+
32
+ Carrier = tuple[str, Hashable]
33
+ EdgeCarrier = tuple[str, frozenset[Hashable]]
34
+
35
+
36
+ class HistoryIssueCode(str, Enum):
37
+ """Stable failures for occurrence-aware history derivation and replay."""
38
+
39
+ EMPTY = "HISTORY_EMPTY"
40
+ SCHEMA_MISMATCH = "HISTORY_SCHEMA_MISMATCH"
41
+ FLOW_LOOKUP = "HISTORY_FLOW_LOOKUP"
42
+ LINEAGE_CONFLICT = "HISTORY_LINEAGE_CONFLICT"
43
+ INVALID_EXTENSION = "HISTORY_INVALID_EXTENSION"
44
+ INVALID_CONFIGURATION = "HISTORY_INVALID_CONFIGURATION"
45
+ INVALID_STATE = "HISTORY_INVALID_STATE"
46
+ INACTIVE_INPUT = "HISTORY_INACTIVE_INPUT"
47
+ ACTIVE_OUTPUT = "HISTORY_ACTIVE_OUTPUT"
48
+ OUTER_RULE = "HISTORY_OUTER_RULE"
49
+ ROUNDTRIP = "HISTORY_ROUNDTRIP"
50
+ ALTERNATIVE_MISMATCH = "HISTORY_ALTERNATIVE_MISMATCH"
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class HistoryIssue:
55
+ code: HistoryIssueCode
56
+ message: str
57
+ context: Mapping[str, Any] = field(default_factory=dict)
58
+
59
+
60
+ class HistoryError(ValueError):
61
+ """Raised when a process cannot yield a lossless history object."""
62
+
63
+ def __init__(self, *issues: HistoryIssue):
64
+ self.issues = tuple(issues)
65
+ super().__init__("; ".join(issue.message for issue in issues))
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class CarrierState:
70
+ """One lineage state at a process cut; absence is explicit."""
71
+
72
+ present: bool
73
+ material_id: str | None = None
74
+ carrier: Hashable | frozenset[Hashable] | None = None
75
+ labels: tuple[tuple[str, Any], ...] = ()
76
+
77
+
78
+ ABSENT = CarrierState(False)
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class NodeLineage:
83
+ lineage_id: str
84
+ members: tuple[Carrier, ...]
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class EdgeLineage:
89
+ lineage_id: str
90
+ endpoints: frozenset[str]
91
+ members: tuple[EdgeCarrier, ...]
92
+
93
+
94
+ @dataclass(frozen=True)
95
+ class ConfigurationCut:
96
+ """One order ideal with decoded state and supplied carrier correspondence.
97
+
98
+ ``world`` uses persistent node-lineage IDs. Node and edge states retain the
99
+ currently active material/carrier, including explicit absence. No schedule
100
+ is selected and no linear extension is enumerated.
101
+ """
102
+
103
+ process_id: str
104
+ executed_events: frozenset[str]
105
+ active_material_ids: frozenset[str]
106
+ world: LewisLabelledGraph
107
+ node_states: tuple[tuple[NodeLineage, CarrierState], ...]
108
+ edge_states: tuple[tuple[EdgeLineage, CarrierState], ...]
109
+
110
+ def lineage_graph(self, *, include_absent: bool = True) -> nx.MultiGraph:
111
+ """Return lineage observations, optionally including absent carriers.
112
+
113
+ :param include_absent: Retain absent historical node and edge lineages.
114
+ :return: Fresh graph with one ``state`` observation per retained lineage.
115
+ """
116
+ graph = nx.MultiGraph()
117
+ graph.graph.update(
118
+ process_id=self.process_id,
119
+ executed_events=self.executed_events,
120
+ active_material_ids=self.active_material_ids,
121
+ projection="configuration",
122
+ )
123
+ for lineage, state in self.node_states:
124
+ if state.present or include_absent:
125
+ graph.add_node(lineage.lineage_id, members=lineage.members, state=state)
126
+ for lineage, state in self.edge_states:
127
+ if state.present or include_absent:
128
+ left, right = tuple(lineage.endpoints)
129
+ graph.add_edge(
130
+ left,
131
+ right,
132
+ key=lineage.lineage_id,
133
+ lineage_id=lineage.lineage_id,
134
+ members=lineage.members,
135
+ state=state,
136
+ )
137
+ return graph
138
+
139
+
140
+ @dataclass(frozen=True)
141
+ class HistoryReplay:
142
+ valid: bool
143
+ issues: tuple[HistoryIssue, ...] = ()
144
+
145
+
146
+ class _UnionFind:
147
+ def __init__(self, values: Iterable[Hashable]) -> None:
148
+ self.parent = {value: value for value in values}
149
+
150
+ def find(self, value: Hashable) -> Hashable:
151
+ parent = self.parent[value]
152
+ if parent != value:
153
+ self.parent[value] = self.find(parent)
154
+ return self.parent[value]
155
+
156
+ def union(self, left: Hashable, right: Hashable) -> None:
157
+ left_root, right_root = self.find(left), self.find(right)
158
+ if left_root == right_root:
159
+ return
160
+ if repr(left_root) <= repr(right_root):
161
+ self.parent[right_root] = left_root
162
+ else:
163
+ self.parent[left_root] = right_root
164
+
165
+ def groups(self) -> tuple[tuple[Hashable, ...], ...]:
166
+ grouped: dict[Hashable, list[Hashable]] = {}
167
+ for value in self.parent:
168
+ grouped.setdefault(self.find(value), []).append(value)
169
+ return tuple(
170
+ sorted(
171
+ (tuple(sorted(group, key=repr)) for group in grouped.values()),
172
+ key=repr,
173
+ )
174
+ )
175
+
176
+
177
+ def _binding_carrier(bindings: tuple[Any, ...], endpoint_node: Hashable) -> Carrier:
178
+ for binding in bindings:
179
+ mapping = dict(binding.mapping)
180
+ if endpoint_node in mapping:
181
+ return binding.material_id, mapping[endpoint_node]
182
+ raise HistoryError(
183
+ HistoryIssue(
184
+ HistoryIssueCode.FLOW_LOOKUP,
185
+ "No material binding covers a rule endpoint carrier.",
186
+ {"node": repr(endpoint_node)},
187
+ )
188
+ )
189
+
190
+
191
+ def _binding_edge(
192
+ bindings: tuple[Any, ...], endpoint_edge: frozenset[Hashable]
193
+ ) -> EdgeCarrier:
194
+ carriers = tuple(_binding_carrier(bindings, node) for node in endpoint_edge)
195
+ material_ids = {material for material, _ in carriers}
196
+ if len(material_ids) != 1:
197
+ raise HistoryError(
198
+ HistoryIssue(
199
+ HistoryIssueCode.FLOW_LOOKUP,
200
+ "One endpoint edge must belong to one material component.",
201
+ )
202
+ )
203
+ return next(iter(material_ids)), frozenset(node for _, node in carriers)
204
+
205
+
206
+ def _lineages(
207
+ materials: tuple[MaterialOccurrence, ...], events: tuple[RuleOccurrence, ...]
208
+ ) -> tuple[tuple[NodeLineage, ...], tuple[EdgeLineage, ...]]:
209
+ node_values = tuple(
210
+ (material.occurrence_id, node)
211
+ for material in materials
212
+ for node in material.value.node_ids
213
+ )
214
+ edge_values = tuple(
215
+ (material.occurrence_id, edge)
216
+ for material in materials
217
+ for edge in material.value.edge_keys
218
+ )
219
+ node_union = _UnionFind(node_values)
220
+ edge_union = _UnionFind(edge_values)
221
+ for event in events:
222
+ for interface in event.rule.interface.node_ids:
223
+ left_node = event.rule.left_arm.mapping[interface]
224
+ right_node = event.rule.right_arm.mapping[interface]
225
+ node_union.union(
226
+ _binding_carrier(event.inputs, left_node),
227
+ _binding_carrier(event.outputs, right_node),
228
+ )
229
+ for interface_edge in event.rule.interface.edge_keys:
230
+ left_edge = event.rule.left_arm.edge_mapping[interface_edge]
231
+ right_edge = event.rule.right_arm.edge_mapping[interface_edge]
232
+ edge_union.union(
233
+ _binding_edge(event.inputs, left_edge),
234
+ _binding_edge(event.outputs, right_edge),
235
+ )
236
+ node_lineages = tuple(
237
+ NodeLineage(f"node:{index}", group) # type: ignore[arg-type]
238
+ for index, group in enumerate(node_union.groups())
239
+ )
240
+ node_by_carrier = {
241
+ carrier: lineage.lineage_id
242
+ for lineage in node_lineages
243
+ for carrier in lineage.members
244
+ }
245
+ edge_lineages = []
246
+ material_by_id = {material.occurrence_id: material for material in materials}
247
+ for index, group in enumerate(edge_union.groups()):
248
+ endpoints: frozenset[str] | None = None
249
+ for material_id, edge in group:
250
+ candidate = frozenset(node_by_carrier[(material_id, node)] for node in edge)
251
+ if endpoints is None:
252
+ endpoints = candidate
253
+ elif endpoints != candidate:
254
+ raise HistoryError(
255
+ HistoryIssue(
256
+ HistoryIssueCode.LINEAGE_CONFLICT,
257
+ "A preserved edge lineage changes node lineage endpoints.",
258
+ {"material": material_id},
259
+ )
260
+ )
261
+ if edge not in material_by_id[material_id].value.edge_keys:
262
+ raise HistoryError(
263
+ HistoryIssue(
264
+ HistoryIssueCode.FLOW_LOOKUP,
265
+ "An edge lineage names an absent material edge.",
266
+ )
267
+ )
268
+ edge_lineages.append(
269
+ EdgeLineage(
270
+ f"edge:{index}",
271
+ endpoints or frozenset(),
272
+ group, # type: ignore[arg-type]
273
+ )
274
+ )
275
+ return node_lineages, tuple(edge_lineages)
276
+
277
+
278
+ @dataclass(frozen=True)
279
+ class OccurrenceMTG:
280
+ """A lossless event/material history plus compact carrier lineages."""
281
+
282
+ process_id: str
283
+ materials: tuple[MaterialOccurrence, ...]
284
+ events: tuple[RuleOccurrence, ...]
285
+ independence: tuple[IndependenceWitness, ...]
286
+ causal_pairs: tuple[tuple[str, str], ...]
287
+ node_lineages: tuple[NodeLineage, ...]
288
+ edge_lineages: tuple[EdgeLineage, ...]
289
+ empty_schema: LabelSchema | None = None
290
+
291
+ @classmethod
292
+ def empty(
293
+ cls, schema: LabelSchema, *, process_id: str = "empty"
294
+ ) -> "OccurrenceMTG":
295
+ """Return the empty history, typed by its graph schema.
296
+
297
+ :param schema: Schema for the empty boundary worlds.
298
+ :param process_id: Identity of this empty occurrence history.
299
+ :return: Typed monoidal unit with no events or material copies.
300
+ """
301
+ return cls(process_id, (), (), (), (), (), (), schema)
302
+
303
+ @property
304
+ def schema(self) -> LabelSchema:
305
+ """Return the common material schema or the declared empty schema."""
306
+ if self.materials:
307
+ return self.materials[0].value.schema
308
+ if self.empty_schema is not None:
309
+ return self.empty_schema
310
+ raise HistoryError(
311
+ HistoryIssue(HistoryIssueCode.EMPTY, "An empty history needs a schema.")
312
+ )
313
+
314
+ @classmethod
315
+ def from_process(
316
+ cls, process: OccurrenceProcess, *, empty_schema: LabelSchema | None = None
317
+ ) -> "OccurrenceMTG":
318
+ if not process.materials:
319
+ if not process.events and empty_schema is not None:
320
+ return cls.empty(empty_schema, process_id=process.process_id)
321
+ raise HistoryError(
322
+ HistoryIssue(
323
+ HistoryIssueCode.EMPTY,
324
+ "An occurrence MTG requires at least one material occurrence.",
325
+ )
326
+ )
327
+ schemas = {material.value.schema for material in process.materials}
328
+ if len(schemas) != 1:
329
+ raise HistoryError(
330
+ HistoryIssue(
331
+ HistoryIssueCode.SCHEMA_MISMATCH,
332
+ "All material occurrences in one MTG need one LLG schema.",
333
+ )
334
+ )
335
+ nodes, edges = _lineages(process.materials, process.events)
336
+ result = cls(
337
+ process.process_id,
338
+ process.materials,
339
+ process.events,
340
+ process.independence,
341
+ process.causal_pairs,
342
+ nodes,
343
+ edges,
344
+ )
345
+ replay = result.replay()
346
+ if not replay.valid:
347
+ raise HistoryError(*replay.issues)
348
+ return result
349
+
350
+ @property
351
+ def material_by_id(self) -> dict[str, MaterialOccurrence]:
352
+ return {material.occurrence_id: material for material in self.materials}
353
+
354
+ @property
355
+ def event_by_id(self) -> dict[str, RuleOccurrence]:
356
+ return {event.occurrence_id: event for event in self.events}
357
+
358
+ @property
359
+ def node_lineage_by_carrier(self) -> dict[Carrier, str]:
360
+ return {
361
+ carrier: lineage.lineage_id
362
+ for lineage in self.node_lineages
363
+ for carrier in lineage.members
364
+ }
365
+
366
+ def to_process(self) -> OccurrenceProcess:
367
+ """Reconstruct the complete process, including occurrence bindings."""
368
+ return OccurrenceProcess(
369
+ self.process_id, self.materials, self.events, self.independence
370
+ )
371
+
372
+ def replay(self) -> HistoryReplay:
373
+ issues = []
374
+ schemas_valid = (
375
+ len({m.value.schema for m in self.materials}) == 1
376
+ and self.empty_schema is None
377
+ if self.materials
378
+ else self.empty_schema is not None and not self.events
379
+ )
380
+ if not schemas_valid:
381
+ return HistoryReplay(
382
+ False,
383
+ (
384
+ HistoryIssue(
385
+ HistoryIssueCode.SCHEMA_MISMATCH,
386
+ "A history requires materials under one schema.",
387
+ ),
388
+ ),
389
+ )
390
+ state_issues = self._material_state_issues()
391
+ if state_issues:
392
+ return HistoryReplay(False, state_issues)
393
+ try:
394
+ process = self.to_process()
395
+ except ProcessError as error:
396
+ issues.append(
397
+ HistoryIssue(
398
+ HistoryIssueCode.ROUNDTRIP,
399
+ "The retained occurrence process no longer validates.",
400
+ {"issues": tuple(issue.to_dict() for issue in error.issues)},
401
+ )
402
+ )
403
+ return HistoryReplay(False, tuple(issues))
404
+ observed_nodes = Counter(
405
+ carrier for lineage in self.node_lineages for carrier in lineage.members
406
+ )
407
+ expected_nodes = Counter(
408
+ (material.occurrence_id, node)
409
+ for material in self.materials
410
+ for node in material.value.node_ids
411
+ )
412
+ observed_edges = Counter(
413
+ carrier for lineage in self.edge_lineages for carrier in lineage.members
414
+ )
415
+ expected_edges = Counter(
416
+ (material.occurrence_id, edge)
417
+ for material in self.materials
418
+ for edge in material.value.edge_keys
419
+ )
420
+ if observed_nodes != expected_nodes or observed_edges != expected_edges:
421
+ issues.append(
422
+ HistoryIssue(
423
+ HistoryIssueCode.ROUNDTRIP,
424
+ "Carrier lineages do not cover every material carrier exactly.",
425
+ )
426
+ )
427
+ if process.causal_pairs != self.causal_pairs:
428
+ issues.append(
429
+ HistoryIssue(
430
+ HistoryIssueCode.ROUNDTRIP,
431
+ "Causal dependencies changed during reconstruction.",
432
+ )
433
+ )
434
+ if observed_nodes == expected_nodes and observed_edges == expected_edges:
435
+ issues.extend(self._lineage_issues())
436
+ return HistoryReplay(not issues, tuple(issues))
437
+
438
+ def _material_state_issues(self) -> tuple[HistoryIssue, ...]:
439
+ if not is_electron_schema(self.schema):
440
+ return ()
441
+ issues = []
442
+ for material in self.materials:
443
+ try:
444
+ derive_electron_labeled_graph(material.value.to_networkx())
445
+ except LLGError as error:
446
+ issues.append(
447
+ HistoryIssue(
448
+ HistoryIssueCode.INVALID_STATE,
449
+ "A material's derived electron state disagrees with its authoritative coordinates.",
450
+ {
451
+ "material": material.occurrence_id,
452
+ "issues": tuple(issue.to_dict() for issue in error.issues),
453
+ },
454
+ )
455
+ )
456
+ return tuple(issues)
457
+
458
+ def _lineage_issues(self) -> tuple[HistoryIssue, ...]:
459
+ expected_nodes, expected_edges = _lineages(self.materials, self.events)
460
+ expected_groups = {frozenset(n.members): n.lineage_id for n in expected_nodes}
461
+ node_groups = {frozenset(n.members) for n in self.node_lineages}
462
+ edge_groups = {frozenset(e.members) for e in self.edge_lineages}
463
+ if (
464
+ node_groups != set(expected_groups)
465
+ or len(node_groups) != len(self.node_lineages)
466
+ or len({n.lineage_id for n in self.node_lineages})
467
+ != len(self.node_lineages)
468
+ or edge_groups != {frozenset(e.members) for e in expected_edges}
469
+ or len(edge_groups) != len(self.edge_lineages)
470
+ or len({e.lineage_id for e in self.edge_lineages})
471
+ != len(self.edge_lineages)
472
+ ):
473
+ return (
474
+ HistoryIssue(
475
+ HistoryIssueCode.ROUNDTRIP,
476
+ "Lineage partitions or identifiers disagree with supplied preservation maps.",
477
+ ),
478
+ )
479
+ transported = {
480
+ n.lineage_id: expected_groups[frozenset(n.members)]
481
+ for n in self.node_lineages
482
+ }
483
+ endpoints = {frozenset(e.members): e.endpoints for e in expected_edges}
484
+ for edge in self.edge_lineages:
485
+ if (
486
+ not edge.endpoints <= set(transported)
487
+ or frozenset(transported[node] for node in edge.endpoints)
488
+ != endpoints[frozenset(edge.members)]
489
+ ):
490
+ return (
491
+ HistoryIssue(
492
+ HistoryIssueCode.ROUNDTRIP,
493
+ "An edge lineage does not have its preserved carrier endpoints.",
494
+ {"lineage": edge.lineage_id},
495
+ ),
496
+ )
497
+ return ()
498
+
499
+ def configuration_cut(self, executed_events: Iterable[str]) -> ConfigurationCut:
500
+ """Decode a downward-closed event set without choosing a schedule.
501
+
502
+ :param executed_events: Set of completed event occurrence IDs.
503
+ :return: Decoded cut, active materials, and complete lineage observations.
504
+ :raises HistoryError: If the history is invalid or the set is not an ideal.
505
+ """
506
+ replay = self.replay()
507
+ if not replay.valid:
508
+ raise HistoryError(*replay.issues)
509
+ executed = frozenset(executed_events)
510
+ unknown = executed - set(self.event_by_id)
511
+ missing = {
512
+ first
513
+ for first, second in self.causal_pairs
514
+ if second in executed and first not in executed
515
+ }
516
+ if unknown or missing:
517
+ raise HistoryError(
518
+ HistoryIssue(
519
+ HistoryIssueCode.INVALID_CONFIGURATION,
520
+ "A configuration must be a downward-closed set of known event IDs.",
521
+ {
522
+ "unknown": tuple(sorted(unknown, key=repr)),
523
+ "missing_predecessors": tuple(sorted(missing)),
524
+ },
525
+ )
526
+ )
527
+ produced = {
528
+ b.material_id: e.occurrence_id for e in self.events for b in e.outputs
529
+ }
530
+ consumed = {
531
+ b.material_id: e.occurrence_id for e in self.events for b in e.inputs
532
+ }
533
+ active = frozenset(
534
+ m.occurrence_id
535
+ for m in self.materials
536
+ if (
537
+ m.occurrence_id not in produced or produced[m.occurrence_id] in executed
538
+ )
539
+ and (
540
+ m.occurrence_id not in consumed
541
+ or consumed[m.occurrence_id] not in executed
542
+ )
543
+ )
544
+ nodes = tuple((n, self._node_state(n, active)) for n in self.node_lineages)
545
+ edges = tuple((e, self._edge_state(e, active)) for e in self.edge_lineages)
546
+ world = self._decode_cut(nodes, edges)
547
+ return ConfigurationCut(self.process_id, executed, active, world, nodes, edges)
548
+
549
+ def _decode_cut(
550
+ self,
551
+ nodes: tuple[tuple[NodeLineage, CarrierState], ...],
552
+ edges: tuple[tuple[EdgeLineage, CarrierState], ...],
553
+ ) -> LewisLabelledGraph:
554
+ graph = nx.Graph()
555
+ materials = self.material_by_id
556
+ for lineage, state in nodes:
557
+ if state.present:
558
+ graph.add_node(
559
+ lineage.lineage_id,
560
+ **materials[state.material_id].value.node_labels(state.carrier),
561
+ )
562
+ for lineage, state in edges:
563
+ if not state.present:
564
+ continue
565
+ endpoints = tuple(lineage.endpoints)
566
+ if len(endpoints) != 2 or not set(endpoints) <= set(graph):
567
+ raise HistoryError(
568
+ HistoryIssue(
569
+ HistoryIssueCode.LINEAGE_CONFLICT,
570
+ "An active edge lacks distinct active endpoints.",
571
+ )
572
+ )
573
+ if graph.has_edge(*endpoints):
574
+ raise HistoryError(
575
+ HistoryIssue(
576
+ HistoryIssueCode.LINEAGE_CONFLICT,
577
+ "Two active edge lineages occupy one simple bond.",
578
+ )
579
+ )
580
+ graph.add_edge(
581
+ *endpoints,
582
+ **materials[state.material_id].value.edge_labels(state.carrier),
583
+ )
584
+ return LewisLabelledGraph.from_networkx(
585
+ (
586
+ derive_electron_labeled_graph(graph)
587
+ if is_electron_schema(self.schema)
588
+ else graph
589
+ ),
590
+ self.schema,
591
+ name=f"{self.process_id}:configuration",
592
+ )
593
+
594
+ def _flow_roles(self) -> tuple[set[str], set[str]]:
595
+ produced = {
596
+ binding.material_id for event in self.events for binding in event.outputs
597
+ }
598
+ consumed = {
599
+ binding.material_id for event in self.events for binding in event.inputs
600
+ }
601
+ return produced, consumed
602
+
603
+ @property
604
+ def initial_material_ids(self) -> frozenset[str]:
605
+ produced, _ = self._flow_roles()
606
+ return frozenset(set(self.material_by_id) - produced)
607
+
608
+ @property
609
+ def final_material_ids(self) -> frozenset[str]:
610
+ _, consumed = self._flow_roles()
611
+ return frozenset(set(self.material_by_id) - consumed)
612
+
613
+ def _active_cuts(self, extension: Iterable[str]) -> tuple[frozenset[str], ...]:
614
+ process = self.to_process()
615
+ order = tuple(extension)
616
+ if not process.is_linear_extension(order):
617
+ raise HistoryError(
618
+ HistoryIssue(
619
+ HistoryIssueCode.INVALID_EXTENSION,
620
+ "A history timeline requires a valid process linear extension.",
621
+ )
622
+ )
623
+ active = set(self.initial_material_ids)
624
+ cuts = [frozenset(active)]
625
+ for event_id in order:
626
+ event = self.event_by_id[event_id]
627
+ inputs = {binding.material_id for binding in event.inputs}
628
+ outputs = {binding.material_id for binding in event.outputs}
629
+ if not inputs <= active:
630
+ raise HistoryError(
631
+ HistoryIssue(
632
+ HistoryIssueCode.INACTIVE_INPUT,
633
+ "An event consumes material absent at its process cut.",
634
+ {
635
+ "event": event_id,
636
+ "materials": tuple(sorted(inputs - active)),
637
+ },
638
+ )
639
+ )
640
+ if outputs & active:
641
+ raise HistoryError(
642
+ HistoryIssue(
643
+ HistoryIssueCode.ACTIVE_OUTPUT,
644
+ "An event produces an already active material occurrence.",
645
+ {"event": event_id},
646
+ )
647
+ )
648
+ active.difference_update(inputs)
649
+ active.update(outputs)
650
+ cuts.append(frozenset(active))
651
+ return tuple(cuts)
652
+
653
+ def _node_state(self, lineage: NodeLineage, active: frozenset[str]) -> CarrierState:
654
+ members = tuple(member for member in lineage.members if member[0] in active)
655
+ if not members:
656
+ return ABSENT
657
+ if len(members) != 1:
658
+ raise HistoryError(
659
+ HistoryIssue(
660
+ HistoryIssueCode.LINEAGE_CONFLICT,
661
+ "A process cut contains two active copies of one node lineage.",
662
+ {"lineage": lineage.lineage_id},
663
+ )
664
+ )
665
+ material_id, node = members[0]
666
+ labels = tuple(
667
+ sorted(
668
+ self.material_by_id[material_id]
669
+ .value.node_labels(node, semantic=True)
670
+ .items()
671
+ )
672
+ )
673
+ return CarrierState(True, material_id, node, labels)
674
+
675
+ def _edge_state(self, lineage: EdgeLineage, active: frozenset[str]) -> CarrierState:
676
+ members = tuple(member for member in lineage.members if member[0] in active)
677
+ if not members:
678
+ return ABSENT
679
+ if len(members) != 1:
680
+ raise HistoryError(
681
+ HistoryIssue(
682
+ HistoryIssueCode.LINEAGE_CONFLICT,
683
+ "A process cut contains two active copies of one edge lineage.",
684
+ {"lineage": lineage.lineage_id},
685
+ )
686
+ )
687
+ material_id, edge = members[0]
688
+ labels = tuple(
689
+ sorted(
690
+ self.material_by_id[material_id]
691
+ .value.edge_labels(edge, semantic=True)
692
+ .items()
693
+ )
694
+ )
695
+ return CarrierState(True, material_id, edge, labels)
696
+
697
+ def full_history(self, extension: Iterable[str]) -> nx.MultiGraph:
698
+ """Project all node/edge lineages over one certified linear extension."""
699
+ replay = self.replay()
700
+ if not replay.valid:
701
+ raise HistoryError(*replay.issues)
702
+ order = tuple(extension)
703
+ cuts = self._active_cuts(order)
704
+ graph = nx.MultiGraph()
705
+ graph.graph.update(
706
+ process_id=self.process_id,
707
+ event_order=order,
708
+ causal_pairs=self.to_process().causal_pairs,
709
+ cut_count=len(cuts),
710
+ )
711
+ for lineage in self.node_lineages:
712
+ graph.add_node(
713
+ lineage.lineage_id,
714
+ members=lineage.members,
715
+ history=tuple(self._node_state(lineage, cut) for cut in cuts),
716
+ )
717
+ for lineage in self.edge_lineages:
718
+ if len(lineage.endpoints) != 2:
719
+ raise HistoryError(
720
+ HistoryIssue(
721
+ HistoryIssueCode.LINEAGE_CONFLICT,
722
+ "A simple edge lineage must have two node endpoints.",
723
+ )
724
+ )
725
+ left, right = tuple(lineage.endpoints)
726
+ graph.add_edge(
727
+ left,
728
+ right,
729
+ key=lineage.lineage_id,
730
+ lineage_id=lineage.lineage_id,
731
+ members=lineage.members,
732
+ history=tuple(self._edge_state(lineage, cut) for cut in cuts),
733
+ )
734
+ return graph
735
+
736
+ def _aggregate(self, material_ids: frozenset[str], name: str) -> LewisLabelledGraph:
737
+ schema = self.schema
738
+ graph = nx.Graph()
739
+ for material_id in sorted(material_ids):
740
+ material = self.material_by_id[material_id]
741
+ for node in material.value.node_ids:
742
+ graph.add_node((material_id, node), **material.value.node_labels(node))
743
+ for edge in material.value.edge_keys:
744
+ left, right = tuple(edge)
745
+ graph.add_edge(
746
+ (material_id, left),
747
+ (material_id, right),
748
+ **material.value.edge_labels(edge),
749
+ )
750
+ return LewisLabelledGraph.from_networkx(graph, schema, name=name)
751
+
752
+ def outer_rule(self) -> RuleSpan:
753
+ """Return the initial-to-final transformation induced by carrier lineages."""
754
+ replay = self.replay()
755
+ if not replay.valid:
756
+ raise HistoryError(*replay.issues)
757
+ left = self._aggregate(self.initial_material_ids, f"{self.process_id}:initial")
758
+ right = self._aggregate(self.final_material_ids, f"{self.process_id}:final")
759
+ preserved = {}
760
+ for lineage in self.node_lineages:
761
+ initial = tuple(
762
+ member
763
+ for member in lineage.members
764
+ if member[0] in self.initial_material_ids
765
+ )
766
+ final = tuple(
767
+ member
768
+ for member in lineage.members
769
+ if member[0] in self.final_material_ids
770
+ )
771
+ if len(initial) == len(final) == 1:
772
+ preserved[initial[0]] = final[0]
773
+ elif len(initial) > 1 or len(final) > 1:
774
+ raise HistoryError(
775
+ HistoryIssue(
776
+ HistoryIssueCode.LINEAGE_CONFLICT,
777
+ "Outer projection has multiple boundary copies in one lineage.",
778
+ )
779
+ )
780
+ preserved_edges = set()
781
+ for lineage in self.edge_lineages:
782
+ initial = tuple(
783
+ member
784
+ for member in lineage.members
785
+ if member[0] in self.initial_material_ids
786
+ )
787
+ final = tuple(
788
+ member
789
+ for member in lineage.members
790
+ if member[0] in self.final_material_ids
791
+ )
792
+ if len(initial) == len(final) == 1:
793
+ material_id, edge = initial[0]
794
+ preserved_edges.add(frozenset((material_id, node) for node in edge))
795
+ elif len(initial) > 1 or len(final) > 1:
796
+ raise HistoryError(
797
+ HistoryIssue(
798
+ HistoryIssueCode.LINEAGE_CONFLICT,
799
+ "Outer projection has multiple boundary edges in one lineage.",
800
+ )
801
+ )
802
+ try:
803
+ return RuleSpan.from_mapping(
804
+ left,
805
+ right,
806
+ preserved,
807
+ preserved_edges=preserved_edges,
808
+ name=f"{self.process_id}:outer",
809
+ )
810
+ except DPOError as error:
811
+ raise HistoryError(
812
+ HistoryIssue(
813
+ HistoryIssueCode.OUTER_RULE,
814
+ "The history boundary does not form an admitted outer rule.",
815
+ {"issues": tuple(issue.to_dict() for issue in error.issues)},
816
+ )
817
+ ) from error
818
+
819
+ def composed_change_graph(self) -> nx.MultiGraph:
820
+ """Project only initial/final lineage states, analogous to a composed ITS."""
821
+ initial = self.configuration_cut(())
822
+ final = self.configuration_cut(self.event_by_id)
823
+ final_nodes = {n.lineage_id: state for n, state in final.node_states}
824
+ final_edges = {e.lineage_id: state for e, state in final.edge_states}
825
+ result = nx.MultiGraph()
826
+ result.graph.update(process_id=self.process_id, projection="composed")
827
+ for lineage, state in initial.node_states:
828
+ result.add_node(
829
+ lineage.lineage_id, left=state, right=final_nodes[lineage.lineage_id]
830
+ )
831
+ for lineage, state in initial.edge_states:
832
+ final_state = final_edges[lineage.lineage_id]
833
+ if state.present or final_state.present:
834
+ left, right = tuple(lineage.endpoints)
835
+ result.add_edge(
836
+ left,
837
+ right,
838
+ key=lineage.lineage_id,
839
+ left=state,
840
+ right=final_state,
841
+ )
842
+ return result
843
+
844
+ def minimal_changed_core(self, extension: Iterable[str]) -> nx.MultiGraph:
845
+ """Retain every carrier whose full history changes, including transients."""
846
+ full = self.full_history(extension)
847
+ changed_edges = []
848
+ changed_nodes = set()
849
+ for left, right, key, attrs in full.edges(keys=True, data=True):
850
+ signatures = {(state.present, state.labels) for state in attrs["history"]}
851
+ if len(signatures) > 1:
852
+ changed_edges.append((left, right, key, attrs))
853
+ changed_nodes.update((left, right))
854
+ for node, attrs in full.nodes(data=True):
855
+ signatures = {(state.present, state.labels) for state in attrs["history"]}
856
+ if len(signatures) > 1:
857
+ changed_nodes.add(node)
858
+ result = nx.MultiGraph()
859
+ result.graph.update(
860
+ process_id=self.process_id, projection="minimal-changed-core"
861
+ )
862
+ for node in sorted(changed_nodes):
863
+ result.add_node(node, **full.nodes[node])
864
+ for left, right, key, attrs in changed_edges:
865
+ result.add_edge(left, right, key=key, **attrs)
866
+ return result
867
+
868
+
869
+ @dataclass(frozen=True)
870
+ class OccurrenceMTGAlternative:
871
+ alternative_id: str
872
+ history: OccurrenceMTG
873
+
874
+
875
+ @dataclass(frozen=True)
876
+ class OccurrenceMTGFamily:
877
+ """One MTG per process alternative; no material-flow quotient is applied."""
878
+
879
+ alternatives: tuple[OccurrenceMTGAlternative, ...]
880
+ choices: tuple[ChoiceWitness, ...]
881
+
882
+ @classmethod
883
+ def from_process_family(
884
+ cls, family: OccurrenceProcessFamily
885
+ ) -> "OccurrenceMTGFamily":
886
+ alternatives = tuple(
887
+ OccurrenceMTGAlternative(
888
+ alternative.alternative_id,
889
+ OccurrenceMTG.from_process(alternative.process),
890
+ )
891
+ for alternative in family.alternatives
892
+ )
893
+ result = cls(alternatives, family.choices)
894
+ if {item.alternative_id for item in alternatives} != {
895
+ item.alternative_id for item in family.alternatives
896
+ }:
897
+ raise HistoryError(
898
+ HistoryIssue(
899
+ HistoryIssueCode.ALTERNATIVE_MISMATCH,
900
+ "MTG derivation changed the process alternative family.",
901
+ )
902
+ )
903
+ return result
904
+
905
+ def to_process_family(self) -> OccurrenceProcessFamily:
906
+ return OccurrenceProcessFamily(
907
+ tuple(
908
+ ProcessAlternative(item.alternative_id, item.history.to_process())
909
+ for item in self.alternatives
910
+ ),
911
+ self.choices,
912
+ )
913
+
914
+
915
+ __all__ = [
916
+ "ABSENT",
917
+ "CarrierState",
918
+ "ConfigurationCut",
919
+ "EdgeLineage",
920
+ "HistoryError",
921
+ "HistoryIssue",
922
+ "HistoryIssueCode",
923
+ "HistoryReplay",
924
+ "NodeLineage",
925
+ "OccurrenceMTG",
926
+ "OccurrenceMTGAlternative",
927
+ "OccurrenceMTGFamily",
928
+ ]