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,186 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
from typing import List, Dict, Any, Set, Optional
|
|
3
|
+
from rdkit import Chem
|
|
4
|
+
from rdkit.Chem import AllChem
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GraphToSMARTS:
|
|
8
|
+
"""Convert NetworkX graphs (with placeholder nodes/constraints) into SMARTS or reaction SMARTS strings.
|
|
9
|
+
|
|
10
|
+
:param placeholder_labels: Set of labels recognized as placeholders (e.g., '_R', 'X', 'Y', 'Z').
|
|
11
|
+
:type placeholder_labels: Optional[Set[str]]
|
|
12
|
+
:param validate: If True, validate generated SMARTS or reaction SMARTS using RDKit.
|
|
13
|
+
:type validate: bool
|
|
14
|
+
|
|
15
|
+
.. rubric:: Examples
|
|
16
|
+
|
|
17
|
+
>>> G = nx.Graph()
|
|
18
|
+
>>> G.add_node(1, label='C', constraint=None)
|
|
19
|
+
>>> G.add_node(2, label='O', constraint=None)
|
|
20
|
+
>>> G.add_edge(1, 2, order=1)
|
|
21
|
+
>>> g2s = GraphToSMARTS()
|
|
22
|
+
>>> smarts = g2s.graph_to_smarts(G)
|
|
23
|
+
>>> isinstance(smarts, str)
|
|
24
|
+
True
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, placeholder_labels: Optional[Set[str]] = None, validate: bool = True
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize the GraphToSMARTS converter.
|
|
31
|
+
|
|
32
|
+
:param placeholder_labels: Labels to treat as wildcard placeholders; defaults to {'_R','X','Y','Z'}.
|
|
33
|
+
:type placeholder_labels: Optional[Set[str]]
|
|
34
|
+
:param validate: Whether to validate SMARTS with RDKit if available.
|
|
35
|
+
:type validate: bool
|
|
36
|
+
:return: None
|
|
37
|
+
:rtype: None
|
|
38
|
+
"""
|
|
39
|
+
if placeholder_labels is None:
|
|
40
|
+
placeholder_labels = {"_R", "X", "Y", "Z"}
|
|
41
|
+
self.placeholder_labels: Set[str] = placeholder_labels
|
|
42
|
+
self.validate: bool = validate
|
|
43
|
+
|
|
44
|
+
def graph_to_smarts(self, G: nx.Graph) -> str:
|
|
45
|
+
"""Convert a NetworkX graph into a SMARTS string representation.
|
|
46
|
+
|
|
47
|
+
:param G: NetworkX Graph with node attributes:
|
|
48
|
+
- 'label': str atomic label or placeholder
|
|
49
|
+
- 'constraint': Optional[List[str]] allowed element list for placeholders
|
|
50
|
+
and edge attribute:
|
|
51
|
+
- 'order': float bond order (1,1.5,2,3)
|
|
52
|
+
:type G: nx.Graph
|
|
53
|
+
:return: SMARTS string encoding the graph structure.
|
|
54
|
+
:rtype: str
|
|
55
|
+
:raises ValueError: If RDKit fails to parse the generated SMARTS when validate=True.
|
|
56
|
+
|
|
57
|
+
.. rubric:: Examples
|
|
58
|
+
|
|
59
|
+
>>> G = nx.Graph()
|
|
60
|
+
>>> G.add_node(1, label='C', constraint=None)
|
|
61
|
+
>>> G.add_node(2, label='O', constraint=None)
|
|
62
|
+
>>> G.add_edge(1, 2, order=1)
|
|
63
|
+
>>> smarts = GraphToSMARTS().graph_to_smarts(G)
|
|
64
|
+
>>> smarts
|
|
65
|
+
'[C:1](-[O:2])'
|
|
66
|
+
"""
|
|
67
|
+
bond_sym: Dict[float, str] = {1: "-", 1.5: ":", 2: "=", 3: "#"}
|
|
68
|
+
|
|
69
|
+
def bracket(node: Any, data: Dict[str, Any]) -> str:
|
|
70
|
+
map_num = node
|
|
71
|
+
if data.get("constraint"):
|
|
72
|
+
core = ",".join(data["constraint"])
|
|
73
|
+
else:
|
|
74
|
+
core = data["label"]
|
|
75
|
+
return f"[{core}:{map_num}]"
|
|
76
|
+
|
|
77
|
+
def choose_root(sub: nx.Graph) -> Any:
|
|
78
|
+
real_nodes = [
|
|
79
|
+
n
|
|
80
|
+
for n, d in sub.nodes(data=True)
|
|
81
|
+
if d.get("label") not in self.placeholder_labels
|
|
82
|
+
]
|
|
83
|
+
if real_nodes:
|
|
84
|
+
return max(real_nodes, key=sub.degree)
|
|
85
|
+
return min(sub.nodes)
|
|
86
|
+
|
|
87
|
+
def rec(node: Any, parent: Any, sub: nx.Graph, visited: Set[Any]) -> str:
|
|
88
|
+
visited.add(node)
|
|
89
|
+
s = bracket(node, sub.nodes[node])
|
|
90
|
+
for nbr in sub.neighbors(node):
|
|
91
|
+
if nbr == parent:
|
|
92
|
+
continue
|
|
93
|
+
order = sub[node][nbr].get("order", 1)
|
|
94
|
+
bond = bond_sym.get(order, "-")
|
|
95
|
+
s += f"({bond}{rec(nbr, node, sub, visited)})"
|
|
96
|
+
return s
|
|
97
|
+
|
|
98
|
+
frags: List[str] = []
|
|
99
|
+
for comp in nx.connected_components(G):
|
|
100
|
+
subgraph = G.subgraph(comp)
|
|
101
|
+
root = choose_root(subgraph)
|
|
102
|
+
frags.append(rec(root, None, subgraph, set()))
|
|
103
|
+
|
|
104
|
+
smarts: str = ".".join(frags)
|
|
105
|
+
|
|
106
|
+
if self.validate:
|
|
107
|
+
try:
|
|
108
|
+
if not Chem.MolFromSmarts(smarts):
|
|
109
|
+
raise ValueError("RDKit could not parse generated SMARTS.")
|
|
110
|
+
except ImportError:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
return smarts
|
|
114
|
+
|
|
115
|
+
def graphs_to_rxn_smarts(self, reactant: nx.Graph, product: nx.Graph) -> str:
|
|
116
|
+
"""Construct a reaction SMARTS string from reactant and product graphs.
|
|
117
|
+
|
|
118
|
+
:param reactant: Reactant NetworkX graph.
|
|
119
|
+
:type reactant: nx.Graph
|
|
120
|
+
:param product: Product NetworkX graph.
|
|
121
|
+
:type product: nx.Graph
|
|
122
|
+
:return: Reaction SMARTS in the form 'reactants>>products'.
|
|
123
|
+
:rtype: str
|
|
124
|
+
:raises ValueError: If RDKit fails to parse the reaction SMARTS when validate=True.
|
|
125
|
+
|
|
126
|
+
.. rubric:: Examples
|
|
127
|
+
|
|
128
|
+
>>> G1 = nx.Graph()
|
|
129
|
+
>>> G1.add_node(1, label='C', constraint=None)
|
|
130
|
+
>>> G1.add_node(2, label='O', constraint=None)
|
|
131
|
+
>>> G1.add_edge(1, 2, order=1)
|
|
132
|
+
>>> G2 = nx.Graph()
|
|
133
|
+
>>> G2.add_node(1, label='C', constraint=None)
|
|
134
|
+
>>> G2.add_node(2, label='O', constraint=None)
|
|
135
|
+
>>> G2.add_edge(1, 2, order=2)
|
|
136
|
+
>>> rxn = GraphToSMARTS().graphs_to_rxn_smarts(G1, G2)
|
|
137
|
+
>>> rxn
|
|
138
|
+
'[C:1](-[O:2])>>[C:1]([O:2])='
|
|
139
|
+
"""
|
|
140
|
+
sm_r: str = self.graph_to_smarts(reactant)
|
|
141
|
+
sm_p: str = self.graph_to_smarts(product)
|
|
142
|
+
rxn: str = f"{sm_r}>>{sm_p}"
|
|
143
|
+
|
|
144
|
+
if self.validate:
|
|
145
|
+
try:
|
|
146
|
+
if not AllChem.ReactionFromSmarts(rxn):
|
|
147
|
+
raise ValueError("RDKit could not parse generated reaction SMARTS.")
|
|
148
|
+
except ImportError:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
return rxn
|
|
152
|
+
|
|
153
|
+
def __repr__(self) -> str:
|
|
154
|
+
"""Return an unambiguous representation of the converter instance.
|
|
155
|
+
|
|
156
|
+
:return: String showing placeholder labels and validation setting.
|
|
157
|
+
:rtype: str
|
|
158
|
+
"""
|
|
159
|
+
return (
|
|
160
|
+
f"<GraphToSMARTS placeholder_labels={self.placeholder_labels}, "
|
|
161
|
+
f"validate={self.validate}>"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def help(self) -> str:
|
|
165
|
+
"""Provide usage information for GraphToSMARTS.
|
|
166
|
+
|
|
167
|
+
:return: Multi-line help string describing available methods.
|
|
168
|
+
:rtype: str
|
|
169
|
+
|
|
170
|
+
.. rubric:: Examples
|
|
171
|
+
|
|
172
|
+
>>> print(GraphToSMARTS().help()) # doctest:+NORMALIZE_WHITESPACE
|
|
173
|
+
GraphToSMARTS(placeholder_labels=None, validate=True)
|
|
174
|
+
- Use .graph_to_smarts(G) for a single graph
|
|
175
|
+
- Use .graphs_to_rxn_smarts(G_react, G_prod) for reaction SMARTS
|
|
176
|
+
"""
|
|
177
|
+
return (
|
|
178
|
+
"GraphToSMARTS(placeholder_labels=None, validate=True)\n"
|
|
179
|
+
" - Use .graph_to_smarts(G) for a single graph\n"
|
|
180
|
+
" - Use .graphs_to_rxn_smarts(G_react, G_prod) for reaction SMARTS\n"
|
|
181
|
+
"Node attributes expected:\n"
|
|
182
|
+
" label: str (e.g., 'C', '_R', 'H+')\n"
|
|
183
|
+
" constraint: Optional[List[str]] for placeholders\n"
|
|
184
|
+
"Edge attribute:\n"
|
|
185
|
+
" order: float (1, 1.5, 2, 3) mapped to '-', ':', '=', '#'\n"
|
|
186
|
+
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import itertools
|
|
3
|
+
from typing import List, Dict, Tuple, Iterator, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SMARTSExpander:
|
|
7
|
+
"""Efficiently enumerate all valid reaction SMARTS by expanding atom-list
|
|
8
|
+
placeholders like [C,N,O,P,S:9], ensuring that each atom-map uses the same
|
|
9
|
+
element everywhere it appears (on both sides of a reaction).
|
|
10
|
+
|
|
11
|
+
:param smarts: SMARTS string, possibly containing one or more atom-list placeholders.
|
|
12
|
+
:type smarts: str
|
|
13
|
+
:return: Expanded SMARTS strings without atom-list placeholders.
|
|
14
|
+
:rtype: List[str]
|
|
15
|
+
:raises ValueError: If no valid expansions exist due to incompatible element lists.
|
|
16
|
+
|
|
17
|
+
Example usage::
|
|
18
|
+
|
|
19
|
+
>>> rxn = (
|
|
20
|
+
... '[H+:6].[C:7](-[O:8](-[H:12]))(-[C,N,O,P,S:9])'
|
|
21
|
+
... '(-[C,N,O,P,S:10])(-[H:11]).'
|
|
22
|
+
... '[C:2](-[S:4](-[C,N,O,P,S:5]))(-[C,N,O,P,S:1])(=[O:3])>>'
|
|
23
|
+
... '[S:4](-[H:6])(-[C,N,O,P,S:5]).[H+:12].'
|
|
24
|
+
... '[C:7](-[O:8](-[C:2](-[C,N,O,P,S:1])(=[O:3])))(-[C,N,O,P,S:9])'
|
|
25
|
+
... '(-[C,N,O,P,S:10])(-[H:11])'
|
|
26
|
+
... )
|
|
27
|
+
>>> ex = SMARTSExpander.expand(rxn)
|
|
28
|
+
>>> len(ex)
|
|
29
|
+
625
|
|
30
|
+
>>> ex[:3] # first three expansions
|
|
31
|
+
['[H+:6].[C:7](-[O:8](-[H:12]))(-[C:9])(-[C:10])(-[H:11]).[C:2]...'
|
|
32
|
+
'>>',
|
|
33
|
+
'...',
|
|
34
|
+
'...']
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_PAT = re.compile(r"\[([A-Z][a-z]?(?:,[A-Z][a-z]?)*)(:[0-9]+)\]")
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _extract_map_to_elements(matches: List[re.Match]) -> Dict[str, List[str]]:
|
|
41
|
+
"""Build a mapping from atom-map to the intersection of allowed elements.
|
|
42
|
+
|
|
43
|
+
:param matches: List of regex match objects for placeholders.
|
|
44
|
+
:type matches: List[re.Match]
|
|
45
|
+
:return: Dictionary mapping ":map" to sorted list of shared elements.
|
|
46
|
+
:rtype: Dict[str, List[str]]
|
|
47
|
+
"""
|
|
48
|
+
amap2set: Dict[str, set] = {}
|
|
49
|
+
for m in matches:
|
|
50
|
+
elems = set(m.group(1).split(","))
|
|
51
|
+
amap = m.group(2)
|
|
52
|
+
if amap not in amap2set:
|
|
53
|
+
amap2set[amap] = elems
|
|
54
|
+
else:
|
|
55
|
+
amap2set[amap] &= elems
|
|
56
|
+
for amap, s in amap2set.items():
|
|
57
|
+
if not s:
|
|
58
|
+
raise ValueError(f"No overlapping elements for atom-map {amap}")
|
|
59
|
+
return {amap: sorted(s) for amap, s in amap2set.items()}
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _build_template(
|
|
63
|
+
smarts: str, matches: List[re.Match]
|
|
64
|
+
) -> Tuple[List[Union[str, str]], List[str]]:
|
|
65
|
+
"""Build a list of string segments and placeholders for reconstruction.
|
|
66
|
+
|
|
67
|
+
:param smarts: Original SMARTS string.
|
|
68
|
+
:type smarts: str
|
|
69
|
+
:param matches: Regex matches for placeholders.
|
|
70
|
+
:type matches: List[re.Match]
|
|
71
|
+
:return: Tuple of list of segments and placeholder order.
|
|
72
|
+
:rtype: Tuple[List[Union[str, str]], List[str]]
|
|
73
|
+
"""
|
|
74
|
+
segments: List[Union[str, str]] = []
|
|
75
|
+
placeholder_order: List[str] = []
|
|
76
|
+
last = 0
|
|
77
|
+
for m in matches:
|
|
78
|
+
# fmt: off
|
|
79
|
+
segments.append(smarts[last: m.start()])
|
|
80
|
+
# fmt: on
|
|
81
|
+
amap = m.group(2)
|
|
82
|
+
segments.append(amap)
|
|
83
|
+
placeholder_order.append(amap)
|
|
84
|
+
last = m.end()
|
|
85
|
+
segments.append(smarts[last:])
|
|
86
|
+
return segments, placeholder_order
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def expand_iter(cls, smarts: str) -> Iterator[str]:
|
|
90
|
+
"""Yield expanded SMARTS strings lazily.
|
|
91
|
+
|
|
92
|
+
:param smarts: SMARTS string with placeholders.
|
|
93
|
+
:type smarts: str
|
|
94
|
+
:yield: One expanded SMARTS string at a time.
|
|
95
|
+
:rtype: Iterator[str]
|
|
96
|
+
|
|
97
|
+
:raises ValueError: If no valid expansions due to incompatible lists.
|
|
98
|
+
"""
|
|
99
|
+
matches = list(cls._PAT.finditer(smarts))
|
|
100
|
+
if not matches:
|
|
101
|
+
yield smarts
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
amap2els = cls._extract_map_to_elements(matches)
|
|
105
|
+
segments, order = cls._build_template(smarts, matches)
|
|
106
|
+
unique_maps = list(dict.fromkeys(order))
|
|
107
|
+
|
|
108
|
+
pools = [amap2els[am] for am in unique_maps]
|
|
109
|
+
for combo in itertools.product(*pools):
|
|
110
|
+
mapping = dict(zip(unique_maps, combo))
|
|
111
|
+
out = []
|
|
112
|
+
for seg in segments:
|
|
113
|
+
if seg in mapping:
|
|
114
|
+
out.append(f"[{mapping[seg]}{seg}]")
|
|
115
|
+
else:
|
|
116
|
+
out.append(seg)
|
|
117
|
+
yield "".join(out)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def expand(cls, smarts: str) -> List[str]:
|
|
121
|
+
"""Return a list of all expanded SMARTS.
|
|
122
|
+
|
|
123
|
+
:param smarts: SMARTS string with placeholders.
|
|
124
|
+
:type smarts: str
|
|
125
|
+
:return: List of expanded SMARTS strings.
|
|
126
|
+
:rtype: List[str]
|
|
127
|
+
|
|
128
|
+
:raises ValueError: If no valid expansions exist.
|
|
129
|
+
"""
|
|
130
|
+
return list(cls.expand_iter(smarts))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# # --- Example usage ---
|
|
134
|
+
|
|
135
|
+
# if __name__ == "__main__":
|
|
136
|
+
# rxn = (
|
|
137
|
+
# '[H+:6].[C:7](-[O:8](-[H:12]))(-[C,N,O,P,S:9])(-[C,N,O,P,S:10])(-[H:11]).'
|
|
138
|
+
# '[C:2](-[S:4](-[C,N,O,P,S:5]))(-[C,N,O,P,S:1])(=[O:3])>>'
|
|
139
|
+
# '[S:4](-[H:6])(-[C,N,O,P,S:5]).[H+:12].'
|
|
140
|
+
# '[C:7](-[O:8](-[C:2](-[C,N,O,P,S:1])(=[O:3])))(-[C,N,O,P,S:9])(-[C,N,O,P,S:10])(-[H:11])'
|
|
141
|
+
# )
|
|
142
|
+
# n = 0
|
|
143
|
+
# for i, s in enumerate(SMARTSExpander.expand_iter(rxn)):
|
|
144
|
+
# if i < 3 or i > 621:
|
|
145
|
+
# print(f"{i+1}: {s}")
|
|
146
|
+
# n += 1
|
|
147
|
+
# print(f"Total: {n} enumerated SMARTS.")
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List, Set
|
|
3
|
+
from rdkit import Chem
|
|
4
|
+
from rdkit.Chem import rdChemReactions
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SMARTSGeneralizer:
|
|
8
|
+
"""Generalizes a list of atom-mapped (reaction) SMARTS into one combinatorial SMARTS
|
|
9
|
+
with element-list placeholders at mapped atom positions.
|
|
10
|
+
Optionally validates output using RDKit.
|
|
11
|
+
|
|
12
|
+
:param sanity_check: If True, validate the output SMARTS with RDKit.
|
|
13
|
+
:type sanity_check: bool
|
|
14
|
+
|
|
15
|
+
.. rubric:: Example
|
|
16
|
+
|
|
17
|
+
>>> input_smarts = [
|
|
18
|
+
... '[C:1]-[N:2]>>[N:1]-[C:2]',
|
|
19
|
+
... '[N:1]-[N:2]>>[N:1]-[N:2]',
|
|
20
|
+
... '[O:1]-[N:2]>>[N:1]-[N:2]'
|
|
21
|
+
... ]
|
|
22
|
+
>>> gen = SMARTSGeneralizer()
|
|
23
|
+
>>> print(gen.generalize(input_smarts))
|
|
24
|
+
[C,N,O:1]-[N:2]>>[N:1]-[C,N,O:2]
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_atom_pat = re.compile(r"\[([A-Z][a-z]?):(\d+)\]")
|
|
28
|
+
|
|
29
|
+
def __init__(self, sanity_check: bool = True):
|
|
30
|
+
"""
|
|
31
|
+
Initialize SMARTSGeneralizer.
|
|
32
|
+
|
|
33
|
+
:param sanity_check: If True, validate the output SMARTS with RDKit.
|
|
34
|
+
:type sanity_check: bool
|
|
35
|
+
"""
|
|
36
|
+
self.sanity_check = sanity_check
|
|
37
|
+
|
|
38
|
+
def generalize(self, smarts_list: List[str]) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Generalize a list of SMARTS/reaction SMARTS into one combinatorial SMARTS,
|
|
41
|
+
with element-list placeholders per atom-map index and position.
|
|
42
|
+
|
|
43
|
+
:param smarts_list: List of atom-mapped SMARTS strings (same topology/order).
|
|
44
|
+
:type smarts_list: list[str]
|
|
45
|
+
:return: Generalized SMARTS with atom-list placeholders.
|
|
46
|
+
:rtype: str
|
|
47
|
+
:raises ValueError: If input list is empty, topology is inconsistent, or output is invalid.
|
|
48
|
+
"""
|
|
49
|
+
if not smarts_list:
|
|
50
|
+
raise ValueError("Input list is empty.")
|
|
51
|
+
|
|
52
|
+
if len(smarts_list) == 1:
|
|
53
|
+
combined = smarts_list[0]
|
|
54
|
+
else:
|
|
55
|
+
pos_list: List[List[re.Match]] = [
|
|
56
|
+
list(self._atom_pat.finditer(s)) for s in smarts_list
|
|
57
|
+
]
|
|
58
|
+
n_atoms = len(pos_list[0])
|
|
59
|
+
for pl in pos_list:
|
|
60
|
+
if len(pl) != n_atoms:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
"All input SMARTS must have same atom-mapped topology and order."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
pos2map: List[str] = []
|
|
66
|
+
pos2elems: List[Set[str]] = []
|
|
67
|
+
for i in range(n_atoms):
|
|
68
|
+
mapnum = pos_list[0][i].group(2)
|
|
69
|
+
elems = set(match[i].group(1) for match in pos_list)
|
|
70
|
+
pos2map.append(mapnum)
|
|
71
|
+
pos2elems.append(elems)
|
|
72
|
+
|
|
73
|
+
# Template assembly: alternate static and atom-map segments
|
|
74
|
+
first = smarts_list[0]
|
|
75
|
+
atoms = list(self._atom_pat.finditer(first))
|
|
76
|
+
segments = []
|
|
77
|
+
last = 0
|
|
78
|
+
for m in atoms:
|
|
79
|
+
# fmt: off
|
|
80
|
+
segments.append(first[last: m.start()])
|
|
81
|
+
# fmt: on
|
|
82
|
+
segments.append(m.group(2)) # mapnum as marker
|
|
83
|
+
last = m.end()
|
|
84
|
+
segments.append(first[last:])
|
|
85
|
+
|
|
86
|
+
# Reconstruct combinatorial SMARTS
|
|
87
|
+
out = []
|
|
88
|
+
idx = 0
|
|
89
|
+
for seg in segments:
|
|
90
|
+
if idx < len(pos2map) and seg == pos2map[idx]:
|
|
91
|
+
els = sorted(pos2elems[idx])
|
|
92
|
+
out.append(f"[{','.join(els)}:{seg}]")
|
|
93
|
+
idx += 1
|
|
94
|
+
else:
|
|
95
|
+
out.append(seg)
|
|
96
|
+
combined = "".join(out)
|
|
97
|
+
|
|
98
|
+
# RDKit validation
|
|
99
|
+
if self.sanity_check:
|
|
100
|
+
if ">>" in combined:
|
|
101
|
+
rxn = rdChemReactions.ReactionFromSmarts(combined)
|
|
102
|
+
if rxn is None or rxn.GetNumProductTemplates() == 0:
|
|
103
|
+
raise ValueError(f"Invalid reaction SMARTS generated: {combined}")
|
|
104
|
+
else:
|
|
105
|
+
mol = Chem.MolFromSmarts(combined)
|
|
106
|
+
if mol is None:
|
|
107
|
+
raise ValueError(f"Invalid molecule SMARTS generated: {combined}")
|
|
108
|
+
|
|
109
|
+
return combined
|
|
110
|
+
|
|
111
|
+
def describe(self) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Print usage instructions and an example.
|
|
114
|
+
|
|
115
|
+
:return: None
|
|
116
|
+
"""
|
|
117
|
+
print(
|
|
118
|
+
"SMARTSGeneralizer: Generalize a list of atom-mapped (reaction) SMARTS into a single "
|
|
119
|
+
"SMARTS with element-list placeholders at each mapped atom position.\n"
|
|
120
|
+
"Usage example:\n"
|
|
121
|
+
" >>> gen = SMARTSGeneralizer(sanity_check=True)\n"
|
|
122
|
+
" >>> smarts_list = ['[C:1]-[N:2]', '[O:1]-[N:2]', '[N:1]-[N:2]']\n"
|
|
123
|
+
" >>> print(gen.generalize(smarts_list)) # [C,N,O:1]-[N:2]"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def __repr__(self) -> str:
|
|
127
|
+
"""
|
|
128
|
+
String representation.
|
|
129
|
+
|
|
130
|
+
:return: Description with current sanity_check setting.
|
|
131
|
+
:rtype: str
|
|
132
|
+
"""
|
|
133
|
+
return f"SMARTSGeneralizer(sanity_check={self.sanity_check})"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import networkx as nx
|
|
3
|
+
from typing import Optional, Set, Tuple, List, Dict
|
|
4
|
+
|
|
5
|
+
from rdkit import Chem
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SMARTSToGraph:
|
|
9
|
+
"""Convert SMARTS or reaction SMARTS strings into NetworkX graphs with full atom and constraint data.
|
|
10
|
+
|
|
11
|
+
:param placeholder_labels: Optional set of labels to treat as placeholders (e.g., wildcard atoms).
|
|
12
|
+
:type placeholder_labels: Optional[Set[str]]
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, placeholder_labels: Optional[Set[str]] = None) -> None:
|
|
16
|
+
"""Initialize a SMARTSToGraph converter.
|
|
17
|
+
|
|
18
|
+
:param placeholder_labels: Set of placeholder labels used in SMARTS to identify wildcard positions.
|
|
19
|
+
Defaults to {'_R', 'X', 'Y', 'Z'} if None.
|
|
20
|
+
:type placeholder_labels: Optional[Set[str]]
|
|
21
|
+
:return: None
|
|
22
|
+
:rtype: None
|
|
23
|
+
"""
|
|
24
|
+
self.placeholder_labels: Set[str] = placeholder_labels or {"_R", "X", "Y", "Z"}
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _safe_total_hs(atom: "Chem.Atom") -> int:
|
|
28
|
+
"""Compute the total number of hydrogens (explicit + implicit) for an RDKit Atom safely.
|
|
29
|
+
|
|
30
|
+
:param atom: RDKit Atom instance whose hydrogen count is desired.
|
|
31
|
+
:type atom: Chem.Atom
|
|
32
|
+
:return: Total hydrogen count for the atom.
|
|
33
|
+
:rtype: int
|
|
34
|
+
:raises Exception: If RDKit property cache update fails.
|
|
35
|
+
|
|
36
|
+
.. rubric:: Examples
|
|
37
|
+
|
|
38
|
+
>>> from rdkit import Chem
|
|
39
|
+
>>> atom = Chem.MolFromSmiles('C').GetAtomWithIdx(0)
|
|
40
|
+
>>> SMARTSToGraph._safe_total_hs(atom)
|
|
41
|
+
3
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
atom.UpdatePropertyCache(strict=False)
|
|
45
|
+
return int(atom.GetTotalNumHs(includeExplicit=True))
|
|
46
|
+
except Exception:
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
def smarts_to_graph(self, smarts: str) -> nx.Graph:
|
|
50
|
+
"""Parse a SMARTS string into a NetworkX graph representation, extracting wildcard constraints.
|
|
51
|
+
|
|
52
|
+
:param smarts: SMARTS pattern to convert (e.g., '[C:1]C[O:2]').
|
|
53
|
+
:type smarts: str
|
|
54
|
+
:return: NetworkX Graph with:
|
|
55
|
+
- node attributes: element (str), charge (int), hcount (int),
|
|
56
|
+
label (str), constraint (Optional[List[str]]), atom_map (int)
|
|
57
|
+
- edge attributes: order (float), standard_order (float)
|
|
58
|
+
:rtype: nx.Graph
|
|
59
|
+
:raises ImportError: If RDKit is not available.
|
|
60
|
+
:raises ValueError: If the SMARTS string is invalid or atoms lack mapping numbers.
|
|
61
|
+
|
|
62
|
+
.. rubric:: Examples
|
|
63
|
+
|
|
64
|
+
>>> stg = SMARTSToGraph()
|
|
65
|
+
>>> graph = stg.smarts_to_graph('[CH3:1]-[OH:2]')
|
|
66
|
+
>>> graph.nodes[1]['element']
|
|
67
|
+
'C'
|
|
68
|
+
>>> graph.nodes[2]['element']
|
|
69
|
+
'O'
|
|
70
|
+
"""
|
|
71
|
+
if Chem is None:
|
|
72
|
+
raise ImportError("RDKit is required for SMARTS parsing.")
|
|
73
|
+
|
|
74
|
+
# Pre-scan SMARTS for wildcard constraint lists (e.g., [C,N:5])
|
|
75
|
+
constraint_map: Dict[int, List[str]] = {}
|
|
76
|
+
for match in re.finditer(r"\[([^:\]]+?):(\d+)\]", smarts):
|
|
77
|
+
atom_expr, atom_idx = match.groups()
|
|
78
|
+
idx = int(atom_idx)
|
|
79
|
+
if "," in atom_expr:
|
|
80
|
+
constraint_map[idx] = [s.strip() for s in atom_expr.split(",")]
|
|
81
|
+
|
|
82
|
+
mol = Chem.MolFromSmarts(smarts)
|
|
83
|
+
if mol is None:
|
|
84
|
+
raise ValueError(f"Invalid SMARTS string: {smarts!r}")
|
|
85
|
+
|
|
86
|
+
G = nx.Graph()
|
|
87
|
+
idx_to_map: Dict[int, int] = {}
|
|
88
|
+
|
|
89
|
+
# Add nodes with full atom data
|
|
90
|
+
for atom in mol.GetAtoms():
|
|
91
|
+
amap = atom.GetAtomMapNum()
|
|
92
|
+
if amap == 0:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"All atoms in SMARTS must have a mapping number (atom map)."
|
|
95
|
+
)
|
|
96
|
+
idx_to_map[atom.GetIdx()] = amap
|
|
97
|
+
|
|
98
|
+
# Determine element, label, and constraints
|
|
99
|
+
raw_label = atom.GetSymbol()
|
|
100
|
+
if amap in constraint_map:
|
|
101
|
+
constraint = constraint_map[amap]
|
|
102
|
+
label = next(iter(self.placeholder_labels))
|
|
103
|
+
element = "*"
|
|
104
|
+
else:
|
|
105
|
+
constraint = None
|
|
106
|
+
label = raw_label
|
|
107
|
+
element = "*" if raw_label in self.placeholder_labels else raw_label
|
|
108
|
+
|
|
109
|
+
charge = atom.GetFormalCharge()
|
|
110
|
+
hcount = self._safe_total_hs(atom)
|
|
111
|
+
|
|
112
|
+
G.add_node(
|
|
113
|
+
amap,
|
|
114
|
+
element=element,
|
|
115
|
+
charge=charge,
|
|
116
|
+
hcount=hcount,
|
|
117
|
+
label=label,
|
|
118
|
+
constraint=constraint,
|
|
119
|
+
atom_map=amap,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Add edges with bond order data
|
|
123
|
+
for bond in mol.GetBonds():
|
|
124
|
+
u = idx_to_map[bond.GetBeginAtomIdx()]
|
|
125
|
+
v = idx_to_map[bond.GetEndAtomIdx()]
|
|
126
|
+
order = bond.GetBondTypeAsDouble()
|
|
127
|
+
G.add_edge(u, v, order=order, standard_order=order)
|
|
128
|
+
|
|
129
|
+
return G
|
|
130
|
+
|
|
131
|
+
def rxn_smarts_to_graphs(self, rxn: str) -> Tuple[nx.Graph, nx.Graph]:
|
|
132
|
+
"""Split a reaction SMARTS into separate reactant and product graphs.
|
|
133
|
+
|
|
134
|
+
:param rxn: Reaction SMARTS in the format 'reactants>>products'.
|
|
135
|
+
:type rxn: str
|
|
136
|
+
:return: Tuple of (reactant_graph, product_graph).
|
|
137
|
+
:rtype: Tuple[nx.Graph, nx.Graph]
|
|
138
|
+
:raises ValueError: If the reaction SMARTS string does not contain '>>'.
|
|
139
|
+
|
|
140
|
+
.. rubric:: Examples
|
|
141
|
+
|
|
142
|
+
>>> stg = SMARTSToGraph()
|
|
143
|
+
>>> react, prod = stg.rxn_smarts_to_graphs('[CH3:1]-[OH:2]>>[CH2:1]=[O:2]')
|
|
144
|
+
>>> react.nodes
|
|
145
|
+
[1, 2]
|
|
146
|
+
>>> prod.nodes
|
|
147
|
+
[1, 2]
|
|
148
|
+
"""
|
|
149
|
+
if ">>" not in rxn:
|
|
150
|
+
raise ValueError("Reaction SMARTS must contain '>>' separator.")
|
|
151
|
+
lhs, rhs = rxn.split(">>", 1)
|
|
152
|
+
return self.smarts_to_graph(lhs), self.smarts_to_graph(rhs)
|
|
153
|
+
|
|
154
|
+
def __repr__(self) -> str:
|
|
155
|
+
"""Return an unambiguous string representation of this converter.
|
|
156
|
+
|
|
157
|
+
:return: Representation of the instance showing placeholder labels.
|
|
158
|
+
:rtype: str
|
|
159
|
+
"""
|
|
160
|
+
return f"<SMARTSToGraph placeholders={self.placeholder_labels}>"
|
|
161
|
+
|
|
162
|
+
def describe(self) -> str:
|
|
163
|
+
"""Provide a usage summary for SMARTSToGraph.
|
|
164
|
+
|
|
165
|
+
:return: Multi-line string explaining available methods and usage.
|
|
166
|
+
:rtype: str
|
|
167
|
+
|
|
168
|
+
.. rubric:: Examples
|
|
169
|
+
|
|
170
|
+
>>> print(SMARTSToGraph().describe())
|
|
171
|
+
SMARTSToGraph(placeholder_labels=None)
|
|
172
|
+
smarts_to_graph(smarts_str) -> Graph
|
|
173
|
+
rxn_smarts_to_graphs(rxn_smarts) -> (Graph_react, Graph_prod)
|
|
174
|
+
"""
|
|
175
|
+
return (
|
|
176
|
+
"SMARTSToGraph(placeholder_labels=None)\n"
|
|
177
|
+
" smarts_to_graph(smarts_str) -> Graph\n"
|
|
178
|
+
" rxn_smarts_to_graphs(rxn_smarts) -> (Graph_react, Graph_prod)\n"
|
|
179
|
+
)
|
synkit/IO/conversion.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""High-level conversion entry points.
|
|
2
|
+
|
|
3
|
+
This module provides I/O-facing access to conversions whose implementation
|
|
4
|
+
belongs to a domain-specific SynKit package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ef_smirks_to_epd(
|
|
13
|
+
ef_smirks: str,
|
|
14
|
+
orbital_class: Optional[str] = None,
|
|
15
|
+
strict_bond_lookup: bool = True,
|
|
16
|
+
) -> dict[str, Any]:
|
|
17
|
+
"""Complete AAM and convert EF-SMIRKS text into generic and typed EPD.
|
|
18
|
+
|
|
19
|
+
See :func:`synkit.Graph.Mech.conversion.ef_smirks_to_epd` for the
|
|
20
|
+
EF-SMIRKS input format and returned fields.
|
|
21
|
+
"""
|
|
22
|
+
from synkit.Graph.Mech.conversion import ef_smirks_to_epd as _convert
|
|
23
|
+
|
|
24
|
+
return _convert(
|
|
25
|
+
ef_smirks,
|
|
26
|
+
orbital_class=orbital_class,
|
|
27
|
+
strict_bond_lookup=strict_bond_lookup,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def epd_to_ef_smirks(complete_aam: str, epd: list[list[Any]]) -> str:
|
|
32
|
+
"""Convert complete atom-mapped RSMI and EPD records into EF-SMIRKS.
|
|
33
|
+
|
|
34
|
+
See :func:`synkit.Graph.Mech.conversion.epd_to_ef_smirks` for accepted
|
|
35
|
+
generic and typed EPD records.
|
|
36
|
+
"""
|
|
37
|
+
from synkit.Graph.Mech.conversion import epd_to_ef_smirks as _convert
|
|
38
|
+
|
|
39
|
+
return _convert(complete_aam, epd)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = ["ef_smirks_to_epd", "epd_to_ef_smirks"]
|