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.
- synkit/CRN/Benchmark/__init__.py +53 -0
- synkit/CRN/Benchmark/crosschecks.py +13 -0
- synkit/CRN/Benchmark/kegg.py +12 -0
- synkit/CRN/Benchmark/networks.py +13 -0
- synkit/CRN/Benchmark/scaling.py +13 -0
- synkit/CRN/Benchmark/validate.py +13 -0
- synkit/CRN/Construct/__init__.py +16 -0
- synkit/CRN/Construct/abstract.py +668 -0
- synkit/CRN/Construct/arity.py +36 -0
- synkit/CRN/Construct/builder.py +619 -0
- synkit/CRN/Construct/derivation.py +63 -0
- synkit/CRN/Construct/flattener.py +100 -0
- synkit/CRN/Construct/keys.py +14 -0
- synkit/CRN/Construct/mixtures.py +117 -0
- synkit/CRN/Construct/rdkit_worker.py +69 -0
- synkit/CRN/Construct/smiles.py +99 -0
- synkit/CRN/Construct/state.py +31 -0
- synkit/CRN/Construct/strategy.py +75 -0
- synkit/CRN/Construct/worker.py +42 -0
- synkit/CRN/IO/__init__.py +36 -0
- synkit/CRN/IO/sbml.py +925 -0
- synkit/CRN/Pathway/__init__.py +72 -0
- synkit/CRN/Pathway/_adapter.py +333 -0
- synkit/CRN/Pathway/pathfinder.py +500 -0
- synkit/CRN/Pathway/reachability.py +796 -0
- synkit/CRN/Pathway/realizability.py +1322 -0
- synkit/CRN/Petrinet/__init__.py +93 -0
- synkit/CRN/Petrinet/analyzer.py +486 -0
- synkit/CRN/Petrinet/minimal_semiflows.py +436 -0
- synkit/CRN/Petrinet/net.py +1041 -0
- synkit/CRN/Petrinet/persistence.py +97 -0
- synkit/CRN/Petrinet/semiflows.py +483 -0
- synkit/CRN/Petrinet/siphon_search.py +402 -0
- synkit/CRN/Petrinet/structure.py +286 -0
- synkit/CRN/Props/__init__.py +130 -0
- synkit/CRN/Props/_rank.py +29 -0
- synkit/CRN/Props/deficiency.py +953 -0
- synkit/CRN/Props/dynamics.py +832 -0
- synkit/CRN/Props/helper.py +151 -0
- synkit/CRN/Props/stoich.py +751 -0
- synkit/CRN/Props/thermo.py +584 -0
- synkit/CRN/Query/__init__.py +66 -0
- synkit/CRN/Query/cache.py +41 -0
- synkit/CRN/Query/kegg_api.py +99 -0
- synkit/CRN/Query/kegg_extract.py +714 -0
- synkit/CRN/Query/kegg_impute.py +707 -0
- synkit/CRN/Query/kegg_parse.py +480 -0
- synkit/CRN/Query/to_syncrn.py +290 -0
- synkit/CRN/README.md +234 -0
- synkit/CRN/Structure/__init__.py +12 -0
- synkit/CRN/Structure/_graph_io.py +713 -0
- synkit/CRN/Structure/_matrices.py +88 -0
- synkit/CRN/Structure/_parse.py +544 -0
- synkit/CRN/Structure/reaction.py +237 -0
- synkit/CRN/Structure/rule.py +55 -0
- synkit/CRN/Structure/species.py +46 -0
- synkit/CRN/Structure/syncrn.py +929 -0
- synkit/CRN/Symmetry/__init__.py +20 -0
- synkit/CRN/Symmetry/_common.py +1059 -0
- synkit/CRN/Symmetry/_ir.py +940 -0
- synkit/CRN/Symmetry/automorphism.py +472 -0
- synkit/CRN/Symmetry/canon.py +441 -0
- synkit/CRN/Symmetry/isomorphism.py +412 -0
- synkit/CRN/Symmetry/symmetry.py +61 -0
- synkit/CRN/Symmetry/wl_canon.py +967 -0
- synkit/CRN/Visualize/__init__.py +11 -0
- synkit/CRN/Visualize/labels.py +113 -0
- synkit/CRN/Visualize/layout.py +708 -0
- synkit/CRN/Visualize/palette.py +292 -0
- synkit/CRN/Visualize/validation.py +122 -0
- synkit/CRN/Visualize/vis.py +1152 -0
- synkit/CRN/__init__.py +233 -0
- synkit/CRN/_build.py +50 -0
- synkit/CRN/_native_build.py +175 -0
- synkit/CRN/analysis.py +629 -0
- synkit/CRN/backend.py +74 -0
- synkit/CRN/cpp/CMakeLists.txt +58 -0
- synkit/CRN/cpp/algebra.cpp +46 -0
- synkit/CRN/cpp/closure.cpp +29 -0
- synkit/CRN/cpp/exact.h +55 -0
- synkit/CRN/cpp/pathway.cpp +46 -0
- synkit/CRN/cpp/semiflows.cpp +209 -0
- synkit/CRN/cpp/structural.cpp +240 -0
- synkit/CRN/cpp/symmetry.cpp +45 -0
- synkit/CRN/cpp/write_manifest.cmake +28 -0
- synkit/CRN/kinds.py +137 -0
- synkit/CRN/lib/synkit_crn.dll +0 -0
- synkit/CRN/lib/synkit_crn.json +33 -0
- synkit/CRN/native.py +388 -0
- synkit/CRN/native_analysis.py +459 -0
- synkit/Chem/Cluster/__init__.py +0 -0
- synkit/Chem/Cluster/butina.py +140 -0
- synkit/Chem/Fingerprint/__init__.py +0 -0
- synkit/Chem/Fingerprint/fp_calculator.py +155 -0
- synkit/Chem/Fingerprint/smiles_featurizer.py +258 -0
- synkit/Chem/Fingerprint/transformation_fp.py +139 -0
- synkit/Chem/Mapper/README.rst +43 -0
- synkit/Chem/Mapper/__init__.py +255 -0
- synkit/Chem/Mapper/_compat.py +130 -0
- synkit/Chem/Mapper/adapters/__init__.py +1 -0
- synkit/Chem/Mapper/adapters/prediction.py +126 -0
- synkit/Chem/Mapper/adapters/template.py +412 -0
- synkit/Chem/Mapper/chem/__init__.py +52 -0
- synkit/Chem/Mapper/chem/aam.py +673 -0
- synkit/Chem/Mapper/chem/blind.py +160 -0
- synkit/Chem/Mapper/chem/its.py +309 -0
- synkit/Chem/Mapper/chem/smiles.py +564 -0
- synkit/Chem/Mapper/graph/__init__.py +66 -0
- synkit/Chem/Mapper/graph/automorphism.py +395 -0
- synkit/Chem/Mapper/graph/blockcut.py +98 -0
- synkit/Chem/Mapper/graph/labeled_graph.py +104 -0
- synkit/Chem/Mapper/graph/refine.py +176 -0
- synkit/Chem/Mapper/graph/synkit_adapter.py +110 -0
- synkit/Chem/Mapper/io/__init__.py +13 -0
- synkit/Chem/Mapper/io/idxmapstr.py +82 -0
- synkit/Chem/Mapper/mapper.py +685 -0
- synkit/Chem/Mapper/reaction_search.py +554 -0
- synkit/Chem/Mapper/reporting/__init__.py +1 -0
- synkit/Chem/Mapper/reporting/alternatives.py +451 -0
- synkit/Chem/Mapper/reporting/analysis.py +681 -0
- synkit/Chem/Mapper/reporting/native_analysis.py +485 -0
- synkit/Chem/Mapper/reporting/spectrum.py +642 -0
- synkit/Chem/Mapper/solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/__init__.py +141 -0
- synkit/Chem/Mapper/solvers/exact/assignment/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_certificate.py +113 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_domains.py +162 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_verification.py +58 -0
- synkit/Chem/Mapper/solvers/exact/assignment/incremental_assignment.py +247 -0
- synkit/Chem/Mapper/solvers/exact/certificate.py +128 -0
- synkit/Chem/Mapper/solvers/exact/core.py +183 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/bounds.py +319 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/exhaustive.py +179 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/inputs.py +283 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/records.py +242 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/search.py +1025 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/verify.py +378 -0
- synkit/Chem/Mapper/solvers/exact/edit_support.py +375 -0
- synkit/Chem/Mapper/solvers/exact/hybrid.py +224 -0
- synkit/Chem/Mapper/solvers/exact/hydrogen.py +264 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/branching.py +499 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumerate.py +1050 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumeration_result.py +30 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/kernel.py +256 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/milp.py +194 -0
- synkit/Chem/Mapper/solvers/exact/native/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/native/build.py +154 -0
- synkit/Chem/Mapper/solvers/exact/native/candidates.py +593 -0
- synkit/Chem/Mapper/solvers/exact/native/canonical.py +396 -0
- synkit/Chem/Mapper/solvers/exact/native/disk_frontier.py +415 -0
- synkit/Chem/Mapper/solvers/exact/native/frontier.py +390 -0
- synkit/Chem/Mapper/solvers/exact/native/hydrogen_quotient.py +213 -0
- synkit/Chem/Mapper/solvers/exact/native/its.py +528 -0
- synkit/Chem/Mapper/solvers/exact/native/native_distance.cpp +2803 -0
- synkit/Chem/Mapper/solvers/exact/native/orbit_aggregation.py +328 -0
- synkit/Chem/Mapper/solvers/exact/native/parallel.py +205 -0
- synkit/Chem/Mapper/solvers/exact/native/pmcd.py +203 -0
- synkit/Chem/Mapper/solvers/exact/native/runtime.py +40 -0
- synkit/Chem/Mapper/solvers/exact/native/search.py +294 -0
- synkit/Chem/Mapper/solvers/exact/native/separator.py +224 -0
- synkit/Chem/Mapper/solvers/exact/native_build.py +12 -0
- synkit/Chem/Mapper/solvers/exact/pabs/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/pabs/cost_lattice.py +43 -0
- synkit/Chem/Mapper/solvers/exact/pabs/factor_spectrum.py +110 -0
- synkit/Chem/Mapper/solvers/exact/pabs/multi_shell.py +368 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation.py +679 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_bounds.py +436 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_limits.py +13 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_search.py +856 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_symmetry.py +66 -0
- synkit/Chem/Mapper/solvers/exact/pabs/residual_typed_bonds.py +136 -0
- synkit/Chem/Mapper/solvers/exact/pabs/reward_frontier.py +201 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_bound.py +258 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_spectrum.py +308 -0
- synkit/Chem/Mapper/solvers/exact/pabs/sparse_reward_bound.py +207 -0
- synkit/Chem/Mapper/solvers/exact/pabs/suffix_spectrum.py +299 -0
- synkit/Chem/Mapper/solvers/exact/search.py +55 -0
- synkit/Chem/Mapper/solvers/exact/seeds/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed.py +104 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_fragments.py +148 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_relaxation.py +181 -0
- synkit/Chem/Mapper/solvers/exact/symmetry.py +223 -0
- synkit/Chem/Mapper/solvers/native/__init__.py +4 -0
- synkit/Chem/Mapper/solvers/native/assignment.py +99 -0
- synkit/Chem/Mapper/solvers/native/astar.py +22 -0
- synkit/Chem/Mapper/solvers/native/build.py +385 -0
- synkit/Chem/Mapper/solvers/native/classification.py +641 -0
- synkit/Chem/Mapper/solvers/native/core.py +378 -0
- synkit/Chem/Mapper/solvers/native/cpp/CMakeLists.txt +120 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/map_reaction.cpp +29 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/slap_graph.cpp +63 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/assignment/assignment.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/astar.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/bound.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/pmcd_witness.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/runtime.h +130 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/hydrogen/hydrogen.h +73 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mapper.h +175 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mces/mces.h +9 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/pabs/seed.h +34 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/reaction/reaction.h +30 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/sepdp/sepdp.h +21 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/slap.h +72 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/solvers/solvers.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/classification.h +140 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/cpabs.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/assignment/abb.cpp +302 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/CMakeLists.txt +12 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/README.md +47 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar.cpp +228 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar_graph.cpp +37 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/bound.cpp +212 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/dispatch.cpp +272 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/pmcd_witness.cpp +36 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/hydrogen/hydrogen.cpp +380 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/README.md +57 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/mces.cpp +287 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/bridge.cpp +334 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/chemical_floor.inc +181 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/endpoint_symmetry.inc +104 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/exact_helpers.h +20 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_assignment_certificate.h +165 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_certificate.h +125 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_model.h +365 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_provider.inc +319 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_rounding.inc +170 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_test_hooks.h +342 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/objective_reduction.inc +137 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/overlap_dual.inc +351 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/profile_seed.inc +214 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/seed_heuristics.inc +640 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_embedding.cpp +430 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_root_lp.h +176 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/test_hooks.inc +262 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/reaction/reaction.cpp +611 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/README.md +61 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/sepdp.cpp +253 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/slap.cpp +810 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/formulation.h +138 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/maxsat.cpp +526 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/ortools.cpp +475 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/seed.h +80 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/weighted_cnf.h +256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/README.md +43 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/dfs.cpp +123 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/edit_support.cpp +224 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification.cpp +553 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_pipeline.cpp +124 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_stream.h +13 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/cpabs.cpp +455 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/edge_deck.cpp +743 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naugraph.c +732 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.c +94 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.h +49 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.c +1771 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.h +133 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nautil.c +750 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naututil.h +355 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.c +1256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.h +1544 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.c +1131 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.h +70 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/sorttemplates.c +580 -0
- synkit/Chem/Mapper/solvers/native/hydrogen.py +711 -0
- synkit/Chem/Mapper/solvers/native/implicit.py +506 -0
- synkit/Chem/Mapper/solvers/native/implicit_certificate.py +187 -0
- synkit/Chem/Mapper/solvers/native/implicit_preparation.py +309 -0
- synkit/Chem/Mapper/solvers/native/lib/synkit_mapper.dll +0 -0
- synkit/Chem/Mapper/solvers/native/mces.py +16 -0
- synkit/Chem/Mapper/solvers/native/reaction.py +179 -0
- synkit/Chem/Mapper/solvers/native/reaction_build.py +277 -0
- synkit/Chem/Mapper/solvers/native/reaction_text.py +26 -0
- synkit/Chem/Mapper/solvers/native/runtime.py +242 -0
- synkit/Chem/Mapper/solvers/native/sepdp.py +47 -0
- synkit/Chem/Mapper/solvers/native/slap.py +266 -0
- synkit/Chem/Mapper/solvers/native/solvers.py +67 -0
- synkit/Chem/Mapper/solvers/native/stereo.py +86 -0
- synkit/Chem/Mapper/solvers/native/supporting.py +34 -0
- synkit/Chem/Mapper/solvers/native/symmetry.py +91 -0
- synkit/Chem/Mapper/solvers/slap/__init__.py +29 -0
- synkit/Chem/Mapper/solvers/slap/lap.py +174 -0
- synkit/Chem/Mapper/solvers/slap/sequential.py +696 -0
- synkit/Chem/Mapper/validation/__init__.py +1 -0
- synkit/Chem/Mapper/validation/aam_validator.py +442 -0
- synkit/Chem/Mapper/validation/annotations.py +274 -0
- synkit/Chem/Mapper/validation/bonds.py +124 -0
- synkit/Chem/Mapper/validation/cohorts.py +72 -0
- synkit/Chem/Mapper/validation/identifiability.py +150 -0
- synkit/Chem/Mapper/validation/numerical_scope.py +36 -0
- synkit/Chem/Mapper/validation/orbits.py +137 -0
- synkit/Chem/Molecule/__init__.py +0 -0
- synkit/Chem/Molecule/_chirality_loci.py +165 -0
- synkit/Chem/Molecule/_chirality_types.py +231 -0
- synkit/Chem/Molecule/_helical_loci.py +167 -0
- synkit/Chem/Molecule/_stereo_axis_evidence.py +202 -0
- synkit/Chem/Molecule/_stereo_element_detection.py +193 -0
- synkit/Chem/Molecule/_stereo_orientation_constraints.py +511 -0
- synkit/Chem/Molecule/_tetrahedral_carriers.py +63 -0
- synkit/Chem/Molecule/atom_features.py +379 -0
- synkit/Chem/Molecule/chirality.py +1041 -0
- synkit/Chem/Molecule/cip_assignment.py +569 -0
- synkit/Chem/Molecule/cip_ranking.py +843 -0
- synkit/Chem/Molecule/coordinate_stereo.py +503 -0
- synkit/Chem/Molecule/derived_stereo_naming.py +165 -0
- synkit/Chem/Molecule/descriptors.py +452 -0
- synkit/Chem/Molecule/formula.py +249 -0
- synkit/Chem/Molecule/global_stereo.py +175 -0
- synkit/Chem/Molecule/graph_annotator.py +300 -0
- synkit/Chem/Molecule/standardize.py +817 -0
- synkit/Chem/Molecule/stereo_evidence.py +179 -0
- synkit/Chem/Molecule/stereo_perception.py +1032 -0
- synkit/Chem/Molecule/valence.py +123 -0
- synkit/Chem/Reaction/Mapper/__init__.py +44 -0
- synkit/Chem/Reaction/__init__.py +20 -0
- synkit/Chem/Reaction/aam_validator.py +5 -0
- synkit/Chem/Reaction/balance_check.py +138 -0
- synkit/Chem/Reaction/canon_rsmi.py +249 -0
- synkit/Chem/Reaction/cleaning.py +58 -0
- synkit/Chem/Reaction/deionize.py +199 -0
- synkit/Chem/Reaction/explicit_h_audit.py +232 -0
- synkit/Chem/Reaction/fix_aam.py +64 -0
- synkit/Chem/Reaction/neutralize.py +207 -0
- synkit/Chem/Reaction/radical_wildcard.py +224 -0
- synkit/Chem/Reaction/standardize.py +203 -0
- synkit/Chem/Reaction/tautomerize.py +205 -0
- synkit/Chem/__init__.py +1 -0
- synkit/Chem/utils.py +315 -0
- synkit/Data/__init__.py +0 -0
- synkit/Data/aldol.json.gz +1 -0
- synkit/Data/gen_partial_aam.py +147 -0
- synkit/Data/paracetamol.json.gz +1 -0
- synkit/Graph/Canon/__init__.py +16 -0
- synkit/Graph/Canon/_native/__init__.py +13 -0
- synkit/Graph/Canon/_native/build.py +93 -0
- synkit/Graph/Canon/_native/lib/synkit_canon.dll +0 -0
- synkit/Graph/Canon/_native/prepare.py +49 -0
- synkit/Graph/Canon/_native/runtime.py +140 -0
- synkit/Graph/Canon/_native/search.py +137 -0
- synkit/Graph/Canon/canon_algs.py +228 -0
- synkit/Graph/Canon/canon_graph.py +531 -0
- synkit/Graph/Canon/cpp/CMakeLists.txt +42 -0
- synkit/Graph/Canon/cpp/README.md +123 -0
- synkit/Graph/Canon/cpp/examples/canonical_graph.cpp +16 -0
- synkit/Graph/Canon/cpp/include/synkit_canon/canon.h +85 -0
- synkit/Graph/Canon/cpp/src/canonical.cpp +391 -0
- synkit/Graph/Canon/cpp/src/symmetry.h +83 -0
- synkit/Graph/Canon/cpp/tests/symmetry.cpp +93 -0
- synkit/Graph/Canon/exact.py +1020 -0
- synkit/Graph/Canon/exact_refinement.py +368 -0
- synkit/Graph/Canon/nauty.py +143 -0
- synkit/Graph/Context/__init__.py +0 -0
- synkit/Graph/Context/hier_context.py +244 -0
- synkit/Graph/Context/radius_expand.py +252 -0
- synkit/Graph/FG/__init__.py +19 -0
- synkit/Graph/FG/api.py +39 -0
- synkit/Graph/FG/audit.py +155 -0
- synkit/Graph/FG/catalog.py +919 -0
- synkit/Graph/FG/detector.py +288 -0
- synkit/Graph/FG/model.py +97 -0
- synkit/Graph/FG/recognizers.py +274 -0
- synkit/Graph/FG/ring_system.py +154 -0
- synkit/Graph/Feature/Descriptors/topology.py +857 -0
- synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
- synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +222 -0
- synkit/Graph/Feature/__init__.py +5 -0
- synkit/Graph/Feature/graph_descriptors.py +323 -0
- synkit/Graph/Feature/graph_fps.py +96 -0
- synkit/Graph/Feature/graph_signature.py +239 -0
- synkit/Graph/Feature/hash_fps.py +133 -0
- synkit/Graph/Feature/morgan_fps.py +88 -0
- synkit/Graph/Feature/path_fps.py +81 -0
- synkit/Graph/Feature/wl_hash.py +134 -0
- synkit/Graph/Fusion/__init__.py +85 -0
- synkit/Graph/Fusion/candidate.py +559 -0
- synkit/Graph/Fusion/constructor.py +877 -0
- synkit/Graph/Fusion/identity.py +184 -0
- synkit/Graph/Fusion/interface.py +700 -0
- synkit/Graph/Fusion/search.py +241 -0
- synkit/Graph/Hyrogen/__init__.py +6 -0
- synkit/Graph/Hyrogen/_misc.py +591 -0
- synkit/Graph/Hyrogen/hcomplete.py +995 -0
- synkit/Graph/Hyrogen/hcomplete_legacy.py +368 -0
- synkit/Graph/Hyrogen/hcompletion_algorithms.py +713 -0
- synkit/Graph/Hyrogen/hextend.py +570 -0
- synkit/Graph/Hyrogen/hextend_legacy.py +215 -0
- synkit/Graph/ITS/__init__.py +4 -0
- synkit/Graph/ITS/its_builder.py +120 -0
- synkit/Graph/ITS/its_construction.py +719 -0
- synkit/Graph/ITS/its_decompose.py +572 -0
- synkit/Graph/ITS/its_destruction.py +331 -0
- synkit/Graph/ITS/its_expand.py +1251 -0
- synkit/Graph/ITS/its_relabel.py +187 -0
- synkit/Graph/ITS/its_reverter.py +356 -0
- synkit/Graph/ITS/normalize_aam.py +145 -0
- synkit/Graph/ITS/partial_its.py +238 -0
- synkit/Graph/ITS/rc_extractor.py +656 -0
- synkit/Graph/ITS/stereo.py +74 -0
- synkit/Graph/MTG/__init__.py +107 -0
- synkit/Graph/MTG/gluing.py +451 -0
- synkit/Graph/MTG/history.py +928 -0
- synkit/Graph/MTG/inventory.py +118 -0
- synkit/Graph/MTG/local.py +447 -0
- synkit/Graph/MTG/mcs_matcher.py +246 -0
- synkit/Graph/MTG/mtg.py +611 -0
- synkit/Graph/MTG/mtg_explore.py +74 -0
- synkit/Graph/MTG/observation.py +209 -0
- synkit/Graph/MTG/process.py +577 -0
- synkit/Graph/MTG/queries.py +66 -0
- synkit/Graph/MTG/realization.py +95 -0
- synkit/Graph/MTG/series_parallel.py +107 -0
- synkit/Graph/MTG/utils.py +424 -0
- synkit/Graph/Matcher/__init__.py +10 -0
- synkit/Graph/Matcher/approx_mcs.py +1264 -0
- synkit/Graph/Matcher/auto_est.py +736 -0
- synkit/Graph/Matcher/automorphism.py +314 -0
- synkit/Graph/Matcher/batch_cluster.py +224 -0
- synkit/Graph/Matcher/dedup_matches.py +247 -0
- synkit/Graph/Matcher/graph_cluster.py +191 -0
- synkit/Graph/Matcher/graph_matcher.py +312 -0
- synkit/Graph/Matcher/graph_morphism.py +421 -0
- synkit/Graph/Matcher/mcs_matcher.py +804 -0
- synkit/Graph/Matcher/multi_turbo_iso.py +186 -0
- synkit/Graph/Matcher/orbit.py +284 -0
- synkit/Graph/Matcher/partial_matcher.py +694 -0
- synkit/Graph/Matcher/sing.py +544 -0
- synkit/Graph/Matcher/subgraph_matcher.py +812 -0
- synkit/Graph/Matcher/turbo_iso.py +272 -0
- synkit/Graph/Matcher/wl_sel.py +537 -0
- synkit/Graph/Mech/__init__.py +26 -0
- synkit/Graph/Mech/conversion.py +1466 -0
- synkit/Graph/Mech/conversion_debug.py +237 -0
- synkit/Graph/Mech/electron_accounting.py +200 -0
- synkit/Graph/Mech/lwg_editor.py +406 -0
- synkit/Graph/Mech/lwg_ops.py +313 -0
- synkit/Graph/Morphism/__init__.py +99 -0
- synkit/Graph/Morphism/constraints.py +460 -0
- synkit/Graph/Morphism/llg.py +999 -0
- synkit/Graph/Morphism/morphism.py +349 -0
- synkit/Graph/Morphism/stereo.py +258 -0
- synkit/Graph/Morphism/stereo_morphism.py +383 -0
- synkit/Graph/Morphism/stereo_morphism_helpers.py +957 -0
- synkit/Graph/Morphism/stereo_morphism_types.py +117 -0
- synkit/Graph/Stereo/__init__.py +376 -0
- synkit/Graph/Stereo/_descriptor_core.py +207 -0
- synkit/Graph/Stereo/_descriptor_serialization.py +138 -0
- synkit/Graph/Stereo/canonical.py +648 -0
- synkit/Graph/Stereo/changes.py +964 -0
- synkit/Graph/Stereo/composition.py +391 -0
- synkit/Graph/Stereo/configured.py +946 -0
- synkit/Graph/Stereo/couplings.py +245 -0
- synkit/Graph/Stereo/descriptors.py +1030 -0
- synkit/Graph/Stereo/enumeration.py +597 -0
- synkit/Graph/Stereo/extended_descriptors.py +406 -0
- synkit/Graph/Stereo/global_configured.py +152 -0
- synkit/Graph/Stereo/global_stereo.py +304 -0
- synkit/Graph/Stereo/identity.py +648 -0
- synkit/Graph/Stereo/interchange.py +496 -0
- synkit/Graph/Stereo/legacy.py +460 -0
- synkit/Graph/Stereo/matching.py +899 -0
- synkit/Graph/Stereo/native.py +154 -0
- synkit/Graph/Stereo/orbits.py +668 -0
- synkit/Graph/Stereo/outcomes.py +99 -0
- synkit/Graph/Stereo/pairwise.py +350 -0
- synkit/Graph/Stereo/rdkit_adapter.py +662 -0
- synkit/Graph/Stereo/rdkit_identity.py +118 -0
- synkit/Graph/Stereo/rdkit_resonance.py +140 -0
- synkit/Graph/Stereo/reaction_interchange.py +266 -0
- synkit/Graph/Stereo/semantics.py +296 -0
- synkit/Graph/Stereo/supports.py +257 -0
- synkit/Graph/Stereo/wire.py +339 -0
- synkit/Graph/Wildcard/__init__.py +0 -0
- synkit/Graph/Wildcard/fuse_graph.py +156 -0
- synkit/Graph/Wildcard/graph_wc.py +303 -0
- synkit/Graph/Wildcard/its_merge.py +857 -0
- synkit/Graph/Wildcard/radwc.py +114 -0
- synkit/Graph/Wildcard/wc_matcher.py +576 -0
- synkit/Graph/Wildcard/wildcard.py +225 -0
- synkit/Graph/__init__.py +17 -0
- synkit/Graph/canon_graph.py +14 -0
- synkit/Graph/syn_graph.py +199 -0
- synkit/Graph/utils.py +324 -0
- synkit/IO/__init__.py +4 -0
- synkit/IO/chem_converter.py +1059 -0
- synkit/IO/combinatorial/__init__.py +8 -0
- synkit/IO/combinatorial/gml_to_graph.py +223 -0
- synkit/IO/combinatorial/graph_to_gml.py +280 -0
- synkit/IO/combinatorial/graph_to_smarts.py +186 -0
- synkit/IO/combinatorial/smarts_expander.py +147 -0
- synkit/IO/combinatorial/smarts_generalizer.py +133 -0
- synkit/IO/combinatorial/smarts_to_graph.py +179 -0
- synkit/IO/conversion.py +42 -0
- synkit/IO/data_io.py +269 -0
- synkit/IO/data_process.py +143 -0
- synkit/IO/debug.py +73 -0
- synkit/IO/gml_to_nx.py +150 -0
- synkit/IO/graph_to_mol.py +199 -0
- synkit/IO/mol_to_graph.py +1105 -0
- synkit/IO/mol_to_graph_chemistry.py +545 -0
- synkit/IO/nx_to_gml.py +209 -0
- synkit/IO/smiles_to_id.py +123 -0
- synkit/Mechanism/__init__.py +151 -0
- synkit/Mechanism/adapters.py +136 -0
- synkit/Mechanism/audit.py +323 -0
- synkit/Mechanism/benchmark.py +638 -0
- synkit/Mechanism/compatibility.py +13 -0
- synkit/Mechanism/drawing.py +85 -0
- synkit/Mechanism/electron_models.py +725 -0
- synkit/Mechanism/equivalence.py +521 -0
- synkit/Mechanism/evidence.py +262 -0
- synkit/Mechanism/interchange.py +141 -0
- synkit/Mechanism/model.py +918 -0
- synkit/Mechanism/neighbor_stereo.py +312 -0
- synkit/Mechanism/radical_data.py +740 -0
- synkit/Mechanism/replay.py +986 -0
- synkit/Mechanism/schema.py +264 -0
- synkit/Mechanism/stereo_state.py +416 -0
- synkit/Mechanism/symbols.py +91 -0
- synkit/Rule/Apply/__init__.py +35 -0
- synkit/Rule/Apply/conditions.py +45 -0
- synkit/Rule/Apply/dpo.py +998 -0
- synkit/Rule/Apply/rule_matcher.py +218 -0
- synkit/Rule/Apply/validation.py +354 -0
- synkit/Rule/Compose/__init__.py +106 -0
- synkit/Rule/Compose/_identity.py +43 -0
- synkit/Rule/Compose/composition.py +976 -0
- synkit/Rule/Compose/laws.py +439 -0
- synkit/Rule/Compose/search.py +771 -0
- synkit/Rule/Compose/synrule.py +170 -0
- synkit/Rule/Modify/__init__.py +0 -0
- synkit/Rule/Modify/implict_rule.py +56 -0
- synkit/Rule/Modify/longest_path.py +92 -0
- synkit/Rule/Modify/molecule_rule.py +115 -0
- synkit/Rule/Modify/prune_templates.py +78 -0
- synkit/Rule/Modify/rule_utils.py +194 -0
- synkit/Rule/Modify/strip_rule.py +97 -0
- synkit/Rule/__init__.py +28 -0
- synkit/Rule/generic_stereo.py +884 -0
- synkit/Rule/stereo_identity.py +271 -0
- synkit/Rule/syn_rule.py +1090 -0
- synkit/Synthesis/MSR/__init__.py +0 -0
- synkit/Synthesis/MSR/path_finder.py +239 -0
- synkit/Synthesis/Metrics/__init__.py +0 -0
- synkit/Synthesis/Metrics/_base.py +52 -0
- synkit/Synthesis/Metrics/_plot.py +129 -0
- synkit/Synthesis/Metrics/_ranking.py +179 -0
- synkit/Synthesis/RBL/__init__.py +80 -0
- synkit/Synthesis/RBL/engine.py +1249 -0
- synkit/Synthesis/RBL/matching.py +652 -0
- synkit/Synthesis/RBL/overlap.py +336 -0
- synkit/Synthesis/RBL/pipeline.py +376 -0
- synkit/Synthesis/RBL/policy.py +139 -0
- synkit/Synthesis/RBL/proof.py +370 -0
- synkit/Synthesis/RBL/reaction.py +506 -0
- synkit/Synthesis/RBL/state.py +169 -0
- synkit/Synthesis/RBL/validation.py +1060 -0
- synkit/Synthesis/Reactor/__init__.py +60 -0
- synkit/Synthesis/Reactor/core/__init__.py +1 -0
- synkit/Synthesis/Reactor/core/engine.py +967 -0
- synkit/Synthesis/Reactor/core/product.py +621 -0
- synkit/Synthesis/Reactor/core/rewrite.py +561 -0
- synkit/Synthesis/Reactor/core/strategy.py +44 -0
- synkit/Synthesis/Reactor/matching/__init__.py +1 -0
- synkit/Synthesis/Reactor/matching/mixin.py +920 -0
- synkit/Synthesis/Reactor/matching/policy.py +345 -0
- synkit/Synthesis/Reactor/matching/symmetry.py +309 -0
- synkit/Synthesis/Reactor/output/__init__.py +1 -0
- synkit/Synthesis/Reactor/output/deduplication.py +703 -0
- synkit/Synthesis/Reactor/output/policy.py +16 -0
- synkit/Synthesis/Reactor/output/serialization.py +485 -0
- synkit/Synthesis/Reactor/output/structural.py +893 -0
- synkit/Synthesis/Reactor/stereo/__init__.py +1 -0
- synkit/Synthesis/Reactor/stereo/assignment.py +35 -0
- synkit/Synthesis/Reactor/stereo/mixin.py +780 -0
- synkit/Synthesis/Reactor/variants/__init__.py +1 -0
- synkit/Synthesis/Reactor/variants/imbalanced.py +180 -0
- synkit/Synthesis/Reactor/variants/partial.py +84 -0
- synkit/Synthesis/Reactor/workflow/__init__.py +1 -0
- synkit/Synthesis/Reactor/workflow/batch.py +550 -0
- synkit/Synthesis/Reactor/workflow/benchmark.py +143 -0
- synkit/Synthesis/Reactor/workflow/postprocess.py +267 -0
- synkit/Synthesis/Reactor/workflow/rule_filter.py +210 -0
- synkit/Synthesis/__init__.py +0 -0
- synkit/Synthesis/reactor_utils.py +316 -0
- synkit/Utils/__init__.py +0 -0
- synkit/Utils/utils.py +172 -0
- synkit/Vis/__init__.py +91 -0
- synkit/Vis/crn/__init__.py +40 -0
- synkit/Vis/epd/__init__.py +20 -0
- synkit/Vis/epd/arrows.py +425 -0
- synkit/Vis/epd/chem.py +197 -0
- synkit/Vis/epd/constants.py +83 -0
- synkit/Vis/epd/layout.py +283 -0
- synkit/Vis/epd/mapping.py +51 -0
- synkit/Vis/epd/models.py +136 -0
- synkit/Vis/epd/render.py +543 -0
- synkit/Vis/epd/utils.py +108 -0
- synkit/Vis/epd/visualizer.py +811 -0
- synkit/Vis/graph_visualizer.py +375 -0
- synkit/Vis/its/__init__.py +19 -0
- synkit/Vis/its/drawer.py +615 -0
- synkit/Vis/its_drawer.py +35 -0
- synkit/Vis/molecule/__init__.py +31 -0
- synkit/Vis/molecule/drawer.py +563 -0
- synkit/Vis/molecule/reaction.py +285 -0
- synkit/Vis/molecule_drawer.py +33 -0
- synkit/Vis/mtg/__init__.py +17 -0
- synkit/Vis/mtg/drawer.py +769 -0
- synkit/Vis/mtg_drawer.py +34 -0
- synkit/Vis/pdf_writer.py +146 -0
- synkit/Vis/reaction/__init__.py +19 -0
- synkit/Vis/reaction/rule.py +141 -0
- synkit/Vis/reaction/rxn.py +532 -0
- synkit/Vis/reaction_drawer.py +41 -0
- synkit/Vis/space/__init__.py +17 -0
- synkit/Vis/space/chemical.py +80 -0
- synkit/Vis/space/embedding.py +87 -0
- synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
- synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
- synkit/Vis/vis_synedu/rxn_vis.py +377 -0
- synkit/Vis/vis_synedu/vis.py +501 -0
- synkit/Vis/visual_drawer.py +215 -0
- synkit/Vis/visual_model.py +520 -0
- synkit/__init__.py +3 -0
- synkit/_native.py +91 -0
- synkit/examples.py +50 -0
- synkit/version.py +23 -0
- synkit-1.6.5.dist-info/METADATA +156 -0
- synkit-1.6.5.dist-info/RECORD +636 -0
- synkit-1.6.5.dist-info/WHEEL +4 -0
- synkit-1.6.5.dist-info/licenses/LICENSE +21 -0
- synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
synkit/Graph/MTG/mtg.py
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""MTG – Mechanistic Transition Graph fusion utility.
|
|
4
|
+
|
|
5
|
+
This module exposes :class:`~MTG`, a helper that merges a chronological
|
|
6
|
+
sequence of **Intermediate Transition State** (ITS) graphs – or their RSMI
|
|
7
|
+
string representations – into a single *product* graph capturing the entire
|
|
8
|
+
bond-order history across the reaction trajectory.
|
|
9
|
+
|
|
10
|
+
The implementation is self-contained except for the external *synkit* helpers
|
|
11
|
+
used for RSMI⇒ITS inter-conversion and canonicalisation.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from collections.abc import Iterator
|
|
15
|
+
from typing import Any, Dict, List, Mapping, MutableMapping, Set, Tuple, Union
|
|
16
|
+
|
|
17
|
+
import networkx as nx
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Optional dependencies
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
try:
|
|
23
|
+
import pandas as pd # type: ignore
|
|
24
|
+
except ImportError: # pragma: no cover – pandas is only required for to_dataframe()
|
|
25
|
+
pd = None # noqa: N816
|
|
26
|
+
|
|
27
|
+
from synkit.Graph.Hyrogen._misc import h_to_explicit
|
|
28
|
+
from synkit.Graph.ITS.normalize_aam import NormalizeAAM
|
|
29
|
+
from synkit.Graph.MTG.mcs_matcher import MCSMatcher
|
|
30
|
+
from synkit.Graph.MTG.utils import (
|
|
31
|
+
normalize_hcount_and_typesGH,
|
|
32
|
+
normalize_order,
|
|
33
|
+
label_mtg_edges,
|
|
34
|
+
compute_standard_order,
|
|
35
|
+
)
|
|
36
|
+
from synkit.Graph.canon_graph import GraphCanonicaliser
|
|
37
|
+
from synkit.IO import ITSFormat, its_to_rsmi, rsmi_to_its
|
|
38
|
+
|
|
39
|
+
NodeID = int
|
|
40
|
+
MissingOrder = Tuple[Set[float], Set[float]]
|
|
41
|
+
GraphMapping = Dict[NodeID, NodeID]
|
|
42
|
+
|
|
43
|
+
_PLACEHOLDER: MissingOrder = (set(), set())
|
|
44
|
+
_PLACEHOLDER_TYPESGH = (set(), set(), set(), set(), set())
|
|
45
|
+
_TUPLE_EDGE_ATTRS = ("order", "kekule_order", "sigma_order", "pi_order")
|
|
46
|
+
_TUPLE_NODE_SCALAR_ATTRS = ("element", "atom_map", "valence_electrons")
|
|
47
|
+
_TUPLE_NODE_TIMELINE_ATTRS = (
|
|
48
|
+
"aromatic",
|
|
49
|
+
"hcount",
|
|
50
|
+
"charge",
|
|
51
|
+
"radical",
|
|
52
|
+
"lone_pairs",
|
|
53
|
+
"present",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
__all__ = ["MTG"]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MTG:
|
|
60
|
+
"""Fuse a chronological series of ITS graphs into a Mechanistic Transition Graph.
|
|
61
|
+
|
|
62
|
+
:param sequences: A list of ITS-format NetworkX graphs or RSMI strings.
|
|
63
|
+
:param mappings: Optional list of precomputed mappings; computed via MCS if None.
|
|
64
|
+
:param node_label_names: Keys for node-label matching.
|
|
65
|
+
:param canonicaliser: Optional GraphCanonicaliser for snapshot canonicalisation.
|
|
66
|
+
:param its_format: ITS format used when ``sequences`` contains RSMI strings.
|
|
67
|
+
Defaults to ``"tuple"`` for Lewis-labelled graph MTGs. Pass
|
|
68
|
+
``"typesGH"`` to build legacy MTGs from strings.
|
|
69
|
+
:raises ValueError: On invalid sequence or mapping lengths.
|
|
70
|
+
:raises RuntimeError: On mapping failures.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
sequences: Union[List[nx.Graph], List[str]],
|
|
76
|
+
mappings: List[GraphMapping] | None = None,
|
|
77
|
+
*,
|
|
78
|
+
node_label_names: List[str] | None = None,
|
|
79
|
+
canonicaliser: GraphCanonicaliser | None = None,
|
|
80
|
+
mcs_mol: bool = False,
|
|
81
|
+
mcs: bool = False,
|
|
82
|
+
its_format: ITSFormat = "tuple",
|
|
83
|
+
) -> None:
|
|
84
|
+
if len(sequences) < 2:
|
|
85
|
+
raise ValueError("Need at least two snapshots.")
|
|
86
|
+
|
|
87
|
+
self._node_label_names = node_label_names or ["element", "charge", "hcount"]
|
|
88
|
+
self._canonicaliser = canonicaliser
|
|
89
|
+
self.mcs_mol = mcs_mol
|
|
90
|
+
self.mcs = mcs
|
|
91
|
+
self.its_format = its_format
|
|
92
|
+
|
|
93
|
+
self._graphs = self._prepare_graph_sequence(sequences)
|
|
94
|
+
self._k = len(self._graphs)
|
|
95
|
+
self._tuple_its = all(self._is_tuple_its(g) for g in self._graphs)
|
|
96
|
+
|
|
97
|
+
self._mappings = (
|
|
98
|
+
mappings if mappings is not None else self._compute_mappings(self._graphs)
|
|
99
|
+
)
|
|
100
|
+
if len(self._mappings) != self._k - 1:
|
|
101
|
+
raise ValueError("Mappings must match snapshot pairs.")
|
|
102
|
+
|
|
103
|
+
self._prod_nodes: Dict[int, Dict[str, Any]]
|
|
104
|
+
self._node_map: Dict[Tuple[int, NodeID], int]
|
|
105
|
+
self._graph: nx.Graph
|
|
106
|
+
|
|
107
|
+
self._build_node_map_and_attributes()
|
|
108
|
+
self._build_edge_history_and_graph()
|
|
109
|
+
|
|
110
|
+
def __repr__(self) -> str:
|
|
111
|
+
return f"<MTG k={self._k} nodes={self._graph.number_of_nodes()} edges={self._graph.number_of_edges()}>"
|
|
112
|
+
|
|
113
|
+
def __len__(self) -> int:
|
|
114
|
+
return self._graph.number_of_nodes()
|
|
115
|
+
|
|
116
|
+
def __iter__(self) -> Iterator[int]:
|
|
117
|
+
return iter(self._graph.nodes)
|
|
118
|
+
|
|
119
|
+
def __getitem__(self, node_id: int) -> Dict[str, Any]:
|
|
120
|
+
return self._graph.nodes[node_id]
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def describe() -> str:
|
|
124
|
+
return (
|
|
125
|
+
"# Usage example\n"
|
|
126
|
+
"mtg = MTG([G0, G1, G2])\n"
|
|
127
|
+
"mg = mtg.get_mtg()\n"
|
|
128
|
+
"rsmi = mtg.get_aam()\n"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def get_mtg(self, *, directed: bool = False) -> nx.Graph:
|
|
132
|
+
return self._graph.to_directed() if directed else self._graph
|
|
133
|
+
|
|
134
|
+
def get_its_steps(self, *, directed: bool = False) -> List[nx.Graph]:
|
|
135
|
+
"""Reconstruct the ordered list of per-step ITS graphs from the MTG."""
|
|
136
|
+
if not self._tuple_its:
|
|
137
|
+
return [graph.copy() for graph in self._graphs]
|
|
138
|
+
graph = self.get_mtg(directed=directed)
|
|
139
|
+
return [self._tuple_step_its(graph, step) for step in range(self._k)]
|
|
140
|
+
|
|
141
|
+
def get_rsmi_steps(
|
|
142
|
+
self,
|
|
143
|
+
*,
|
|
144
|
+
directed: bool = False,
|
|
145
|
+
explicit_hydrogen: bool = False,
|
|
146
|
+
sanitize: bool = True,
|
|
147
|
+
) -> List[str]:
|
|
148
|
+
"""Serialize reconstructed per-step ITS graphs to reaction SMILES."""
|
|
149
|
+
fmt = "tuple" if self._tuple_its else "typesGH"
|
|
150
|
+
return [
|
|
151
|
+
its_to_rsmi(
|
|
152
|
+
its,
|
|
153
|
+
format=fmt,
|
|
154
|
+
explicit_hydrogen=explicit_hydrogen,
|
|
155
|
+
sanitize=sanitize,
|
|
156
|
+
)
|
|
157
|
+
for its in self.get_its_steps(directed=directed)
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
def get_compose_its(self, *, directed: bool = False) -> nx.Graph:
|
|
161
|
+
g = self.get_mtg(directed=directed)
|
|
162
|
+
if self._tuple_its:
|
|
163
|
+
g = self._compose_tuple_node_attrs(g)
|
|
164
|
+
g = self._compose_tuple_edge_attrs(g)
|
|
165
|
+
else:
|
|
166
|
+
g = label_mtg_edges(g, inplace=False)
|
|
167
|
+
g = normalize_order(g)
|
|
168
|
+
g = normalize_hcount_and_typesGH(g)
|
|
169
|
+
return compute_standard_order(g)
|
|
170
|
+
|
|
171
|
+
def get_aam(self, *, directed: bool = False, explicit_h: bool = False) -> str:
|
|
172
|
+
g = self.get_compose_its(directed=directed)
|
|
173
|
+
rsmi = its_to_rsmi(g, explicit_hydrogen=True)
|
|
174
|
+
return (
|
|
175
|
+
NormalizeAAM().fit(rsmi, fix_aam_indice=False) if not explicit_h else rsmi
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def to_dataframe(self):
|
|
179
|
+
if pd is None:
|
|
180
|
+
raise RuntimeError("pandas required for DataFrame export.")
|
|
181
|
+
return pd.DataFrame.from_dict(
|
|
182
|
+
dict(self._graph.nodes(data=True)), orient="index"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def _merge_attrs(lhs: MutableMapping[str, Any], rhs: Mapping[str, Any]) -> None:
|
|
187
|
+
for k, v in rhs.items():
|
|
188
|
+
if not lhs.get(k) and v is not None:
|
|
189
|
+
lhs[k] = v
|
|
190
|
+
|
|
191
|
+
def _build_node_map_and_attributes(self) -> None:
|
|
192
|
+
if self._tuple_its and self._has_tuple_atom_maps(self._graphs):
|
|
193
|
+
self._build_tuple_node_map_and_attributes()
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
prod, node_map = {}, {}
|
|
197
|
+
last = self._graphs[-1]
|
|
198
|
+
for nid, attrs in last.nodes(data=True):
|
|
199
|
+
prod[nid] = attrs.copy()
|
|
200
|
+
node_map[(self._k - 1, nid)] = nid
|
|
201
|
+
pid_counter = max(prod, default=-1) + 1
|
|
202
|
+
|
|
203
|
+
# merge attributes backwards
|
|
204
|
+
for i in range(self._k - 2, -1, -1):
|
|
205
|
+
G, mp = self._graphs[i], self._mappings[i]
|
|
206
|
+
for nid, attrs in G.nodes(data=True):
|
|
207
|
+
tgt = mp.get(nid)
|
|
208
|
+
if tgt is not None and (i + 1, tgt) in node_map:
|
|
209
|
+
pid = node_map[(i + 1, tgt)]
|
|
210
|
+
self._merge_attrs(prod[pid], attrs)
|
|
211
|
+
else:
|
|
212
|
+
pid = pid_counter
|
|
213
|
+
prod[pid] = attrs.copy()
|
|
214
|
+
pid_counter += 1
|
|
215
|
+
node_map[(i, nid)] = pid
|
|
216
|
+
|
|
217
|
+
# assemble typesGH history per pid
|
|
218
|
+
first_idx: Dict[int, int] = {}
|
|
219
|
+
for (gi, n), p in node_map.items():
|
|
220
|
+
# track the earliest snapshot index where pid appears
|
|
221
|
+
if p in first_idx:
|
|
222
|
+
first_idx[p] = min(first_idx[p], gi)
|
|
223
|
+
else:
|
|
224
|
+
first_idx[p] = gi
|
|
225
|
+
|
|
226
|
+
if self._tuple_its:
|
|
227
|
+
self._simplify_tuple_node_attrs(prod, node_map)
|
|
228
|
+
else:
|
|
229
|
+
for p, attrs in prod.items():
|
|
230
|
+
hist: List[Any] = []
|
|
231
|
+
fi = first_idx[p]
|
|
232
|
+
for i in range(self._k):
|
|
233
|
+
if i < fi:
|
|
234
|
+
hist.append(_PLACEHOLDER_TYPESGH)
|
|
235
|
+
elif i == fi:
|
|
236
|
+
val = (
|
|
237
|
+
self._graphs[i]
|
|
238
|
+
.nodes[
|
|
239
|
+
next(
|
|
240
|
+
n
|
|
241
|
+
for (gi, n), pp in node_map.items()
|
|
242
|
+
if gi == i and pp == p
|
|
243
|
+
)
|
|
244
|
+
]
|
|
245
|
+
.get(
|
|
246
|
+
"typesGH", (_PLACEHOLDER_TYPESGH, _PLACEHOLDER_TYPESGH)
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
hist.append(val)
|
|
250
|
+
else:
|
|
251
|
+
originals = [
|
|
252
|
+
n for (gi, n), pp in node_map.items() if gi == i and pp == p
|
|
253
|
+
]
|
|
254
|
+
if originals:
|
|
255
|
+
val = (
|
|
256
|
+
self._graphs[i]
|
|
257
|
+
.nodes[originals[0]]
|
|
258
|
+
.get(
|
|
259
|
+
"typesGH",
|
|
260
|
+
(_PLACEHOLDER_TYPESGH, _PLACEHOLDER_TYPESGH),
|
|
261
|
+
)[-1]
|
|
262
|
+
)
|
|
263
|
+
hist.append(val)
|
|
264
|
+
else:
|
|
265
|
+
hist.append(_PLACEHOLDER_TYPESGH)
|
|
266
|
+
attrs["typesGH_history"] = tuple(hist)
|
|
267
|
+
attrs["typesGH"] = attrs["typesGH_history"]
|
|
268
|
+
|
|
269
|
+
self._prod_nodes = prod
|
|
270
|
+
self._node_map = node_map
|
|
271
|
+
|
|
272
|
+
def _build_tuple_node_map_and_attributes(self) -> None:
|
|
273
|
+
prod: Dict[int, Dict[str, Any]] = {}
|
|
274
|
+
node_map: Dict[Tuple[int, NodeID], int] = {}
|
|
275
|
+
pid_counter = 0
|
|
276
|
+
|
|
277
|
+
for gi, graph in enumerate(self._graphs):
|
|
278
|
+
used_in_graph: Set[int] = set()
|
|
279
|
+
for nid, attrs in graph.nodes(data=True):
|
|
280
|
+
pid = self._tuple_node_pid(attrs)
|
|
281
|
+
if pid is None or pid in used_in_graph:
|
|
282
|
+
while pid_counter in prod:
|
|
283
|
+
pid_counter += 1
|
|
284
|
+
pid = pid_counter
|
|
285
|
+
pid_counter += 1
|
|
286
|
+
prod.setdefault(pid, {})
|
|
287
|
+
node_map[(gi, nid)] = pid
|
|
288
|
+
used_in_graph.add(pid)
|
|
289
|
+
|
|
290
|
+
self._simplify_tuple_node_attrs(prod, node_map)
|
|
291
|
+
self._prod_nodes = prod
|
|
292
|
+
self._node_map = node_map
|
|
293
|
+
|
|
294
|
+
def _build_edge_history_and_graph(self) -> None:
|
|
295
|
+
hist: Dict[Tuple[int, int], Dict[str, List[MissingOrder]]] = {}
|
|
296
|
+
for i, G in enumerate(self._graphs):
|
|
297
|
+
for u, v, a in G.edges(data=True):
|
|
298
|
+
pu, pv = self._node_map[(i, u)], self._node_map[(i, v)]
|
|
299
|
+
key = tuple(sorted((pu, pv)))
|
|
300
|
+
attr_hist = hist.setdefault(
|
|
301
|
+
key,
|
|
302
|
+
{name: [_PLACEHOLDER] * self._k for name in _TUPLE_EDGE_ATTRS},
|
|
303
|
+
)
|
|
304
|
+
for name in _TUPLE_EDGE_ATTRS:
|
|
305
|
+
attr_hist[name][i] = a.get(name, _PLACEHOLDER)
|
|
306
|
+
g = nx.Graph()
|
|
307
|
+
g.add_nodes_from(self._prod_nodes.items())
|
|
308
|
+
for (u, v), attr_hist in hist.items():
|
|
309
|
+
attrs: Dict[str, Any] = {"order": tuple(attr_hist["order"])}
|
|
310
|
+
if self._tuple_its:
|
|
311
|
+
attrs = {}
|
|
312
|
+
for name, values in attr_hist.items():
|
|
313
|
+
attrs[name] = self._edge_pair_history_to_timeline(
|
|
314
|
+
tuple(values),
|
|
315
|
+
g.nodes[u].get("present"),
|
|
316
|
+
g.nodes[v].get("present"),
|
|
317
|
+
)
|
|
318
|
+
attrs["steps"] = tuple(
|
|
319
|
+
i
|
|
320
|
+
for i, value in enumerate(attr_hist["order"])
|
|
321
|
+
if self._is_observed_pair(value)
|
|
322
|
+
)
|
|
323
|
+
g.add_edge(u, v, **attrs)
|
|
324
|
+
if g.number_of_nodes() != len(self._prod_nodes):
|
|
325
|
+
raise RuntimeError("Node count mismatch.")
|
|
326
|
+
self._graph = g
|
|
327
|
+
|
|
328
|
+
def _simplify_tuple_node_attrs(
|
|
329
|
+
self,
|
|
330
|
+
prod: Dict[int, Dict[str, Any]],
|
|
331
|
+
node_map: Dict[Tuple[int, NodeID], int],
|
|
332
|
+
) -> None:
|
|
333
|
+
"""
|
|
334
|
+
Replace tuple-ITS node attrs with compact MTG attrs.
|
|
335
|
+
|
|
336
|
+
A path of ``k`` ITS steps has ``k + 1`` mechanism states: the first
|
|
337
|
+
step's left side followed by each step's right side.
|
|
338
|
+
"""
|
|
339
|
+
refs_by_pid: Dict[int, Dict[int, NodeID]] = {}
|
|
340
|
+
for (gi, nid), pid in node_map.items():
|
|
341
|
+
refs_by_pid.setdefault(pid, {})[gi] = nid
|
|
342
|
+
|
|
343
|
+
for pid, refs in refs_by_pid.items():
|
|
344
|
+
simplified: Dict[str, Any] = {}
|
|
345
|
+
for key in _TUPLE_NODE_SCALAR_ATTRS:
|
|
346
|
+
timeline = self._node_attr_timeline(refs, key)
|
|
347
|
+
simplified[key] = next(
|
|
348
|
+
(value for value in timeline if value is not None),
|
|
349
|
+
None,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
for key in _TUPLE_NODE_TIMELINE_ATTRS:
|
|
353
|
+
simplified[key] = self._node_attr_timeline(refs, key)
|
|
354
|
+
|
|
355
|
+
simplified["steps"] = tuple(sorted(refs))
|
|
356
|
+
prod[pid] = simplified
|
|
357
|
+
|
|
358
|
+
def _node_attr_timeline(
|
|
359
|
+
self,
|
|
360
|
+
refs: Dict[int, NodeID],
|
|
361
|
+
key: str,
|
|
362
|
+
) -> Tuple[Any, ...]:
|
|
363
|
+
timeline: List[Any] = [None] * (self._k + 1)
|
|
364
|
+
for gi in range(self._k):
|
|
365
|
+
nid = refs.get(gi)
|
|
366
|
+
if nid is None:
|
|
367
|
+
continue
|
|
368
|
+
value = self._graphs[gi].nodes[nid].get(key)
|
|
369
|
+
if self._is_pair(value):
|
|
370
|
+
timeline[gi] = value[0]
|
|
371
|
+
timeline[gi + 1] = value[1]
|
|
372
|
+
return tuple(timeline)
|
|
373
|
+
|
|
374
|
+
def _compose_tuple_node_attrs(self, graph: nx.Graph) -> nx.Graph:
|
|
375
|
+
"""
|
|
376
|
+
Collapse tuple-ITS node histories to the outermost observed states.
|
|
377
|
+
|
|
378
|
+
The fused MTG node attrs are initially copied from the last ITS step.
|
|
379
|
+
For a composed ITS we instead need the first available left-side value
|
|
380
|
+
and the last available right-side value across the whole trajectory.
|
|
381
|
+
"""
|
|
382
|
+
out = graph.copy()
|
|
383
|
+
for _, attrs in out.nodes(data=True):
|
|
384
|
+
for key in _TUPLE_NODE_SCALAR_ATTRS:
|
|
385
|
+
value = attrs.get(key)
|
|
386
|
+
attrs[key] = (value, value)
|
|
387
|
+
for key in _TUPLE_NODE_TIMELINE_ATTRS:
|
|
388
|
+
timeline = attrs.get(key)
|
|
389
|
+
if isinstance(timeline, tuple) and timeline:
|
|
390
|
+
attrs[key] = (timeline[0], timeline[-1])
|
|
391
|
+
return out
|
|
392
|
+
|
|
393
|
+
def _compose_tuple_edge_attrs(self, graph: nx.Graph) -> nx.Graph:
|
|
394
|
+
"""Collapse tuple edge timelines to first-state / final-state pairs."""
|
|
395
|
+
out = graph.copy()
|
|
396
|
+
for _, _, attrs in out.edges(data=True):
|
|
397
|
+
for name in _TUPLE_EDGE_ATTRS:
|
|
398
|
+
timeline = attrs.get(name)
|
|
399
|
+
if not isinstance(timeline, tuple):
|
|
400
|
+
continue
|
|
401
|
+
if timeline:
|
|
402
|
+
attrs[name] = (timeline[0], timeline[-1])
|
|
403
|
+
return out
|
|
404
|
+
|
|
405
|
+
def _tuple_step_its(self, graph: nx.Graph, step: int) -> nx.Graph:
|
|
406
|
+
"""Extract one paired tuple ITS step from compact tuple-MTG timelines."""
|
|
407
|
+
its = nx.Graph()
|
|
408
|
+
for node, attrs in graph.nodes(data=True):
|
|
409
|
+
node_attrs: Dict[str, Any] = {}
|
|
410
|
+
if step not in attrs.get("steps", ()):
|
|
411
|
+
continue
|
|
412
|
+
present_pair = self._timeline_pair(attrs.get("present"), step)
|
|
413
|
+
if present_pair[0] is None or present_pair[1] is None:
|
|
414
|
+
continue
|
|
415
|
+
for key in _TUPLE_NODE_SCALAR_ATTRS:
|
|
416
|
+
value = attrs.get(key)
|
|
417
|
+
node_attrs[key] = (value, value)
|
|
418
|
+
for key in _TUPLE_NODE_TIMELINE_ATTRS:
|
|
419
|
+
value = self._timeline_pair(attrs.get(key), step)
|
|
420
|
+
if value != (None, None):
|
|
421
|
+
node_attrs[key] = value
|
|
422
|
+
its.add_node(node, **node_attrs)
|
|
423
|
+
|
|
424
|
+
for u, v, attrs in graph.edges(data=True):
|
|
425
|
+
if step not in attrs.get("steps", ()):
|
|
426
|
+
continue
|
|
427
|
+
edge_attrs: Dict[str, Any] = {}
|
|
428
|
+
has_edge = False
|
|
429
|
+
for key in _TUPLE_EDGE_ATTRS:
|
|
430
|
+
value = self._timeline_pair(attrs.get(key), step)
|
|
431
|
+
if value == (None, None):
|
|
432
|
+
continue
|
|
433
|
+
edge_attrs[key] = value
|
|
434
|
+
if (
|
|
435
|
+
key == "order"
|
|
436
|
+
and value[0] is not None
|
|
437
|
+
and value[1] is not None
|
|
438
|
+
and value != (0, 0)
|
|
439
|
+
and value != (0.0, 0.0)
|
|
440
|
+
):
|
|
441
|
+
has_edge = True
|
|
442
|
+
if has_edge and u in its and v in its:
|
|
443
|
+
its.add_edge(u, v, **edge_attrs)
|
|
444
|
+
return compute_standard_order(its)
|
|
445
|
+
|
|
446
|
+
def _prepare_graph_sequence(
|
|
447
|
+
self, seq: List[nx.Graph] | List[str]
|
|
448
|
+
) -> List[nx.Graph]:
|
|
449
|
+
out: List[nx.Graph] = []
|
|
450
|
+
for item in seq:
|
|
451
|
+
g = (
|
|
452
|
+
rsmi_to_its(item, core=False, format=self.its_format)
|
|
453
|
+
if isinstance(item, str)
|
|
454
|
+
else item
|
|
455
|
+
)
|
|
456
|
+
if self._canonicaliser:
|
|
457
|
+
g = self._canonicaliser.canonicalise_graph(g).canonical_graph
|
|
458
|
+
if self._is_tuple_its(g):
|
|
459
|
+
out.append(g)
|
|
460
|
+
continue
|
|
461
|
+
g = h_to_explicit(g, its=True)
|
|
462
|
+
out.append(normalize_hcount_and_typesGH(g))
|
|
463
|
+
return out
|
|
464
|
+
|
|
465
|
+
@staticmethod
|
|
466
|
+
def _is_tuple_its(graph: nx.Graph) -> bool:
|
|
467
|
+
"""
|
|
468
|
+
Detect paired-attribute ITS graphs produced by the newer tuple format.
|
|
469
|
+
|
|
470
|
+
Tuple ITS nodes carry side-specific attributes directly, such as
|
|
471
|
+
``element=("C", "C")`` and ``lone_pairs=(0, 0)``. Legacy ITS graphs
|
|
472
|
+
instead keep the paired state primarily in ``typesGH``.
|
|
473
|
+
"""
|
|
474
|
+
if graph.number_of_nodes() == 0:
|
|
475
|
+
return False
|
|
476
|
+
_, attrs = next(iter(graph.nodes(data=True)))
|
|
477
|
+
element = attrs.get("element")
|
|
478
|
+
return isinstance(element, tuple) and len(element) == 2
|
|
479
|
+
|
|
480
|
+
@staticmethod
|
|
481
|
+
def _is_pair(value: Any) -> bool:
|
|
482
|
+
return isinstance(value, tuple) and len(value) == 2
|
|
483
|
+
|
|
484
|
+
@classmethod
|
|
485
|
+
def _is_observed_pair(cls, value: Any) -> bool:
|
|
486
|
+
return cls._is_pair(value) and not (
|
|
487
|
+
isinstance(value[0], set) and isinstance(value[1], set)
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
@staticmethod
|
|
491
|
+
def _timeline_pair(timeline: Any, step: int) -> Tuple[Any, Any]:
|
|
492
|
+
if not isinstance(timeline, tuple) or len(timeline) <= step + 1:
|
|
493
|
+
return (None, None)
|
|
494
|
+
return (timeline[step], timeline[step + 1])
|
|
495
|
+
|
|
496
|
+
@classmethod
|
|
497
|
+
def _edge_pair_history_to_timeline(
|
|
498
|
+
cls,
|
|
499
|
+
history: Tuple[Any, ...],
|
|
500
|
+
u_present: Any,
|
|
501
|
+
v_present: Any,
|
|
502
|
+
) -> Tuple[Any, ...]:
|
|
503
|
+
"""
|
|
504
|
+
Convert ITS step-pair history into mechanism-state timeline.
|
|
505
|
+
|
|
506
|
+
Example: ``((2, 1), (1, 2))`` becomes ``(2, 1, 2)``.
|
|
507
|
+
Missing edge states are ``0`` when both endpoint atoms exist and
|
|
508
|
+
``None`` when an endpoint is absent.
|
|
509
|
+
"""
|
|
510
|
+
if not history:
|
|
511
|
+
return ()
|
|
512
|
+
|
|
513
|
+
timeline: List[Any] = [None] * (len(history) + 1)
|
|
514
|
+
for idx, value in enumerate(history):
|
|
515
|
+
if cls._is_pair(value) and not (
|
|
516
|
+
isinstance(value[0], set) and isinstance(value[1], set)
|
|
517
|
+
):
|
|
518
|
+
timeline[idx] = value[0]
|
|
519
|
+
timeline[idx + 1] = value[1]
|
|
520
|
+
return tuple(
|
|
521
|
+
cls._fill_missing_edge_state(value, idx, u_present, v_present)
|
|
522
|
+
for idx, value in enumerate(timeline)
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
@staticmethod
|
|
526
|
+
def _fill_missing_edge_state(
|
|
527
|
+
value: Any,
|
|
528
|
+
idx: int,
|
|
529
|
+
u_present: Any,
|
|
530
|
+
v_present: Any,
|
|
531
|
+
) -> Any:
|
|
532
|
+
if value is not None:
|
|
533
|
+
return value
|
|
534
|
+
if (
|
|
535
|
+
isinstance(u_present, tuple)
|
|
536
|
+
and isinstance(v_present, tuple)
|
|
537
|
+
and len(u_present) > idx
|
|
538
|
+
and len(v_present) > idx
|
|
539
|
+
and u_present[idx] is True
|
|
540
|
+
and v_present[idx] is True
|
|
541
|
+
):
|
|
542
|
+
return 0.0
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
def _compute_mappings(self, graphs: List[nx.Graph]) -> List[GraphMapping]:
|
|
546
|
+
if self._tuple_its:
|
|
547
|
+
return [
|
|
548
|
+
self._compute_tuple_mapping(graphs[i], graphs[i + 1])
|
|
549
|
+
for i in range(len(graphs) - 1)
|
|
550
|
+
]
|
|
551
|
+
|
|
552
|
+
mappings: List[GraphMapping] = []
|
|
553
|
+
for i in range(len(graphs) - 1):
|
|
554
|
+
m = MCSMatcher(node_label_names=self._node_label_names)
|
|
555
|
+
m.find_rc_mapping(
|
|
556
|
+
graphs[i], graphs[i + 1], mcs=self.mcs, mcs_mol=self.mcs_mol
|
|
557
|
+
)
|
|
558
|
+
if not m._mappings:
|
|
559
|
+
raise RuntimeError(f"No mapping between {i} and {i+1}")
|
|
560
|
+
mappings.append(m._mappings[0])
|
|
561
|
+
return mappings
|
|
562
|
+
|
|
563
|
+
@classmethod
|
|
564
|
+
def _compute_tuple_mapping(cls, left: nx.Graph, right: nx.Graph) -> GraphMapping:
|
|
565
|
+
left_by_map = cls._nodes_by_atom_map(left)
|
|
566
|
+
right_by_map = cls._nodes_by_atom_map(right)
|
|
567
|
+
common_maps = sorted(set(left_by_map) & set(right_by_map))
|
|
568
|
+
mapping = {left_by_map[amap]: right_by_map[amap] for amap in common_maps}
|
|
569
|
+
|
|
570
|
+
if mapping:
|
|
571
|
+
return mapping
|
|
572
|
+
|
|
573
|
+
common_nodes = sorted(set(left.nodes()) & set(right.nodes()))
|
|
574
|
+
return {node: node for node in common_nodes}
|
|
575
|
+
|
|
576
|
+
@classmethod
|
|
577
|
+
def _has_tuple_atom_maps(cls, graphs: List[nx.Graph]) -> bool:
|
|
578
|
+
return any(
|
|
579
|
+
cls._tuple_node_pid(attrs) is not None
|
|
580
|
+
for graph in graphs
|
|
581
|
+
for _, attrs in graph.nodes(data=True)
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
@staticmethod
|
|
585
|
+
def _tuple_node_pid(attrs: Mapping[str, Any]) -> int | None:
|
|
586
|
+
atom_map = attrs.get("atom_map")
|
|
587
|
+
if isinstance(atom_map, tuple) and len(atom_map) == 2:
|
|
588
|
+
atom_map = atom_map[1] if atom_map[1] not in (None, 0, "") else atom_map[0]
|
|
589
|
+
if atom_map in (None, 0, ""):
|
|
590
|
+
return None
|
|
591
|
+
return int(atom_map)
|
|
592
|
+
|
|
593
|
+
@staticmethod
|
|
594
|
+
def _nodes_by_atom_map(graph: nx.Graph) -> Dict[int, NodeID]:
|
|
595
|
+
by_map: Dict[int, NodeID] = {}
|
|
596
|
+
for node, attrs in graph.nodes(data=True):
|
|
597
|
+
atom_map = MTG._tuple_node_pid(attrs)
|
|
598
|
+
if atom_map is None:
|
|
599
|
+
continue
|
|
600
|
+
if atom_map in by_map:
|
|
601
|
+
continue
|
|
602
|
+
by_map[atom_map] = node
|
|
603
|
+
return by_map
|
|
604
|
+
|
|
605
|
+
@property
|
|
606
|
+
def node_mapping(self) -> Dict[Tuple[int, NodeID], int]:
|
|
607
|
+
return dict(self._node_map)
|
|
608
|
+
|
|
609
|
+
@property
|
|
610
|
+
def k(self) -> int:
|
|
611
|
+
return self._k
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from synkit.Graph.MTG.mtg import MTG
|
|
3
|
+
from synkit.Rule.Apply.rule_matcher import RuleMatcher
|
|
4
|
+
from synkit.Graph.MTG.mcs_matcher import MCSMatcher
|
|
5
|
+
from networkx import Graph
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def find_mtg(
|
|
9
|
+
g1: Graph,
|
|
10
|
+
g2: Graph,
|
|
11
|
+
ground_truth: str,
|
|
12
|
+
node_label_names: Optional[List[str]] = None,
|
|
13
|
+
) -> Optional[MTG]:
|
|
14
|
+
"""Attempt to construct a Mapping Transformation Graph (MTG) for two input graphs
|
|
15
|
+
by finding maximum common substructure mappings and validating against a ground truth.
|
|
16
|
+
|
|
17
|
+
:param g1: The first input graph to match.
|
|
18
|
+
:type g1: networkx.Graph
|
|
19
|
+
:param g2: The second input graph to match.
|
|
20
|
+
:type g2: networkx.Graph
|
|
21
|
+
:param ground_truth: A string representation of the expected atom-atom mapping (AAM)
|
|
22
|
+
used to validate candidate mappings.
|
|
23
|
+
:type ground_truth: str
|
|
24
|
+
:param node_label_names: List of node attribute names to use for MCS matching.
|
|
25
|
+
Defaults to ["element", "charge", "hcount"].
|
|
26
|
+
:type node_label_names: list of str, optional
|
|
27
|
+
:return: An MTG instance if a valid mapping satisfying the ground truth is found;
|
|
28
|
+
otherwise, None.
|
|
29
|
+
:rtype: MTG or None
|
|
30
|
+
:raises ValueError: If input graphs are empty or ground_truth is invalid format.
|
|
31
|
+
|
|
32
|
+
.. rubric:: Examples
|
|
33
|
+
|
|
34
|
+
>>> from networkx import Graph
|
|
35
|
+
>>> g1, g2 = Graph(), Graph()
|
|
36
|
+
>>> # populate g1 and g2 with nodes/edges
|
|
37
|
+
>>> mtg = find_mtg(
|
|
38
|
+
... g1,
|
|
39
|
+
... g2,
|
|
40
|
+
... ground_truth="{0:1, 1:0}",
|
|
41
|
+
... node_label_names=["element", "charge", "hcount"]
|
|
42
|
+
... )
|
|
43
|
+
>>> if mtg:
|
|
44
|
+
... print(mtg)
|
|
45
|
+
"""
|
|
46
|
+
# Validate inputs
|
|
47
|
+
if not g1 or not g2:
|
|
48
|
+
raise ValueError("Input graphs g1 and g2 must be non-empty.")
|
|
49
|
+
if not isinstance(ground_truth, str) or not ground_truth.strip():
|
|
50
|
+
raise ValueError("Ground truth mapping must be a non-empty string.")
|
|
51
|
+
|
|
52
|
+
# Set default node_label_names if not provided
|
|
53
|
+
if node_label_names is None:
|
|
54
|
+
node_label_names = ["element", "charge", "hcount"]
|
|
55
|
+
|
|
56
|
+
# Initialize maximum common substructure matcher
|
|
57
|
+
mcs = MCSMatcher(node_label_names=node_label_names)
|
|
58
|
+
mcs.find_rc_mapping(g1, g2, mcs=False)
|
|
59
|
+
mappings = mcs._mappings
|
|
60
|
+
|
|
61
|
+
for mapping in mappings:
|
|
62
|
+
# Construct MTG using current mapping
|
|
63
|
+
mtg = MTG([g1, g2], mappings=[mapping])
|
|
64
|
+
aam = mtg.get_aam()
|
|
65
|
+
try:
|
|
66
|
+
# Validate generated AAM against ground truth
|
|
67
|
+
RuleMatcher(ground_truth, aam, explicit_h=False)
|
|
68
|
+
return mtg
|
|
69
|
+
except AssertionError:
|
|
70
|
+
# Mapping did not satisfy ground truth, try next
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# No valid mapping found
|
|
74
|
+
return None
|