synkit 1.3.1a0__tar.gz → 1.3.2b1__tar.gz
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-1.3.1a0 → synkit-1.3.2b1}/.gitignore +10 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/PKG-INFO +1 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/pyproject.toml +1 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_parse.py +1 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/atom_features.py +0 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/standardize.py +56 -8
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/tautomerize.py +50 -7
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/canon_graph.py +2 -2
- synkit-1.3.2b1/synkit/Graph/FG/__init__.py +19 -0
- synkit-1.3.2b1/synkit/Graph/FG/api.py +39 -0
- synkit-1.3.2b1/synkit/Graph/FG/audit.py +155 -0
- synkit-1.3.2b1/synkit/Graph/FG/catalog.py +1168 -0
- synkit-1.3.2b1/synkit/Graph/FG/detector.py +288 -0
- synkit-1.3.2b1/synkit/Graph/FG/model.py +97 -0
- synkit-1.3.2b1/synkit/Graph/FG/ring_system.py +154 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/_misc.py +78 -7
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_construction.py +14 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_destruction.py +25 -3
- synkit-1.3.2b1/synkit/Graph/ITS/its_expand.py +444 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_reverter.py +7 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/rc_extractor.py +24 -2
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/mtg.py +377 -39
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/graph_matcher.py +34 -15
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/subgraph_matcher.py +127 -11
- synkit-1.3.2b1/synkit/Graph/Mech/conversion.py +1305 -0
- synkit-1.3.2b1/synkit/Graph/Mech/electron_accounting.py +64 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/canon_graph.py +12 -3
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/utils.py +31 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/chem_converter.py +85 -10
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/graph_to_mol.py +2 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/mol_to_graph.py +88 -38
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/rule_matcher.py +26 -3
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/syn_rule.py +135 -28
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/imba_engine.py +10 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/partial_engine.py +15 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/rbl_engine.py +22 -1
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/rule_filter.py +11 -1
- synkit-1.3.2b1/synkit/Synthesis/Reactor/syn_reactor.py +1429 -0
- synkit-1.3.2b1/synkit/Utils/__init__.py +0 -0
- synkit-1.3.2b1/synkit/Vis/__init__.py +47 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/graph_visualizer.py +1 -1
- synkit-1.3.2b1/synkit/Vis/its_drawer.py +615 -0
- synkit-1.3.2b1/synkit/Vis/molecule_drawer.py +565 -0
- synkit-1.3.2b1/synkit/Vis/reaction_drawer.py +285 -0
- synkit-1.3.2b1/synkit/Vis/rxn_vis.py +532 -0
- synkit-1.3.2b1/synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
- synkit-1.3.2b1/synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
- synkit-1.3.2b1/synkit/Vis/vis_synedu/rxn_vis.py +382 -0
- synkit-1.3.2b1/synkit/Vis/vis_synedu/vis.py +501 -0
- synkit-1.3.2b1/synkit/Vis/visual_drawer.py +215 -0
- synkit-1.3.2b1/synkit/Vis/visual_model.py +520 -0
- synkit-1.3.1a0/synkit/Graph/ITS/its_expand.py +0 -86
- synkit-1.3.1a0/synkit/Graph/MTG/group_comp.py +0 -157
- synkit-1.3.1a0/synkit/Graph/MTG/groupoid.py +0 -358
- synkit-1.3.1a0/synkit/Synthesis/Reactor/syn_reactor.py +0 -638
- synkit-1.3.1a0/synkit/Vis/__init__.py +0 -5
- synkit-1.3.1a0/synkit/Vis/rxn_vis.py +0 -159
- {synkit-1.3.1a0 → synkit-1.3.2b1}/LICENSE +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/README.md +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/crn.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/mod_crn.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/DAG/syncrn.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/abstract.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/arity.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/builder.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/derivation.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/flattener.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/keys.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/mixtures.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/smiles.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/state.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/strategy.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Construct/worker.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/_adapter.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/pathfinder.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/reachability.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Pathway/realizability.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/analyzer.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/net.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/persistence.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/semiflows.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Petrinet/structure.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/dynamics.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/helper.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/stoich.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Props/thermo.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_api.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_extract.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Query/kegg_impute.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/reaction.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/rule.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/species.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Structure/syncrn.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/_common.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/_ir.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/automorphism.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/canon.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/isomorphism.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/symmetry.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Symmetry/wl_canon.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/crn_vis.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/labels.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/layout.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/palette.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/validation.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/Visualize/vis.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/backend.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/conversion.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/hyperedge.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/hypergraph.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Structure/rxn.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/automorphism.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/canon.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/Symmetry/wl_canon.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/configs/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/configs/loader.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/configs/models.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/constants.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/crn_formula.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/deficiency.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/enumerator.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/exceptions.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/explorer.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/helpers.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/injectivity.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/motif.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/network.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/pathway.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/properties.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/reaction.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/utils.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/CRN/dev_crn/viz.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Cluster/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Cluster/butina.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Fingerprint/transformation_fp.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/descriptors.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/formula.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/graph_annotator.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/standardize.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Molecule/valence.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/Mapper/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/Mapper/wl_mapper.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/aam_validator.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/balance_check.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/cleaning.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/deionize.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/fix_aam.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/neutralize.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/Reaction/radical_wildcard.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Chem/utils.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Data/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Data/gen_partial_aam.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/canon_algs.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Canon/nauty.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Context/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Context/hier_context.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Context/radius_expand.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/Descriptors/topology.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/graph_descriptors.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/graph_fps.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/graph_signature.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/hash_fps.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/morgan_fps.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/path_fps.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Feature/wl_hash.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Hyrogen/hextend.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_builder.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_decompose.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/its_relabel.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/normalize_aam.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/ITS/partial_its.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/mcs_matcher.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/mtg_explore.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/MTG/utils.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/approx_mcs.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/auto_est.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/automorphism.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/batch_cluster.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/dedup_matches.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/graph_cluster.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/graph_morphism.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/mcs_matcher.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/multi_turbo_iso.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/orbit.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/partial_matcher.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/sing.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/turbo_iso.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Matcher/wl_sel.py +0 -0
- {synkit-1.3.1a0/synkit/Graph/Wildcard → synkit-1.3.2b1/synkit/Graph/Mech}/__init__.py +0 -0
- {synkit-1.3.1a0/synkit/Rule/Apply → synkit-1.3.2b1/synkit/Graph/Wildcard}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/fuse_graph.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/graph_wc.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/its_merge.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/radwc.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/wc_matcher.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/Wildcard/wildcard.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Graph/syn_graph.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/gml_to_graph.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/graph_to_gml.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/graph_to_smarts.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/smarts_expander.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/smarts_generalizer.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/combinatorial/smarts_to_graph.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/data_io.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/data_process.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/debug.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/dg_to_gml.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/gml_to_nx.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/nx_to_gml.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/IO/smiles_to_id.py +0 -0
- {synkit-1.3.1a0/synkit/Rule/Compose → synkit-1.3.2b1/synkit/Rule/Apply}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/reactor_rule.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/retro_reactor.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Apply/rule_rbl.py +0 -0
- {synkit-1.3.1a0/synkit/Rule/Modify → synkit-1.3.2b1/synkit/Rule/Compose}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/compose_rule.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/rule_compose.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/rule_mapping.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/seq_comp.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Compose/valence_constrain.py +0 -0
- {synkit-1.3.1a0/synkit/Synthesis/MSR → synkit-1.3.2b1/synkit/Rule/Modify}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/implict_rule.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/longest_path.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/molecule_rule.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/prune_templates.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/rule_utils.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/Modify/strip_rule.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Rule/__init__.py +0 -0
- {synkit-1.3.1a0/synkit/Synthesis/Metrics → synkit-1.3.2b1/synkit/Synthesis/MSR}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/MSR/multi_steps.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/MSR/path_finder.py +0 -0
- {synkit-1.3.1a0/synkit/Synthesis/Reactor → synkit-1.3.2b1/synkit/Synthesis/Metrics}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Metrics/_base.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Metrics/_plot.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Metrics/_ranking.py +0 -0
- {synkit-1.3.1a0/synkit/Synthesis → synkit-1.3.2b1/synkit/Synthesis/Reactor}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/batch_reactor.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/benchmark.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/post_syn.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/Reactor/strategy.py +0 -0
- {synkit-1.3.1a0/synkit/Utils → synkit-1.3.2b1/synkit/Synthesis}/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Synthesis/reactor_utils.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Utils/utils.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/chemical_space.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/crn_vis.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/embedding.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/pdf_writer.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/Vis/rule_vis.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/__init__.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/examples.py +0 -0
- {synkit-1.3.1a0 → synkit-1.3.2b1}/synkit/version.py +0 -0
|
@@ -426,7 +426,7 @@ def parse_module_reaction_directions(
|
|
|
426
426
|
in_reaction = False
|
|
427
427
|
for line in text.splitlines():
|
|
428
428
|
if line.startswith("REACTION"):
|
|
429
|
-
payload = line[len("REACTION"):].strip()
|
|
429
|
+
payload = line[len("REACTION") :].strip() # noqa
|
|
430
430
|
in_reaction = True
|
|
431
431
|
elif in_reaction and (line.startswith(" ") or line.startswith("\t")):
|
|
432
432
|
payload = line.strip()
|
|
@@ -192,7 +192,6 @@ class AtomFeatureExtractor:
|
|
|
192
192
|
"partial_charge": round(float(gcharge), 3),
|
|
193
193
|
"hybridization": str(atom.GetHybridization()),
|
|
194
194
|
"in_ring": bool(atom.IsInRing()),
|
|
195
|
-
"implicit_hcount": int(atom.GetNumImplicitHs()),
|
|
196
195
|
"neighbors": neighbor_symbols,
|
|
197
196
|
"atom_map": atom_map,
|
|
198
197
|
}
|
|
@@ -72,7 +72,34 @@ class Standardize:
|
|
|
72
72
|
return valid
|
|
73
73
|
|
|
74
74
|
@staticmethod
|
|
75
|
-
def
|
|
75
|
+
def _parse_molecule_fragments(
|
|
76
|
+
smiles_list: List[str],
|
|
77
|
+
) -> Tuple[List[Chem.Mol], bool]:
|
|
78
|
+
"""Parse and sanitize SMILES fragments.
|
|
79
|
+
|
|
80
|
+
:param smiles_list: List of SMILES strings to validate.
|
|
81
|
+
:type smiles_list: List[str]
|
|
82
|
+
:returns: Tuple of valid molecules and whether any fragment was invalid.
|
|
83
|
+
:rtype: Tuple[List[rdkit.Chem.Mol], bool]
|
|
84
|
+
"""
|
|
85
|
+
valid: List[Chem.Mol] = []
|
|
86
|
+
had_invalid = False
|
|
87
|
+
for smi in smiles_list:
|
|
88
|
+
mol = Chem.MolFromSmiles(smi, sanitize=False)
|
|
89
|
+
if mol:
|
|
90
|
+
try:
|
|
91
|
+
Chem.SanitizeMol(mol)
|
|
92
|
+
valid.append(mol)
|
|
93
|
+
except Exception:
|
|
94
|
+
had_invalid = True
|
|
95
|
+
else:
|
|
96
|
+
had_invalid = True
|
|
97
|
+
return valid, had_invalid
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def standardize_rsmi(
|
|
101
|
+
rsmi: str, stereo: bool = False, remove_invalid: bool = True
|
|
102
|
+
) -> Optional[str]:
|
|
76
103
|
"""
|
|
77
104
|
Normalize a reaction SMILES: validate molecules, sort fragments, optionally keep stereo.
|
|
78
105
|
|
|
@@ -80,6 +107,10 @@ class Standardize:
|
|
|
80
107
|
:type rsmi: str
|
|
81
108
|
:param stereo: If True, include stereochemistry in the output. Defaults to False.
|
|
82
109
|
:type stereo: bool
|
|
110
|
+
:param remove_invalid: If True, drop invalid fragments and standardize
|
|
111
|
+
remaining molecules. If False, return None when any invalid fragment
|
|
112
|
+
exists. Defaults to True.
|
|
113
|
+
:type remove_invalid: bool
|
|
83
114
|
:returns: Standardized reaction SMILES or None if no valid molecules remain.
|
|
84
115
|
:rtype: Optional[str]
|
|
85
116
|
:raises ValueError: If the input format is invalid.
|
|
@@ -91,8 +122,15 @@ class Standardize:
|
|
|
91
122
|
"Invalid reaction SMILES format. Expected 'reactants>>products'."
|
|
92
123
|
)
|
|
93
124
|
|
|
94
|
-
react_mols = Standardize.
|
|
95
|
-
|
|
125
|
+
react_mols, react_invalid = Standardize._parse_molecule_fragments(
|
|
126
|
+
react_str.split(".")
|
|
127
|
+
)
|
|
128
|
+
prod_mols, prod_invalid = Standardize._parse_molecule_fragments(
|
|
129
|
+
prod_str.split(".")
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if not remove_invalid and (react_invalid or prod_invalid):
|
|
133
|
+
return None
|
|
96
134
|
|
|
97
135
|
if not react_mols or not prod_mols:
|
|
98
136
|
return None
|
|
@@ -107,7 +145,11 @@ class Standardize:
|
|
|
107
145
|
return f"{sorted_react}>>{sorted_prod}"
|
|
108
146
|
|
|
109
147
|
def fit(
|
|
110
|
-
self,
|
|
148
|
+
self,
|
|
149
|
+
rsmi: str,
|
|
150
|
+
remove_aam: bool = True,
|
|
151
|
+
ignore_stereo: bool = True,
|
|
152
|
+
remove_invalid: bool = True,
|
|
111
153
|
) -> Optional[str]:
|
|
112
154
|
"""
|
|
113
155
|
Full standardization pipeline: strip atom‑mapping, normalize SMILES, fix hydrogen notation.
|
|
@@ -118,16 +160,22 @@ class Standardize:
|
|
|
118
160
|
:type remove_aam: bool
|
|
119
161
|
:param ignore_stereo: If True, drop stereochemistry. Defaults to True.
|
|
120
162
|
:type ignore_stereo: bool
|
|
163
|
+
:param remove_invalid: If True, drop invalid fragments and standardize
|
|
164
|
+
remaining molecules. If False, return None when any invalid fragment
|
|
165
|
+
exists. Defaults to True.
|
|
166
|
+
:type remove_invalid: bool
|
|
121
167
|
:returns: The standardized reaction SMILES, or None if standardization fails.
|
|
122
168
|
:rtype: Optional[str]
|
|
123
169
|
"""
|
|
124
|
-
|
|
125
|
-
rsmi =
|
|
126
|
-
|
|
127
|
-
std = self.standardize_rsmi(rsmi, stereo=not ignore_stereo)
|
|
170
|
+
std = self.standardize_rsmi(
|
|
171
|
+
rsmi, stereo=not ignore_stereo, remove_invalid=remove_invalid
|
|
172
|
+
)
|
|
128
173
|
if std is None:
|
|
129
174
|
return None
|
|
130
175
|
|
|
176
|
+
if remove_aam:
|
|
177
|
+
std = self.remove_atom_mapping(std)
|
|
178
|
+
|
|
131
179
|
# Format any double‑hydrogen notation
|
|
132
180
|
return std.replace("[HH]", "[H][H]")
|
|
133
181
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from typing import List, Dict, Optional
|
|
2
2
|
from rdkit import Chem
|
|
3
|
-
from fgutils import FGQuery
|
|
4
3
|
from joblib import Parallel, delayed
|
|
5
4
|
|
|
5
|
+
from synkit.Graph.FG import smiles_to_graph_and_functional_groups
|
|
6
|
+
|
|
6
7
|
|
|
7
8
|
class Tautomerize:
|
|
8
9
|
"""Standardize molecules by converting enol and hemiketal tautomers into
|
|
@@ -103,18 +104,60 @@ class Tautomerize:
|
|
|
103
104
|
:returns: Canonical SMILES of the standardized molecule.
|
|
104
105
|
:rtype: str
|
|
105
106
|
"""
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
while True:
|
|
108
|
+
targets = Tautomerize._tautomer_targets(smiles)
|
|
109
|
+
if not targets:
|
|
110
|
+
break
|
|
111
|
+
label, indices = targets[0]
|
|
110
112
|
if label == "hemiketal":
|
|
111
113
|
smiles = Tautomerize.standardize_hemiketal(smiles, indices)
|
|
112
|
-
fg = query.get(smiles)
|
|
113
114
|
elif label == "enol":
|
|
114
115
|
smiles = Tautomerize.standardize_enol(smiles, indices)
|
|
115
|
-
fg = query.get(smiles)
|
|
116
116
|
return Chem.CanonSmiles(smiles)
|
|
117
117
|
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _tautomer_targets(smiles: str) -> list[tuple[str, List[int]]]:
|
|
120
|
+
"""Return RDKit-index targets used by the tautomer repair helpers."""
|
|
121
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
122
|
+
if mol is None:
|
|
123
|
+
return []
|
|
124
|
+
graph, groups = smiles_to_graph_and_functional_groups(smiles)
|
|
125
|
+
node_to_idx = {
|
|
126
|
+
(
|
|
127
|
+
atom.GetAtomMapNum() if atom.GetAtomMapNum() else atom.GetIdx() + 1
|
|
128
|
+
): atom.GetIdx()
|
|
129
|
+
for atom in mol.GetAtoms()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
targets = [
|
|
133
|
+
(label, [node_to_idx[node] for node in nodes])
|
|
134
|
+
for label, nodes in groups
|
|
135
|
+
if label in {"hemiketal", "enol"}
|
|
136
|
+
]
|
|
137
|
+
targets.extend(
|
|
138
|
+
("hemiketal", [node_to_idx[node] for node in nodes])
|
|
139
|
+
for nodes in Tautomerize._geminal_diol_nodes(graph)
|
|
140
|
+
)
|
|
141
|
+
return targets
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _geminal_diol_nodes(graph) -> list[tuple[int, ...]]:
|
|
145
|
+
"""Legacy tautomerization compatibility for hydrated carbonyls."""
|
|
146
|
+
targets: list[tuple[int, ...]] = []
|
|
147
|
+
for carbon, data in graph.nodes(data=True):
|
|
148
|
+
if data.get("element") != "C":
|
|
149
|
+
continue
|
|
150
|
+
hydroxyls = [
|
|
151
|
+
neighbor
|
|
152
|
+
for neighbor in graph.neighbors(carbon)
|
|
153
|
+
if graph.nodes[neighbor].get("element") == "O"
|
|
154
|
+
and graph.nodes[neighbor].get("hcount", 0) >= 1
|
|
155
|
+
and graph.edges[carbon, neighbor].get("order") == 1.0
|
|
156
|
+
]
|
|
157
|
+
if len(hydroxyls) >= 2:
|
|
158
|
+
targets.append((carbon, hydroxyls[0], hydroxyls[1]))
|
|
159
|
+
return targets
|
|
160
|
+
|
|
118
161
|
@staticmethod
|
|
119
162
|
def fix_dict(data: Dict[str, str], reaction_column: str) -> Dict[str, str]:
|
|
120
163
|
"""Standardize the reactant and product SMILES in a reaction
|
|
@@ -316,8 +316,8 @@ class GraphCanonicaliser:
|
|
|
316
316
|
nodes = sorted(g.nodes(data=True), key=lambda x: self._node_key(*x))
|
|
317
317
|
edges = sorted(g.edges(data=True), key=lambda x: self._edge_key(*x))
|
|
318
318
|
|
|
319
|
-
node_str = ";".join(f"{n}:{self._node_key(n,d)}" for n, d in nodes)
|
|
320
|
-
edge_str = ";".join(f"{(u,v)}:{self._edge_key(u,v,d)}" for u, v, d in edges)
|
|
319
|
+
node_str = ";".join(f"{n}:{self._node_key(n, d)}" for n, d in nodes)
|
|
320
|
+
edge_str = ";".join(f"{(u, v)}:{self._edge_key(u, v, d)}" for u, v, d in edges)
|
|
321
321
|
return f"N[{node_str}]|E[{edge_str}]"
|
|
322
322
|
|
|
323
323
|
# ------------------------------------------------------------------ #
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Functional-group detection on SynKit molecular graphs."""
|
|
2
|
+
|
|
3
|
+
from .catalog import default_registry
|
|
4
|
+
from .audit import FunctionalGroupAudit, audit_reaction_smiles
|
|
5
|
+
from .api import FunctionalGroupLabels, smiles_to_graph_and_functional_groups
|
|
6
|
+
from .detector import FunctionalGroupDetector
|
|
7
|
+
from .model import FunctionalGroupMatch, FunctionalGroupPattern, FunctionalGroupRegistry
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"FunctionalGroupDetector",
|
|
11
|
+
"FunctionalGroupLabels",
|
|
12
|
+
"FunctionalGroupAudit",
|
|
13
|
+
"FunctionalGroupMatch",
|
|
14
|
+
"FunctionalGroupPattern",
|
|
15
|
+
"FunctionalGroupRegistry",
|
|
16
|
+
"default_registry",
|
|
17
|
+
"audit_reaction_smiles",
|
|
18
|
+
"smiles_to_graph_and_functional_groups",
|
|
19
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import networkx as nx
|
|
4
|
+
|
|
5
|
+
from synkit.IO.chem_converter import smiles_to_graph
|
|
6
|
+
|
|
7
|
+
from .detector import FunctionalGroupDetector
|
|
8
|
+
|
|
9
|
+
FunctionalGroupLabels = list[tuple[str, tuple[int, ...]]]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def smiles_to_graph_and_functional_groups(
|
|
13
|
+
smiles: str,
|
|
14
|
+
*,
|
|
15
|
+
sanitize: bool = True,
|
|
16
|
+
) -> tuple[nx.Graph, FunctionalGroupLabels]:
|
|
17
|
+
"""Convert SMILES to a molecular graph and detect functional groups.
|
|
18
|
+
|
|
19
|
+
Atom-mapped SMILES keep their non-zero atom-map numbers as graph node IDs.
|
|
20
|
+
Unmapped atoms use their 1-based atom order as node IDs, so both mapped and
|
|
21
|
+
unmapped SMILES can be passed to the same API.
|
|
22
|
+
|
|
23
|
+
:param smiles: Input SMILES, with or without atom-map labels.
|
|
24
|
+
:type smiles: str
|
|
25
|
+
:param sanitize: If ``True``, sanitize the RDKit molecule during conversion.
|
|
26
|
+
:type sanitize: bool
|
|
27
|
+
:return: Molecular graph and detected ``(name, node_ids)`` FG labels.
|
|
28
|
+
:rtype: tuple[nx.Graph, list[tuple[str, tuple[int, ...]]]]
|
|
29
|
+
:raises ValueError: If the SMILES cannot be converted to a molecular graph.
|
|
30
|
+
"""
|
|
31
|
+
graph = smiles_to_graph(
|
|
32
|
+
smiles,
|
|
33
|
+
drop_non_aam=False,
|
|
34
|
+
sanitize=sanitize,
|
|
35
|
+
use_index_as_atom_map=True,
|
|
36
|
+
)
|
|
37
|
+
if graph is None:
|
|
38
|
+
raise ValueError(f"Could not convert SMILES to molecular graph: {smiles!r}")
|
|
39
|
+
return graph, FunctionalGroupDetector().detect(graph)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import Counter
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from time import perf_counter
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
|
|
8
|
+
import networkx as nx
|
|
9
|
+
|
|
10
|
+
from synkit.Chem.Reaction.standardize import Standardize
|
|
11
|
+
from synkit.IO.chem_converter import smiles_to_graph
|
|
12
|
+
|
|
13
|
+
from .detector import FunctionalGroupDetector
|
|
14
|
+
from .ring_system import AromaticRingSystemDetector
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class FunctionalGroupAudit:
|
|
19
|
+
"""Aggregated detector coverage over a reaction-SMILES corpus."""
|
|
20
|
+
|
|
21
|
+
reactions: int
|
|
22
|
+
molecules: int
|
|
23
|
+
parse_failures: int
|
|
24
|
+
elapsed_seconds: float
|
|
25
|
+
label_counts: Counter[str]
|
|
26
|
+
heteroaromatic_systems: int
|
|
27
|
+
named_heteroaromatic_systems: int
|
|
28
|
+
unnamed_heteroaromatic_systems: Counter[tuple]
|
|
29
|
+
uncovered_atom_signatures: Counter[tuple]
|
|
30
|
+
uncovered_edge_signatures: Counter[tuple]
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def unnamed_heteroaromatic_count(self) -> int:
|
|
34
|
+
return self.heteroaromatic_systems - self.named_heteroaromatic_systems
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def audit_reaction_smiles(
|
|
38
|
+
reactions: Iterable[str],
|
|
39
|
+
*,
|
|
40
|
+
standardizer: Standardize | None = None,
|
|
41
|
+
) -> FunctionalGroupAudit:
|
|
42
|
+
"""Audit FG coverage for an iterable of reaction SMILES strings."""
|
|
43
|
+
std = Standardize() if standardizer is None else standardizer
|
|
44
|
+
detector = FunctionalGroupDetector()
|
|
45
|
+
|
|
46
|
+
reaction_count = 0
|
|
47
|
+
molecule_count = 0
|
|
48
|
+
parse_failures = 0
|
|
49
|
+
heteroaromatic_systems = 0
|
|
50
|
+
named_heteroaromatic_systems = 0
|
|
51
|
+
label_counts: Counter[str] = Counter()
|
|
52
|
+
unnamed_systems: Counter[tuple] = Counter()
|
|
53
|
+
uncovered_atoms: Counter[tuple] = Counter()
|
|
54
|
+
uncovered_edges: Counter[tuple] = Counter()
|
|
55
|
+
|
|
56
|
+
started = perf_counter()
|
|
57
|
+
for reaction in reactions:
|
|
58
|
+
reaction_count += 1
|
|
59
|
+
standardized = std.fit(reaction, remove_aam=True)
|
|
60
|
+
for side in standardized.split(">>"):
|
|
61
|
+
for smiles in side.split("."):
|
|
62
|
+
graph = smiles_to_graph(
|
|
63
|
+
smiles,
|
|
64
|
+
drop_non_aam=False,
|
|
65
|
+
use_index_as_atom_map=True,
|
|
66
|
+
)
|
|
67
|
+
if graph is None:
|
|
68
|
+
parse_failures += 1
|
|
69
|
+
continue
|
|
70
|
+
molecule_count += 1
|
|
71
|
+
matches = detector.matches(graph)
|
|
72
|
+
label_counts.update(match.name for match in matches)
|
|
73
|
+
covered = {node for match in matches for node in match.group_nodes}
|
|
74
|
+
_count_uncovered_signatures(
|
|
75
|
+
graph,
|
|
76
|
+
covered,
|
|
77
|
+
uncovered_atoms,
|
|
78
|
+
uncovered_edges,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
named_ring_nodes = {
|
|
82
|
+
match.group_nodes
|
|
83
|
+
for match in matches
|
|
84
|
+
if match.name != "heteroaromatic_ring"
|
|
85
|
+
and match.pattern.priority == 70
|
|
86
|
+
}
|
|
87
|
+
for system in AromaticRingSystemDetector.detect(graph):
|
|
88
|
+
if not system.hetero_nodes:
|
|
89
|
+
continue
|
|
90
|
+
heteroaromatic_systems += 1
|
|
91
|
+
has_named_subring = any(
|
|
92
|
+
set(nodes).issubset(system.nodes) for nodes in named_ring_nodes
|
|
93
|
+
)
|
|
94
|
+
if has_named_subring:
|
|
95
|
+
named_heteroaromatic_systems += 1
|
|
96
|
+
continue
|
|
97
|
+
unnamed_systems[
|
|
98
|
+
(
|
|
99
|
+
system.hetero_pattern,
|
|
100
|
+
system.is_fused,
|
|
101
|
+
system.ring_sizes,
|
|
102
|
+
tuple(sorted(system.element_counts.items())),
|
|
103
|
+
)
|
|
104
|
+
] += 1
|
|
105
|
+
|
|
106
|
+
return FunctionalGroupAudit(
|
|
107
|
+
reactions=reaction_count,
|
|
108
|
+
molecules=molecule_count,
|
|
109
|
+
parse_failures=parse_failures,
|
|
110
|
+
elapsed_seconds=perf_counter() - started,
|
|
111
|
+
label_counts=label_counts,
|
|
112
|
+
heteroaromatic_systems=heteroaromatic_systems,
|
|
113
|
+
named_heteroaromatic_systems=named_heteroaromatic_systems,
|
|
114
|
+
unnamed_heteroaromatic_systems=unnamed_systems,
|
|
115
|
+
uncovered_atom_signatures=uncovered_atoms,
|
|
116
|
+
uncovered_edge_signatures=uncovered_edges,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _count_uncovered_signatures(
|
|
121
|
+
graph: nx.Graph,
|
|
122
|
+
covered: set[int],
|
|
123
|
+
atom_counts: Counter[tuple],
|
|
124
|
+
edge_counts: Counter[tuple],
|
|
125
|
+
) -> None:
|
|
126
|
+
for node, data in graph.nodes(data=True):
|
|
127
|
+
if data.get("element") == "H" or node in covered:
|
|
128
|
+
continue
|
|
129
|
+
neighbors = tuple(
|
|
130
|
+
sorted(
|
|
131
|
+
graph.nodes[neighbor].get("element")
|
|
132
|
+
for neighbor in graph.neighbors(node)
|
|
133
|
+
if graph.nodes[neighbor].get("element") != "H"
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
atom_counts[
|
|
137
|
+
(
|
|
138
|
+
data.get("element"),
|
|
139
|
+
data.get("aromatic", False),
|
|
140
|
+
data.get("hcount", 0),
|
|
141
|
+
neighbors,
|
|
142
|
+
)
|
|
143
|
+
] += 1
|
|
144
|
+
|
|
145
|
+
for left, right, data in graph.edges(data=True):
|
|
146
|
+
if left in covered or right in covered:
|
|
147
|
+
continue
|
|
148
|
+
left_element = graph.nodes[left].get("element")
|
|
149
|
+
right_element = graph.nodes[right].get("element")
|
|
150
|
+
if "H" in {left_element, right_element}:
|
|
151
|
+
continue
|
|
152
|
+
edge_counts[
|
|
153
|
+
tuple(sorted((left_element, right_element)))
|
|
154
|
+
+ (data.get("order"), data.get("aromatic", False))
|
|
155
|
+
] += 1
|