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
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
from copy import copy, deepcopy
|
|
2
|
+
import networkx as nx
|
|
3
|
+
from operator import eq
|
|
4
|
+
from typing import List, Set, Any, Tuple, Iterable, Optional
|
|
5
|
+
from networkx.algorithms.isomorphism import generic_node_match, generic_edge_match
|
|
6
|
+
|
|
7
|
+
from synkit.Graph.Feature.graph_descriptors import GraphDescriptor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def has_XH(G: nx.Graph) -> bool:
|
|
11
|
+
"""Check whether the graph contains any heavy atom–hydrogen bond.
|
|
12
|
+
|
|
13
|
+
A heavy atom is any atom whose 'element' attribute is not 'H'.
|
|
14
|
+
This function searches for any edge that connects a heavy atom to a hydrogen atom.
|
|
15
|
+
|
|
16
|
+
:param G: A graph where each node has an 'element' attribute indicating the atom type.
|
|
17
|
+
:type G: nx.Graph
|
|
18
|
+
|
|
19
|
+
:return: True if at least one edge connects a hydrogen atom ('H') to a heavy atom (element ≠ 'H').
|
|
20
|
+
False otherwise.
|
|
21
|
+
:rtype: bool
|
|
22
|
+
"""
|
|
23
|
+
for u, v, _ in G.edges(data=True):
|
|
24
|
+
el_u = G.nodes[u].get("element")
|
|
25
|
+
el_v = G.nodes[v].get("element")
|
|
26
|
+
if (el_u != "H" and el_v == "H") or (el_v != "H" and el_u == "H"):
|
|
27
|
+
return True
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def has_HH(G: nx.Graph) -> bool:
|
|
32
|
+
"""Check whether the graph contains any heavy atom–hydrogen bond.
|
|
33
|
+
|
|
34
|
+
A heavy atom is any atom whose 'element' attribute is not 'H'.
|
|
35
|
+
This function searches for any edge that connects a heavy atom to a hydrogen atom.
|
|
36
|
+
|
|
37
|
+
:param G: A graph where each node has an 'element' attribute indicating the atom type.
|
|
38
|
+
:type G: nx.Graph
|
|
39
|
+
|
|
40
|
+
:return: True if at least one edge connects a hydrogen atom ('H') to a heavy atom (element ≠ 'H').
|
|
41
|
+
False otherwise.
|
|
42
|
+
:rtype: bool
|
|
43
|
+
"""
|
|
44
|
+
for u, v, _ in G.edges(data=True):
|
|
45
|
+
el_u = G.nodes[u].get("element")
|
|
46
|
+
el_v = G.nodes[v].get("element")
|
|
47
|
+
if el_u == el_v == "H":
|
|
48
|
+
return True
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def h_to_implicit(G: nx.Graph) -> nx.Graph:
|
|
53
|
+
"""Convert explicit hydrogen atoms to implicit counts on heavy atoms.
|
|
54
|
+
|
|
55
|
+
For each hydrogen atom ('element' == 'H'), its neighbor (assumed to be a heavy atom)
|
|
56
|
+
will have its 'hcount' attribute incremented. The hydrogen nodes are then removed.
|
|
57
|
+
|
|
58
|
+
:param G: Input graph with explicit hydrogen atoms as nodes (element='H').
|
|
59
|
+
Heavy atoms must have 'element' and optionally 'hcount' attributes.
|
|
60
|
+
:type G: nx.Graph
|
|
61
|
+
|
|
62
|
+
:return: A copy of the original graph with hydrogen atoms removed and their counts
|
|
63
|
+
added to the corresponding heavy atoms' 'hcount' attribute.
|
|
64
|
+
:rtype: nx.Graph
|
|
65
|
+
"""
|
|
66
|
+
H2 = G.copy()
|
|
67
|
+
h_nodes = [n for n, d in H2.nodes(data=True) if d.get("element") == "H"]
|
|
68
|
+
|
|
69
|
+
for h in h_nodes:
|
|
70
|
+
neighbors = list(H2.neighbors(h))
|
|
71
|
+
for heavy in neighbors:
|
|
72
|
+
if H2.nodes[heavy].get("element") != "H":
|
|
73
|
+
H2.nodes[heavy]["hcount"] = H2.nodes[heavy].get("hcount", 0) + 1
|
|
74
|
+
H2.remove_node(h)
|
|
75
|
+
|
|
76
|
+
return H2
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def normalize_edge_orders(G: nx.Graph) -> None:
|
|
80
|
+
"""
|
|
81
|
+
In-place normalize all edge attributes in G:
|
|
82
|
+
- If 'order' is a float or int, replace it with (order, order).
|
|
83
|
+
- If 'standard_order' is missing, set it to 0.0.
|
|
84
|
+
"""
|
|
85
|
+
for _, _, data in G.edges(data=True):
|
|
86
|
+
o = data.get("order")
|
|
87
|
+
# Wrap scalar orders into tuples
|
|
88
|
+
if isinstance(o, (int, float)):
|
|
89
|
+
data["order"] = (float(o), float(o))
|
|
90
|
+
if "standard_order" not in data:
|
|
91
|
+
data["standard_order"] = 0.0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def h_to_explicit(G: nx.Graph, nodes: List[int] = None, its: bool = False) -> nx.Graph:
|
|
95
|
+
"""Convert implicit hydrogen counts on heavy atoms into explicit hydrogen
|
|
96
|
+
nodes.
|
|
97
|
+
|
|
98
|
+
For each node ID in `nodes`, this function reads the node's 'hcount', adds that many
|
|
99
|
+
new hydrogen nodes, connects them to the node with a single bond (order=1.0), and
|
|
100
|
+
decrements the node's 'hcount'. Optionally updates the 'typesGH' field if present.
|
|
101
|
+
|
|
102
|
+
:param G: Input graph with heavy atoms containing 'hcount' indicating implicit hydrogens.
|
|
103
|
+
:type G: nx.Graph
|
|
104
|
+
:param nodes: List of node IDs (typically heavy atoms) on which to expand implicit hydrogens.
|
|
105
|
+
:type nodes: List[int]
|
|
106
|
+
|
|
107
|
+
:return: A copy of the graph with new explicit hydrogen nodes added and connected
|
|
108
|
+
to the specified heavy atoms.
|
|
109
|
+
:rtype: nx.Graph
|
|
110
|
+
"""
|
|
111
|
+
if nodes is None or len(nodes) == 0:
|
|
112
|
+
nodes = G.nodes()
|
|
113
|
+
H2 = G.copy()
|
|
114
|
+
max_node = max(H2.nodes) if H2.nodes else 0
|
|
115
|
+
|
|
116
|
+
for heavy in nodes:
|
|
117
|
+
if heavy not in H2:
|
|
118
|
+
continue
|
|
119
|
+
count = H2.nodes[heavy].get("hcount", 0)
|
|
120
|
+
if its and _is_pair_hcount(count):
|
|
121
|
+
max_node = _expand_paired_its_hydrogens(H2, heavy, max_node)
|
|
122
|
+
continue
|
|
123
|
+
if count <= 0:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
restored_maps = list(H2.nodes[heavy].pop("_implicit_h_atom_maps", ()))
|
|
127
|
+
|
|
128
|
+
for _ in range(count):
|
|
129
|
+
max_node += 1
|
|
130
|
+
atom_map = restored_maps.pop(0) if restored_maps else 0
|
|
131
|
+
H2.add_node(
|
|
132
|
+
max_node,
|
|
133
|
+
element="H",
|
|
134
|
+
aromatic=False,
|
|
135
|
+
hcount=0,
|
|
136
|
+
charge=0,
|
|
137
|
+
atom_map=atom_map,
|
|
138
|
+
_restored_mapped_h=bool(atom_map),
|
|
139
|
+
typesGH=(("H", False, 0, 0, []), ("H", False, 0, 0, [])),
|
|
140
|
+
)
|
|
141
|
+
H2.add_edge(heavy, max_node, order=1.0)
|
|
142
|
+
|
|
143
|
+
H2.nodes[heavy]["hcount"] -= count
|
|
144
|
+
if restored_maps:
|
|
145
|
+
H2.nodes[heavy]["_implicit_h_atom_maps"] = tuple(restored_maps)
|
|
146
|
+
|
|
147
|
+
# Optionally adjust the typesGH field if it exists
|
|
148
|
+
if "typesGH" in H2.nodes[heavy]:
|
|
149
|
+
tgh = H2.nodes[heavy]["typesGH"]
|
|
150
|
+
tgh_list = [list(row) for row in tgh]
|
|
151
|
+
tgh_list[0][2] -= count # Assume hcount is stored at position [0][2]
|
|
152
|
+
H2.nodes[heavy]["typesGH"] = tuple(tuple(row) for row in tgh_list)
|
|
153
|
+
if its:
|
|
154
|
+
normalize_edge_orders(H2)
|
|
155
|
+
|
|
156
|
+
return H2
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _is_pair_hcount(value: Any) -> bool:
|
|
160
|
+
"""Return whether a value is a 2-item integer hydrogen-count pair."""
|
|
161
|
+
return (
|
|
162
|
+
isinstance(value, (list, tuple))
|
|
163
|
+
and len(value) == 2
|
|
164
|
+
and all(isinstance(item, int) for item in value)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _paired_hydrogen_node_attrs(present: Tuple[bool, bool]) -> dict:
|
|
169
|
+
"""Build named paired attrs for a tuple-style ITS hydrogen node."""
|
|
170
|
+
return {
|
|
171
|
+
"element": ("H", "H"),
|
|
172
|
+
"aromatic": (False, False),
|
|
173
|
+
"hcount": (0, 0),
|
|
174
|
+
"charge": (0, 0),
|
|
175
|
+
"radical": (0, 0),
|
|
176
|
+
"lone_pairs": (0, 0),
|
|
177
|
+
"valence_electrons": (1, 1),
|
|
178
|
+
"neighbors": ([], []),
|
|
179
|
+
"atom_map": (0, 0),
|
|
180
|
+
"present": present,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _expand_paired_its_hydrogens(
|
|
185
|
+
graph: nx.Graph,
|
|
186
|
+
heavy: int,
|
|
187
|
+
max_node: int,
|
|
188
|
+
) -> int:
|
|
189
|
+
"""Expand paired ITS hydrogen counts into shared and side-only H nodes."""
|
|
190
|
+
react_h, prod_h = graph.nodes[heavy]["hcount"]
|
|
191
|
+
shared = min(react_h, prod_h)
|
|
192
|
+
react_only = react_h - shared
|
|
193
|
+
prod_only = prod_h - shared
|
|
194
|
+
|
|
195
|
+
expansion_plan = (
|
|
196
|
+
[((True, True), (1.0, 1.0))] * shared
|
|
197
|
+
+ [((True, False), (1.0, 0.0))] * react_only
|
|
198
|
+
+ [((False, True), (0.0, 1.0))] * prod_only
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
for present, bond_pair in expansion_plan:
|
|
202
|
+
max_node += 1
|
|
203
|
+
graph.add_node(max_node, **_paired_hydrogen_node_attrs(present))
|
|
204
|
+
graph.add_edge(
|
|
205
|
+
heavy,
|
|
206
|
+
max_node,
|
|
207
|
+
order=bond_pair,
|
|
208
|
+
kekule_order=bond_pair,
|
|
209
|
+
sigma_order=bond_pair,
|
|
210
|
+
pi_order=(0.0, 0.0),
|
|
211
|
+
standard_order=bond_pair[0] - bond_pair[1],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
graph.nodes[heavy]["hcount"] = (0, 0)
|
|
215
|
+
return max_node
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def implicit_hydrogen(
|
|
219
|
+
graph: nx.Graph, preserve_atom_maps: Set[int], reindex: bool = False
|
|
220
|
+
) -> nx.Graph:
|
|
221
|
+
"""Adds implicit hydrogens to a molecular graph and removes non-preserved
|
|
222
|
+
hydrogens. This function operates on a deep copy of the input graph to
|
|
223
|
+
avoid in-place modifications. It counts hydrogen neighbors for each non-
|
|
224
|
+
hydrogen node and adjusts based on hydrogens that need to be preserved.
|
|
225
|
+
Non-preserved hydrogen nodes are removed from the graph.
|
|
226
|
+
|
|
227
|
+
:param graph: A NetworkX graph representing the molecule, where each node has an 'element'
|
|
228
|
+
attribute for the element type (e.g., 'C', 'H') and an 'atom_map' attribute for atom mapping.
|
|
229
|
+
:type graph: nx.Graph
|
|
230
|
+
:param preserve_atom_maps: Set of atom map numbers for hydrogens that should be preserved.
|
|
231
|
+
:type preserve_atom_maps: Set[int]
|
|
232
|
+
:param reindex: If true, reindexes node indices and atom maps sequentially after modifications.
|
|
233
|
+
:type reindex: bool
|
|
234
|
+
|
|
235
|
+
:return: Graph with non-preserved hydrogens removed and hydrogen counts
|
|
236
|
+
adjusted on the remaining atoms.
|
|
237
|
+
:rtype: nx.Graph
|
|
238
|
+
"""
|
|
239
|
+
# Create a deep copy of the graph to avoid in-place modifications
|
|
240
|
+
new_graph = copy(graph)
|
|
241
|
+
|
|
242
|
+
# First pass: count hydrogen neighbors for each non-hydrogen node
|
|
243
|
+
for node, data in new_graph.nodes(data=True):
|
|
244
|
+
if data["element"] != "H": # Skip hydrogen atoms
|
|
245
|
+
count_h_explicit = sum(
|
|
246
|
+
1
|
|
247
|
+
for neighbor in new_graph.neighbors(node)
|
|
248
|
+
if new_graph.nodes[neighbor]["element"] == "H"
|
|
249
|
+
)
|
|
250
|
+
count_h_implicit = data["hcount"]
|
|
251
|
+
new_graph.nodes[node]["hcount"] = count_h_explicit + count_h_implicit
|
|
252
|
+
|
|
253
|
+
# List of hydrogens to preserve based on atom map
|
|
254
|
+
preserved_hydrogens = [
|
|
255
|
+
node
|
|
256
|
+
for node, data in new_graph.nodes(data=True)
|
|
257
|
+
if data["element"] == "H" and data["atom_map"] in preserve_atom_maps
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
# Adjust hydrogen counts for preserved hydrogens
|
|
261
|
+
for hydrogen in preserved_hydrogens:
|
|
262
|
+
for neighbor in new_graph.neighbors(hydrogen):
|
|
263
|
+
if (
|
|
264
|
+
new_graph.nodes[neighbor]["element"] != "H"
|
|
265
|
+
): # Only adjust non-hydrogen neighbors
|
|
266
|
+
new_graph.nodes[neighbor]["hcount"] -= 1
|
|
267
|
+
|
|
268
|
+
# Remove non-preserved hydrogen nodes from the graph
|
|
269
|
+
hydrogen_to_remove = [
|
|
270
|
+
node
|
|
271
|
+
for node, data in new_graph.nodes(data=True)
|
|
272
|
+
if data["element"] == "H" and node not in preserved_hydrogens
|
|
273
|
+
]
|
|
274
|
+
new_graph.remove_nodes_from(hydrogen_to_remove)
|
|
275
|
+
|
|
276
|
+
# Reindex the graph if reindex=True
|
|
277
|
+
if reindex:
|
|
278
|
+
# Create new mapping and update node indices and atom maps
|
|
279
|
+
mapping = {node: idx + 1 for idx, node in enumerate(new_graph.nodes())}
|
|
280
|
+
new_graph = nx.relabel_nodes(new_graph, mapping) # Relabel nodes
|
|
281
|
+
|
|
282
|
+
# Update atom maps to reflect new node indices
|
|
283
|
+
for node, data in new_graph.nodes(data=True):
|
|
284
|
+
data["atom_map"] = node # Sync atom map with node index
|
|
285
|
+
|
|
286
|
+
return new_graph
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _normalize_sequence_at_index(
|
|
290
|
+
seqs: Iterable[Any], index: int = 2, target_min: int = 0
|
|
291
|
+
) -> Optional[Tuple[Any, ...]]:
|
|
292
|
+
"""
|
|
293
|
+
If possible, shift the integer at `index` in each sequence so that the minimum becomes
|
|
294
|
+
`target_min`. Returns a new tuple of sequences if any change is needed, else None.
|
|
295
|
+
"""
|
|
296
|
+
# collect valid ints at the index
|
|
297
|
+
valid_vals = [
|
|
298
|
+
t[index]
|
|
299
|
+
for t in seqs
|
|
300
|
+
if isinstance(t, (list, tuple)) and len(t) > index and isinstance(t[index], int)
|
|
301
|
+
]
|
|
302
|
+
if not valid_vals:
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
offset = min(valid_vals) - target_min
|
|
306
|
+
if offset == 0:
|
|
307
|
+
return None # already normalized
|
|
308
|
+
|
|
309
|
+
def normalize(t):
|
|
310
|
+
if (
|
|
311
|
+
isinstance(t, (list, tuple))
|
|
312
|
+
and len(t) > index
|
|
313
|
+
and isinstance(t[index], int)
|
|
314
|
+
):
|
|
315
|
+
t_list = list(t)
|
|
316
|
+
t_list[index] = t_list[index] - offset
|
|
317
|
+
return tuple(t_list)
|
|
318
|
+
return t # leave untouched
|
|
319
|
+
|
|
320
|
+
return tuple(normalize(t) for t in seqs)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def standardize_hydrogen(G: nx.Graph, in_place: bool = False) -> nx.Graph:
|
|
324
|
+
"""
|
|
325
|
+
For each node, shift the third element (index 2) of each tuple in 'typesGH' so that the
|
|
326
|
+
minimum among those values becomes zero. Nonconforming entries are preserved.
|
|
327
|
+
"""
|
|
328
|
+
target = G if in_place else G.copy()
|
|
329
|
+
|
|
330
|
+
for node, data in target.nodes(data=True):
|
|
331
|
+
typesGH = data.get("typesGH")
|
|
332
|
+
if not typesGH:
|
|
333
|
+
continue
|
|
334
|
+
normalized = _normalize_sequence_at_index(typesGH, index=2, target_min=0)
|
|
335
|
+
if normalized is not None:
|
|
336
|
+
target.nodes[node]["typesGH"] = normalized
|
|
337
|
+
|
|
338
|
+
return target
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def check_equivariant_graph(
|
|
342
|
+
its_graphs: List[nx.Graph],
|
|
343
|
+
) -> Tuple[List[Tuple[int, int]], int]:
|
|
344
|
+
"""Checks for isomorphism among a list of ITS graphs.
|
|
345
|
+
|
|
346
|
+
:param its_graphs: A list of ITS graphs.
|
|
347
|
+
:type its_graphs: List[nx.Graph]
|
|
348
|
+
|
|
349
|
+
:return: A list of tuples representing pairs of indices of isomorphic graphs.
|
|
350
|
+
:rtype: List[Tuple[int, int]]
|
|
351
|
+
"""
|
|
352
|
+
nodeLabelNames = ["typesGH"]
|
|
353
|
+
nodeLabelDefault = [()]
|
|
354
|
+
nodeLabelOperator = [eq]
|
|
355
|
+
nodeMatch = generic_node_match(nodeLabelNames, nodeLabelDefault, nodeLabelOperator)
|
|
356
|
+
edgeMatch = generic_edge_match("order", 1, eq)
|
|
357
|
+
|
|
358
|
+
classified = []
|
|
359
|
+
|
|
360
|
+
for i in range(1, len(its_graphs)):
|
|
361
|
+
# Compare the first graph with each subsequent graph
|
|
362
|
+
if nx.is_isomorphic(
|
|
363
|
+
its_graphs[0], its_graphs[i], node_match=nodeMatch, edge_match=edgeMatch
|
|
364
|
+
):
|
|
365
|
+
classified.append((0, i))
|
|
366
|
+
return classified, len(classified)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def check_explicit_hydrogen(graph: nx.Graph) -> tuple:
|
|
370
|
+
"""Counts the explicit hydrogen nodes in the given graph and collects their
|
|
371
|
+
IDs.
|
|
372
|
+
|
|
373
|
+
:param graph: The graph to inspect.
|
|
374
|
+
:type graph: nx.Graph
|
|
375
|
+
|
|
376
|
+
:return: A tuple containing the number of hydrogen nodes and a list of their node IDs.
|
|
377
|
+
:rtype: tuple
|
|
378
|
+
"""
|
|
379
|
+
hydrogen_nodes = [
|
|
380
|
+
node_id
|
|
381
|
+
for node_id, attr in graph.nodes(data=True)
|
|
382
|
+
if attr.get("element") == "H"
|
|
383
|
+
]
|
|
384
|
+
return len(hydrogen_nodes), hydrogen_nodes
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def check_hcount_change(react_graph: nx.Graph, prod_graph: nx.Graph) -> int:
|
|
388
|
+
"""Computes the maximum change in hydrogen count ('hcount') between
|
|
389
|
+
corresponding nodes in the reactant and product graphs. It considers both
|
|
390
|
+
hydrogen formation and breakage.
|
|
391
|
+
|
|
392
|
+
:param react_graph: The graph representing reactants.
|
|
393
|
+
:type react_graph: nx.Graph
|
|
394
|
+
:param prod_graph: The graph representing products.
|
|
395
|
+
:type prod_graph: nx.Graph
|
|
396
|
+
|
|
397
|
+
:return: The maximum hydrogen change observed across all nodes.
|
|
398
|
+
:rtype: int
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
def _coerce_hcount(value: Any) -> int:
|
|
402
|
+
try:
|
|
403
|
+
if isinstance(value, (tuple, list)):
|
|
404
|
+
values = [int(item or 0) for item in value]
|
|
405
|
+
return max(values, default=0)
|
|
406
|
+
return int(value or 0)
|
|
407
|
+
except (TypeError, ValueError):
|
|
408
|
+
return 0
|
|
409
|
+
|
|
410
|
+
hcount_break, _ = check_explicit_hydrogen(react_graph)
|
|
411
|
+
hcount_form, _ = check_explicit_hydrogen(prod_graph)
|
|
412
|
+
|
|
413
|
+
for node_id in set(react_graph.nodes) | set(prod_graph.nodes):
|
|
414
|
+
react_hcount = (
|
|
415
|
+
_coerce_hcount(react_graph.nodes[node_id].get("hcount", 0))
|
|
416
|
+
if node_id in react_graph
|
|
417
|
+
else 0
|
|
418
|
+
)
|
|
419
|
+
prod_hcount = (
|
|
420
|
+
_coerce_hcount(prod_graph.nodes[node_id].get("hcount", 0))
|
|
421
|
+
if node_id in prod_graph
|
|
422
|
+
else 0
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
if react_hcount >= prod_hcount:
|
|
426
|
+
hcount_break += react_hcount - prod_hcount
|
|
427
|
+
else:
|
|
428
|
+
hcount_form += prod_hcount - react_hcount
|
|
429
|
+
|
|
430
|
+
return max(hcount_break, hcount_form)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def get_cycle_member_rings(G: nx.Graph, type="minimal") -> List[int]:
|
|
434
|
+
"""Identifies all cycles in the given graph using cycle bases to ensure no
|
|
435
|
+
overlap and returns a list of the sizes of these cycles (member rings),
|
|
436
|
+
sorted in ascending order.
|
|
437
|
+
|
|
438
|
+
:param G: The NetworkX graph to be analyzed.
|
|
439
|
+
:type G: nx.Graph
|
|
440
|
+
|
|
441
|
+
:return: A sorted list of cycle sizes (member rings) found in the graph.
|
|
442
|
+
:rtype: List[int]
|
|
443
|
+
"""
|
|
444
|
+
if not isinstance(G, nx.Graph):
|
|
445
|
+
raise TypeError("Input must be a networkx Graph object.")
|
|
446
|
+
|
|
447
|
+
if type == "minimal":
|
|
448
|
+
cycles = nx.minimum_cycle_basis(G)
|
|
449
|
+
else:
|
|
450
|
+
cycles = nx.cycle_basis(G)
|
|
451
|
+
member_rings = [len(cycle) for cycle in cycles]
|
|
452
|
+
|
|
453
|
+
member_rings.sort()
|
|
454
|
+
|
|
455
|
+
return member_rings
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def get_priority(reaction_centers: List[Any]) -> List[int]:
|
|
459
|
+
"""Evaluate reaction centers for specific graph characteristics, selecting
|
|
460
|
+
indices based on the shortest reaction paths and maximum ring sizes, and
|
|
461
|
+
adjusting for certain graph types by modifying the ring information.
|
|
462
|
+
|
|
463
|
+
:param reaction_centers: List[Any], a list of reaction centers where each center should be
|
|
464
|
+
capable of being analyzed for graph type and ring sizes.
|
|
465
|
+
|
|
466
|
+
:return: Indices selected by shortest reaction path and largest ring size,
|
|
467
|
+
falling back to the shortest-path indices.
|
|
468
|
+
:rtype: List[int]
|
|
469
|
+
"""
|
|
470
|
+
# Extract topology types and ring sizes from reaction centers
|
|
471
|
+
topo_type = [
|
|
472
|
+
GraphDescriptor.check_graph_type(center) for center in reaction_centers
|
|
473
|
+
]
|
|
474
|
+
cyclic = [
|
|
475
|
+
get_cycle_member_rings(center, "fundamental") for center in reaction_centers
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
# Adjust ring information based on the graph type
|
|
479
|
+
for index, graph_type in enumerate(topo_type):
|
|
480
|
+
if graph_type in ["Acyclic", "Complex Cyclic"]:
|
|
481
|
+
cyclic[index] = [0] + cyclic[index]
|
|
482
|
+
|
|
483
|
+
# Determine minimum reaction steps
|
|
484
|
+
reaction_steps = [len(rings) for rings in cyclic]
|
|
485
|
+
min_reaction_step = min(reaction_steps)
|
|
486
|
+
|
|
487
|
+
# Filter indices with the minimum reaction steps
|
|
488
|
+
indices_shortest = [
|
|
489
|
+
i for i, steps in enumerate(reaction_steps) if steps == min_reaction_step
|
|
490
|
+
]
|
|
491
|
+
|
|
492
|
+
# Filter indices with the maximum ring size
|
|
493
|
+
max_size = max(
|
|
494
|
+
max(rings) for rings in cyclic if rings
|
|
495
|
+
) # Safeguard against empty sublists
|
|
496
|
+
prior_indices = [i for i, rings in enumerate(cyclic) if max(rings) == max_size]
|
|
497
|
+
|
|
498
|
+
# Combine criteria for final indices
|
|
499
|
+
final_indices = [index for index in prior_indices if index in indices_shortest]
|
|
500
|
+
|
|
501
|
+
# Fallback to shortest indices if no indices meet both criteria
|
|
502
|
+
if not final_indices:
|
|
503
|
+
return indices_shortest
|
|
504
|
+
|
|
505
|
+
return final_indices
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _normalize_h_pair(h_react: int, h_prod: int) -> Tuple[int, int]:
|
|
509
|
+
"""Normalize reactant/product hydrogen counts to relative change form.
|
|
510
|
+
|
|
511
|
+
.. rubric:: Examples
|
|
512
|
+
|
|
513
|
+
(1, 1) -> (0, 0)
|
|
514
|
+
(2, 1) -> (1, 0)
|
|
515
|
+
(1, 2) -> (0, 1)
|
|
516
|
+
|
|
517
|
+
:param h_react: Hydrogen count in reactant state.
|
|
518
|
+
:type h_react: int
|
|
519
|
+
:param h_prod: Hydrogen count in product state.
|
|
520
|
+
:type h_prod: int
|
|
521
|
+
:return: Normalized hydrogen pair.
|
|
522
|
+
:rtype: Tuple[int, int]
|
|
523
|
+
"""
|
|
524
|
+
common = min(h_react, h_prod)
|
|
525
|
+
return h_react - common, h_prod - common
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def normalize_h_pair_graph(rc_graph: nx.Graph, inplace: bool = False) -> nx.Graph:
|
|
529
|
+
"""
|
|
530
|
+
Normalize paired hydrogen counts for all ITS nodes.
|
|
531
|
+
|
|
532
|
+
New-style ITS nodes may store ``hcount`` directly as
|
|
533
|
+
``(reactant_hcount, product_hcount)``. Legacy ITS nodes may instead store
|
|
534
|
+
hydrogen counts inside ``typesGH``:
|
|
535
|
+
|
|
536
|
+
``typesGH = (reactant_attr, product_attr)``
|
|
537
|
+
|
|
538
|
+
where each side tuple has the form
|
|
539
|
+
``(element, aromatic, hydrogen_count, charge, neighbors)``.
|
|
540
|
+
Both representations are normalized when present.
|
|
541
|
+
|
|
542
|
+
:param rc_graph: Reaction-center graph.
|
|
543
|
+
:type rc_graph: nx.Graph
|
|
544
|
+
:param inplace: Whether to modify the input graph in place.
|
|
545
|
+
:type inplace: bool
|
|
546
|
+
:return: Graph with normalized ``typesGH`` hydrogen fields.
|
|
547
|
+
:rtype: nx.Graph
|
|
548
|
+
"""
|
|
549
|
+
graph = rc_graph if inplace else deepcopy(rc_graph)
|
|
550
|
+
|
|
551
|
+
for node, data in graph.nodes(data=True):
|
|
552
|
+
hcount = data.get("hcount")
|
|
553
|
+
if (
|
|
554
|
+
isinstance(hcount, (list, tuple))
|
|
555
|
+
and len(hcount) == 2
|
|
556
|
+
and all(isinstance(value, int) for value in hcount)
|
|
557
|
+
):
|
|
558
|
+
data["hcount"] = _normalize_h_pair(hcount[0], hcount[1])
|
|
559
|
+
|
|
560
|
+
typesgh = data.get("typesGH")
|
|
561
|
+
if typesgh is None:
|
|
562
|
+
continue
|
|
563
|
+
|
|
564
|
+
if len(typesgh) != 2:
|
|
565
|
+
raise ValueError(
|
|
566
|
+
f"Node {node} has invalid typesGH structure: expected length 2, "
|
|
567
|
+
f"got {len(typesgh)}."
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
react_attr, prod_attr = typesgh
|
|
571
|
+
|
|
572
|
+
if len(react_attr) < 3 or len(prod_attr) < 3:
|
|
573
|
+
raise ValueError(
|
|
574
|
+
f"Node {node} has invalid typesGH atom tuples: "
|
|
575
|
+
f"{react_attr}, {prod_attr}"
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
h_react = react_attr[2]
|
|
579
|
+
h_prod = prod_attr[2]
|
|
580
|
+
|
|
581
|
+
new_h_react, new_h_prod = _normalize_h_pair(h_react, h_prod)
|
|
582
|
+
|
|
583
|
+
new_react_attr = list(react_attr)
|
|
584
|
+
new_prod_attr = list(prod_attr)
|
|
585
|
+
|
|
586
|
+
new_react_attr[2] = new_h_react
|
|
587
|
+
new_prod_attr[2] = new_h_prod
|
|
588
|
+
|
|
589
|
+
data["typesGH"] = (tuple(new_react_attr), tuple(new_prod_attr))
|
|
590
|
+
|
|
591
|
+
return graph
|