synkit 0.0.9__tar.gz → 0.0.10__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-0.0.9 → synkit-0.0.10}/PKG-INFO +1 -1
- synkit-0.0.10/Test/Chem/Reaction/test_radical_wildcard.py +51 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/ITS/test_its_expand.py +9 -1
- synkit-0.0.10/Test/Graph/ITS/test_its_relabel.py +61 -0
- synkit-0.0.10/Test/Rule/Apply/test_rule_matcher.py +94 -0
- synkit-0.0.10/Test/Synthesis/Reactor/test_partial_engine.py +48 -0
- synkit-0.0.10/Test/Synthesis/Reactor/test_rbl_reactor.py +52 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/api.rst +6 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/conf.py +17 -2
- synkit-0.0.10/doc/figures/context.png +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/getting_started.rst +1 -1
- {synkit-0.0.9 → synkit-0.0.10}/doc/graph.rst +44 -0
- {synkit-0.0.9 → synkit-0.0.10}/pyproject.toml +1 -1
- synkit-0.0.10/synkit/Chem/Reaction/radical_wildcard.py +165 -0
- synkit-0.0.10/synkit/Data/gen_partial_aam.py +148 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Canon/nauty.py +36 -8
- synkit-0.0.10/synkit/Graph/ITS/its_builder.py +114 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/ITS/its_construction.py +80 -71
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/ITS/its_decompose.py +53 -20
- synkit-0.0.10/synkit/Graph/ITS/its_expand.py +84 -0
- synkit-0.0.10/synkit/Graph/ITS/its_relabel.py +191 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/graph_morphism.py +100 -2
- synkit-0.0.10/synkit/Graph/Wildcard/fuse_graph.py +158 -0
- synkit-0.0.10/synkit/Rule/Apply/rule_matcher.py +191 -0
- synkit-0.0.10/synkit/Synthesis/Reactor/partial_engine.py +71 -0
- synkit-0.0.10/synkit/Synthesis/Reactor/rbl_engine.py +123 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/syn_reactor.py +63 -45
- synkit-0.0.10/synkit/Utils/__init__.py +0 -0
- synkit-0.0.10/synkit/__init__.py +0 -0
- synkit-0.0.9/synkit/Graph/ITS/its_builder.py +0 -94
- synkit-0.0.9/synkit/Graph/ITS/its_expand.py +0 -88
- {synkit-0.0.9 → synkit-0.0.10}/.github/workflows/build-doc.yml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/.github/workflows/publish-package.yml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/.github/workflows/test-and-lint.yml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/.gitignore +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/.readthedocs.yml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Data/Figure/synkit.png +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Data/Testcase/Compose/ComposeRule/data.txt +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Data/Testcase/Compose/SingleRule/R0/0.gml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Data/Testcase/Compose/SingleRule/R0/1.gml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Data/Testcase/Compose/SingleRule/R0/2.gml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/LICENSE +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Makefile +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/README.md +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Fingerprint/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Fingerprint/test_fp_calculator.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Fingerprint/test_smiles_featurizer.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Fingerprint/test_transformation_fp.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Molecule/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Molecule/test_standardize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_aam_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_aam_validator.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_balance_checker.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_canon_rsmi.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_cleanning.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_deionize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_fix_aam.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_neutralize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_rsmi_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_standardize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/Reaction/test_tautomerize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Chem/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Context/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Context/test_hier_context.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Context/test_radius_expand.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Feature/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Feature/test_graph_descriptors.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Feature/test_graph_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Feature/test_graph_signature.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Feature/test_hash_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Feature/test_morgan_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Feature/test_path_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Hydrogen/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Hydrogen/test_graph_hydrogen.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Hydrogen/test_hcomplete.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Hydrogen/test_misc.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/ITS/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/ITS/test_its_construction.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/ITS/test_normalize_aam.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/MTG/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/MTG/test_group_comp.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/MTG/test_groupoid.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/MTG/test_mtg.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Matcher/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Matcher/test_batch_cluster.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Matcher/test_graph_cluster.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Matcher/test_graph_matcher.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Matcher/test_graph_morphism.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/Matcher/test_subgraph_matcher.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/test_canon_graph.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Graph/test_syn_graph.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/IO/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/IO/test_chemical_converter.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/IO/test_dg_to_gml.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/IO/test_gml_to_nx.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/IO/test_graph_to_mol.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/IO/test_mol_to_graph.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/IO/test_nx_to_gml.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Apply/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Apply/test_reactor_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Apply/test_retro_reactor.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Apply/test_rule_rbl.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Compose/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Compose/test_rule_compose.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Compose/test_valance_constrain.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Modify/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Modify/test_molecule_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/Modify/test_rule_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Rule/test_syn_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/CRN/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/CRN/test_crn.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/CRN/test_mod_crn.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/MSR/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/MSR/test_multi_steps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/MSR/test_path_finder.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/Reactor/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/Reactor/test_core_engine.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/Reactor/test_mod_aam.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/Reactor/test_mod_reactor.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/Reactor/test_strategy.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Synthesis/test_reactor_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Vis/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/Vis/test_embedding.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/Test/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/build-doc.sh +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/changelog.rst +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/chem.rst +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/figures/aldol.png +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/figures/aldol_its.png +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/figures/mtg.png +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/index.rst +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/io.rst +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/reference.rst +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/refs.bib +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/requirements.txt +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/rule.rst +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/doc/synthesis.rst +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/environment.yml +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/lint.sh +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/make.bat +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/pytest.sh +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/requirements.txt +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Fingerprint/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Fingerprint/transformation_fp.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Molecule/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Molecule/standardize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/aam_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/aam_validator.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/balance_check.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/cleanning.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/deionize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/fix_aam.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/neutralize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/rsmi_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/standardize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/Reaction/tautomerize.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Chem/utils.py +0 -0
- {synkit-0.0.9/synkit/Graph/Context → synkit-0.0.10/synkit/Data}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Canon/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Canon/canon_algs.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Canon/canon_graph.py +0 -0
- {synkit-0.0.9/synkit/Graph/Hyrogen → synkit-0.0.10/synkit/Graph/Context}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Context/hier_context.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Context/radius_expand.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/graph_descriptors.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/graph_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/graph_signature.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/hash_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/morgan_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/path_fps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Feature/wl_hash.py +0 -0
- {synkit-0.0.9/synkit/Graph/MTG → synkit-0.0.10/synkit/Graph/Hyrogen}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Hyrogen/_misc.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Hyrogen/hextend.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/ITS/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/ITS/normalize_aam.py +0 -0
- {synkit-0.0.9/synkit/Rule/Apply → synkit-0.0.10/synkit/Graph/MTG}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/MTG/group_comp.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/MTG/groupoid.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/MTG/mcs_matcher.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/MTG/mtg.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/batch_cluster.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/graph_cluster.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/graph_matcher.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/mcs_matcher.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/multi_turbo_iso.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/partial_matcher.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/sing.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/subgraph_matcher.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/Matcher/turbo_iso.py +0 -0
- {synkit-0.0.9/synkit/Rule/Compose → synkit-0.0.10/synkit/Graph/Wildcard}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/canon_graph.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/syn_graph.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Graph/utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/chem_converter.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/data_io.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/data_process.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/debug.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/dg_to_gml.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/gml_to_nx.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/graph_to_mol.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/mol_to_graph.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/nx_to_gml.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/IO/smiles_to_id.py +0 -0
- {synkit-0.0.9/synkit/Rule/Modify → synkit-0.0.10/synkit/Rule/Apply}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Apply/reactor_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Apply/retro_reactor.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Apply/rule_rbl.py +0 -0
- {synkit-0.0.9/synkit/Synthesis/CRN → synkit-0.0.10/synkit/Rule/Compose}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Compose/compose_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Compose/rule_compose.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Compose/rule_mapping.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Compose/seq_comp.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Compose/valence_constrain.py +0 -0
- {synkit-0.0.9/synkit/Synthesis/MSR → synkit-0.0.10/synkit/Rule/Modify}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Modify/implict_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Modify/longest_path.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Modify/molecule_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Modify/prune_templates.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Modify/rule_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/Modify/strip_rule.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Rule/syn_rule.py +0 -0
- {synkit-0.0.9/synkit/Synthesis/Metrics → synkit-0.0.10/synkit/Synthesis/CRN}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/CRN/crn.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/CRN/dcrn.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/CRN/mod_crn.py +0 -0
- {synkit-0.0.9/synkit/Synthesis/Reactor → synkit-0.0.10/synkit/Synthesis/MSR}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/MSR/multi_steps.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/MSR/path_finder.py +0 -0
- {synkit-0.0.9/synkit/Synthesis → synkit-0.0.10/synkit/Synthesis/Metrics}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Metrics/_base.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Metrics/_plot.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Metrics/_ranking.py +0 -0
- {synkit-0.0.9/synkit/Utils → synkit-0.0.10/synkit/Synthesis/Reactor}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/batch_reactor.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/core_engine.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/rule_filter.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/Reactor/strategy.py +0 -0
- {synkit-0.0.9/synkit → synkit-0.0.10/synkit/Synthesis}/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Synthesis/reactor_utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Utils/utils.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Vis/__init__.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Vis/chemical_space.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Vis/embedding.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Vis/graph_visualizer.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Vis/pdf_writer.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Vis/rule_vis.py +0 -0
- {synkit-0.0.9 → synkit-0.0.10}/synkit/Vis/rxn_vis.py +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from synkit.Chem.Reaction.radical_wildcard import RadicalWildcardAdder
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestRadicalWildcardAdder(unittest.TestCase):
|
|
6
|
+
"""
|
|
7
|
+
Unit tests for RadicalWildcardAdder using unittest.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def test_transform_given_rxn(self):
|
|
11
|
+
"""
|
|
12
|
+
Ensure that transform() applies the wildcard_map correctly and preserves explicit H.
|
|
13
|
+
"""
|
|
14
|
+
rxn_in = (
|
|
15
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[OH:5]."
|
|
16
|
+
"[O:6][H:7]>>"
|
|
17
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:6]."
|
|
18
|
+
"[OH:5][H:7]"
|
|
19
|
+
)
|
|
20
|
+
adder = RadicalWildcardAdder()
|
|
21
|
+
result = adder.transform(rxn_in)
|
|
22
|
+
|
|
23
|
+
expected = (
|
|
24
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[OH:5]."
|
|
25
|
+
"[O:6]([H:7])[*:8]>>"
|
|
26
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:6][*:8]."
|
|
27
|
+
"[OH:5][H:7]"
|
|
28
|
+
)
|
|
29
|
+
self.assertEqual(result, expected)
|
|
30
|
+
|
|
31
|
+
def test_auto_map_selection_and_repr_str(self):
|
|
32
|
+
"""
|
|
33
|
+
Check that wildcard_map is auto-selected and repr/str methods report correctly.
|
|
34
|
+
"""
|
|
35
|
+
rxn = (
|
|
36
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[OH:5]."
|
|
37
|
+
"[O:6][H:7]>>"
|
|
38
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:6]."
|
|
39
|
+
"[OH:5][H:7]"
|
|
40
|
+
)
|
|
41
|
+
adder = RadicalWildcardAdder()
|
|
42
|
+
out = adder.transform(rxn)
|
|
43
|
+
|
|
44
|
+
# The output should contain exactly one [*:8] in reactants and one in products
|
|
45
|
+
reactants, products = out.split(">>")
|
|
46
|
+
self.assertEqual(reactants.count("[*:8]"), 1)
|
|
47
|
+
self.assertEqual(products.count("[*:8]"), 1)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
unittest.main()
|
|
@@ -12,7 +12,15 @@ class TestPartialExpand(unittest.TestCase):
|
|
|
12
12
|
"[CH3:1][CH2:2][CH2:3][Cl:4].[NH2:5][H:6]"
|
|
13
13
|
+ ">>[CH3:1][CH2:2][CH2:3][NH2:5].[Cl:4][H:6]"
|
|
14
14
|
)
|
|
15
|
-
|
|
15
|
+
self.assertTrue(AAMValidator.smiles_check(output_rsmi, expected_rsmi, "ITS"))
|
|
16
|
+
|
|
17
|
+
def test_expand_with_relabel(self):
|
|
18
|
+
input_rsmi = "CC[CH2:3][Cl:1].[NH2:2][H:4]>>CC[CH2:3][NH2:2].[Cl:1][H:4]"
|
|
19
|
+
output_rsmi = ITSExpand.expand_aam_with_its(input_rsmi, relabel=True)
|
|
20
|
+
expected_rsmi = (
|
|
21
|
+
"[CH3:1][CH2:2][CH2:3][Cl:4].[NH2:5][H:6]"
|
|
22
|
+
+ ">>[CH3:1][CH2:2][CH2:3][NH2:5].[Cl:4][H:6]"
|
|
23
|
+
)
|
|
16
24
|
self.assertTrue(AAMValidator.smiles_check(output_rsmi, expected_rsmi, "ITS"))
|
|
17
25
|
|
|
18
26
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from rdkit import Chem
|
|
3
|
+
from synkit.Graph.ITS.its_relabel import ITSRelabel
|
|
4
|
+
from synkit.Graph.syn_graph import SynGraph
|
|
5
|
+
from synkit.IO.chem_converter import smiles_to_graph
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestITSRelabel(unittest.TestCase):
|
|
9
|
+
def setUp(self):
|
|
10
|
+
self.its = ITSRelabel()
|
|
11
|
+
|
|
12
|
+
def test_get_nodes_with_atom_map(self):
|
|
13
|
+
# Build a graph from SMILES with explicit atom mapping
|
|
14
|
+
raw = smiles_to_graph(
|
|
15
|
+
"[CH3:1][CH2:2][OH:3]", use_index_as_atom_map=True, drop_non_aam=False
|
|
16
|
+
)
|
|
17
|
+
sg = SynGraph(raw)
|
|
18
|
+
nodes = ITSRelabel._get_nodes_with_atom_map(sg)
|
|
19
|
+
# Every atom should have a non-zero atom_map
|
|
20
|
+
self.assertCountEqual(nodes, list(sg.raw.nodes()))
|
|
21
|
+
|
|
22
|
+
def test_remove_internal_edges(self):
|
|
23
|
+
# Linear 3‑carbon chain; after removing internal edges none remain
|
|
24
|
+
raw = smiles_to_graph("CCC", use_index_as_atom_map=True, drop_non_aam=False)
|
|
25
|
+
sg = SynGraph(raw)
|
|
26
|
+
all_nodes = list(sg.raw.nodes())
|
|
27
|
+
pruned = ITSRelabel._remove_internal_edges(sg, all_nodes)
|
|
28
|
+
self.assertEqual(pruned.raw.number_of_edges(), 0)
|
|
29
|
+
|
|
30
|
+
def test_dict_to_tuple_list_sorting(self):
|
|
31
|
+
mapping = {3: 1, 2: 2, 1: 3}
|
|
32
|
+
# Sort by key
|
|
33
|
+
by_key = ITSRelabel._dict_to_tuple_list(mapping, sort_by_key=True)
|
|
34
|
+
self.assertEqual(by_key, [(1, 3), (2, 2), (3, 1)])
|
|
35
|
+
# Sort by value
|
|
36
|
+
by_val = ITSRelabel._dict_to_tuple_list(mapping, sort_by_value=True)
|
|
37
|
+
self.assertEqual(by_val, [(3, 1), (2, 2), (1, 3)])
|
|
38
|
+
# No sorting
|
|
39
|
+
no_sort = ITSRelabel._dict_to_tuple_list(mapping)
|
|
40
|
+
self.assertCountEqual(no_sort, [(1, 3), (2, 2), (3, 1)])
|
|
41
|
+
|
|
42
|
+
def test_fit_simple_reaction(self):
|
|
43
|
+
# CCO to CC=O, mapping preserved
|
|
44
|
+
input_rsmi = "CC[CH2:3][Cl:1].[N:2]>>CC[CH2:3][N:2].[Cl:1]"
|
|
45
|
+
out = self.its.fit(input_rsmi)
|
|
46
|
+
react, prod = out.split(">>")
|
|
47
|
+
self.assertIsNotNone(Chem.MolFromSmiles(react))
|
|
48
|
+
self.assertIsNotNone(Chem.MolFromSmiles(prod))
|
|
49
|
+
|
|
50
|
+
def test_fit_invalid_format_raises(self):
|
|
51
|
+
with self.assertRaises(ValueError):
|
|
52
|
+
self.its.fit("invalid_format")
|
|
53
|
+
|
|
54
|
+
def test_fit_non_isomorphic_raises(self):
|
|
55
|
+
# Wrong reaction format
|
|
56
|
+
with self.assertRaises(ValueError):
|
|
57
|
+
self.its.fit("C:1>CC:1")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
unittest.main()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import unittest
|
|
3
|
+
from contextlib import redirect_stdout
|
|
4
|
+
|
|
5
|
+
import networkx as nx
|
|
6
|
+
|
|
7
|
+
from synkit.IO.chem_converter import rsmi_to_its
|
|
8
|
+
from synkit.Rule.Apply.rule_matcher import RuleMatcher
|
|
9
|
+
from synkit.Chem.Reaction.standardize import Standardize
|
|
10
|
+
from synkit.Chem.Reaction.aam_validator import AAMValidator
|
|
11
|
+
from synkit.Chem.Reaction.balance_check import BalanceReactionCheck
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestRuleMatcher(unittest.TestCase):
|
|
15
|
+
|
|
16
|
+
def test_rule_match_balance(self):
|
|
17
|
+
"""Balanced reaction should match directly and produce correct SMARTS."""
|
|
18
|
+
input_rsmi = "CC[CH2:3][Cl:1].[NH2:2][H:4]>>CC[CH2:3][NH2:2].[Cl:1][H:4]"
|
|
19
|
+
rule = rsmi_to_its(input_rsmi, core=True)
|
|
20
|
+
rsmi_std = Standardize().fit(input_rsmi)
|
|
21
|
+
expected_rsmi = (
|
|
22
|
+
"[CH3:1][CH2:2][CH2:3][Cl:4].[NH2:5][H:6]"
|
|
23
|
+
">>[CH3:1][CH2:2][CH2:3][NH2:5].[Cl:4][H:6]"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
matcher = RuleMatcher(rsmi_std, rule)
|
|
27
|
+
smarts, returned_rule = matcher.get_result()
|
|
28
|
+
|
|
29
|
+
# The returned SMARTS should regenerate the expected RSMI via AAMValidator
|
|
30
|
+
self.assertTrue(AAMValidator.smiles_check(smarts, expected_rsmi, "ITS"))
|
|
31
|
+
# The returned rule graph should be isomorphic to the input rule
|
|
32
|
+
self.assertTrue(nx.is_isomorphic(returned_rule, rule))
|
|
33
|
+
|
|
34
|
+
def test_rbl_missing_product(self):
|
|
35
|
+
"""Partial (RBL) match when product fragments are missing in rule."""
|
|
36
|
+
rsmi = "CC(Br)C.CB(O)O>>CC(C)C"
|
|
37
|
+
template = "[CH3:1][Br:2].[BH2:3][CH3:4]>>[CH3:1][CH3:4].[BH2:3][Br:2]"
|
|
38
|
+
matcher = RuleMatcher(rsmi, template)
|
|
39
|
+
smarts, _ = matcher.get_result()
|
|
40
|
+
expect = "CB(O)O.CC(C)Br>>CC(C)C.OB(O)Br"
|
|
41
|
+
self.assertEqual(Standardize().fit(smarts), expect)
|
|
42
|
+
|
|
43
|
+
def test_rbl_missing_reactant(self):
|
|
44
|
+
"""Partial (RBL) match when reactant fragments are missing in rule."""
|
|
45
|
+
rsmi = "CCC(=O)(O)>>CCC(=O)OC.O"
|
|
46
|
+
template = (
|
|
47
|
+
"[CH3:1][C:2](=[O:3])[OH:4].[CH3:5][O:6][H:7]"
|
|
48
|
+
">>[CH3:1][C:2](=[O:3])[O:6][CH3:5].[H:7][OH:4]"
|
|
49
|
+
)
|
|
50
|
+
matcher = RuleMatcher(rsmi, template)
|
|
51
|
+
smarts, _ = matcher.get_result()
|
|
52
|
+
expect = "CCC(=O)O.CO>>CCC(=O)OC.O"
|
|
53
|
+
self.assertEqual(Standardize().fit(smarts), expect)
|
|
54
|
+
|
|
55
|
+
def test_no_match_raises(self):
|
|
56
|
+
"""If no SMARTS reproduces the RSMI under the rule, a ValueError is raised."""
|
|
57
|
+
rsmi = "CCO>>CC=O"
|
|
58
|
+
# Use a completely unrelated template
|
|
59
|
+
bad_template = "[CH3:1][OH:2]>>[CH2:1]=O"
|
|
60
|
+
with self.assertRaises(ValueError):
|
|
61
|
+
RuleMatcher(rsmi, bad_template)
|
|
62
|
+
|
|
63
|
+
def test_str_and_repr(self):
|
|
64
|
+
"""__str__ and __repr__ reflect the RSMI, balance status, and rule size."""
|
|
65
|
+
input_rsmi = "CC[CH2:3][Cl:1].[NH2:2][H:4]>>CC[CH2:3][NH2:2].[Cl:1][H:4]"
|
|
66
|
+
template = rsmi_to_its(input_rsmi, core=True)
|
|
67
|
+
rsmi = Standardize().fit(input_rsmi)
|
|
68
|
+
matcher = RuleMatcher(rsmi, template)
|
|
69
|
+
# str should mention balanced/unbalanced correctly
|
|
70
|
+
if BalanceReactionCheck(n_jobs=1).rsmi_balance_check(matcher.rsmi):
|
|
71
|
+
self.assertIn("(balanced)", str(matcher))
|
|
72
|
+
else:
|
|
73
|
+
self.assertIn("(unbalanced)", str(matcher))
|
|
74
|
+
# repr should include node/edge counts of the rule
|
|
75
|
+
rep = repr(matcher)
|
|
76
|
+
self.assertIn("RuleMatcher(rsmi=", rep)
|
|
77
|
+
self.assertIn("balanced=", rep)
|
|
78
|
+
|
|
79
|
+
def test_help_output(self):
|
|
80
|
+
"""help() should print internal state and list candidate SMARTS patterns."""
|
|
81
|
+
input_rsmi = "CC[CH2:3][Cl:1].[NH2:2][H:4]>>CC[CH2:3][NH2:2].[Cl:1][H:4]"
|
|
82
|
+
template = rsmi_to_its(input_rsmi, core=True)
|
|
83
|
+
rsmi = Standardize().fit(input_rsmi)
|
|
84
|
+
matcher = RuleMatcher(rsmi, template)
|
|
85
|
+
buf = io.StringIO()
|
|
86
|
+
with redirect_stdout(buf):
|
|
87
|
+
matcher.help()
|
|
88
|
+
out = buf.getvalue()
|
|
89
|
+
self.assertIn("RuleMatcher for RSMI", out)
|
|
90
|
+
self.assertIn("Candidate SMARTS patterns:", out)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
unittest.main()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from synkit.Synthesis.Reactor.partial_engine import PartialEngine
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestPartialEngine(unittest.TestCase):
|
|
6
|
+
def test_forward_direction_example(self):
|
|
7
|
+
"""
|
|
8
|
+
Example 1:
|
|
9
|
+
PartialEngine(smi='CCC(=O)OC',
|
|
10
|
+
template='[C:1][O:2].[O:3][H:4]>>[C:1][O:3].[O:2][H:4]')
|
|
11
|
+
.fit(invert=False)
|
|
12
|
+
should return the two forward wildcarded SMARTS.
|
|
13
|
+
"""
|
|
14
|
+
smi = "CCC(=O)OC"
|
|
15
|
+
template = "[C:1][O:2].[O:3][H:4]>>[C:1][O:3].[O:2][H:4]"
|
|
16
|
+
engine = PartialEngine(smi, template)
|
|
17
|
+
result = engine.fit(invert=False)
|
|
18
|
+
expected = [
|
|
19
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:5][CH3:6].[OH:7][*:8]>>"
|
|
20
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:7][*:8].[OH:5][CH3:6]",
|
|
21
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:5][CH3:6].[OH:7][*:8]>>"
|
|
22
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[OH:5].[CH3:6][O:7][*:8]",
|
|
23
|
+
]
|
|
24
|
+
self.assertEqual(result, expected)
|
|
25
|
+
|
|
26
|
+
def test_backward_direction_example(self):
|
|
27
|
+
"""
|
|
28
|
+
Example 2:
|
|
29
|
+
PartialEngine(smi='CCC(=O)OCC',
|
|
30
|
+
template='[C:1][O:2].[O:3][H:4]>>[C:1][O:3].[O:2][H:4]')
|
|
31
|
+
.fit(invert=True)
|
|
32
|
+
should return the two backward wildcarded SMARTS.
|
|
33
|
+
"""
|
|
34
|
+
smi = "CCC(=O)OCC"
|
|
35
|
+
template = "[C:1][O:2].[O:3][H:4]>>[C:1][O:3].[O:2][H:4]"
|
|
36
|
+
engine = PartialEngine(smi, template)
|
|
37
|
+
result = engine.fit(invert=True)
|
|
38
|
+
expected = [
|
|
39
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:8][*:9].[OH:5][CH2:6][CH3:7]>>"
|
|
40
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:5][CH2:6][CH3:7].[OH:8][*:9]",
|
|
41
|
+
"[CH2:6]([CH3:7])[O:8][*:9].[CH3:1][CH2:2][C:3](=[O:4])[OH:5]>>"
|
|
42
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:5][CH2:6][CH3:7].[OH:8][*:9]",
|
|
43
|
+
]
|
|
44
|
+
self.assertEqual(result, expected)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
unittest.main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from synkit.IO import its_to_rsmi, rsmi_to_its
|
|
3
|
+
from synkit.Synthesis.Reactor.rbl_engine import RBLEngine
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestRBLEngine(unittest.TestCase):
|
|
7
|
+
def test_example1(self):
|
|
8
|
+
# Example 1
|
|
9
|
+
rsmi = "CCC(=O)(O)>>CCC(=O)OC"
|
|
10
|
+
raw_template = (
|
|
11
|
+
"[CH3:1][C:2](=[O:3])[OH:4]."
|
|
12
|
+
"[CH3:5][O:6][H:7]>>"
|
|
13
|
+
"[CH3:1][C:2](=[O:3])[O:6][CH3:5]."
|
|
14
|
+
"[H:7][OH:4]"
|
|
15
|
+
)
|
|
16
|
+
its = rsmi_to_its(raw_template, core=True)
|
|
17
|
+
template = its_to_rsmi(its)
|
|
18
|
+
|
|
19
|
+
engine = RBLEngine(rsmi, template)
|
|
20
|
+
result = engine.fit()
|
|
21
|
+
|
|
22
|
+
expected = [
|
|
23
|
+
(
|
|
24
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[OH:7]."
|
|
25
|
+
"[OH:5][CH3:6]>>"
|
|
26
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:5][CH3:6]."
|
|
27
|
+
"[OH2:7]"
|
|
28
|
+
)
|
|
29
|
+
]
|
|
30
|
+
self.assertEqual(result, expected)
|
|
31
|
+
|
|
32
|
+
def test_example2(self):
|
|
33
|
+
# Example 2
|
|
34
|
+
rsmi = "CCC(=O)OC>>CCC(=O)OCC"
|
|
35
|
+
template = "[C:1][O:2].[O:3][H:4]>>[C:1][O:3].[O:2][H:4]"
|
|
36
|
+
|
|
37
|
+
engine = RBLEngine(rsmi, template)
|
|
38
|
+
result = engine.fit()
|
|
39
|
+
|
|
40
|
+
expected = [
|
|
41
|
+
(
|
|
42
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:8][CH3:9]."
|
|
43
|
+
"[OH:5][CH2:6][CH3:7]>>"
|
|
44
|
+
"[CH3:1][CH2:2][C:3](=[O:4])[O:5][CH2:6][CH3:7]."
|
|
45
|
+
"[OH:8][CH3:9]"
|
|
46
|
+
)
|
|
47
|
+
]
|
|
48
|
+
self.assertEqual(result, expected)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
unittest.main()
|
|
@@ -48,6 +48,7 @@ The `ITS` submodule provides tools for constructing, decomposing, and validating
|
|
|
48
48
|
|
|
49
49
|
- **its_construction**: Functions for constructing an ITS graph.
|
|
50
50
|
- **its_decompose**: Functions for decomposing an ITS graph and extracting reaction center.
|
|
51
|
+
- **its_expand**: Functions for expanding partial ITS graphs into full ITS graphs.
|
|
51
52
|
|
|
52
53
|
.. automodule:: synkit.Graph.ITS.its_construction
|
|
53
54
|
:members:
|
|
@@ -55,6 +56,11 @@ The `ITS` submodule provides tools for constructing, decomposing, and validating
|
|
|
55
56
|
:show-inheritance:
|
|
56
57
|
|
|
57
58
|
.. automodule:: synkit.Graph.ITS.its_decompose
|
|
59
|
+
:members: get_rc, its_decompose
|
|
60
|
+
:undoc-members:
|
|
61
|
+
:show-inheritance:
|
|
62
|
+
|
|
63
|
+
.. automodule:: synkit.Graph.ITS.its_expand
|
|
58
64
|
:members:
|
|
59
65
|
:undoc-members:
|
|
60
66
|
:show-inheritance:
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
from importlib.metadata import version as _get_version, PackageNotFoundError
|
|
3
4
|
|
|
5
|
+
# -- Path setup --------------------------------------------------------------
|
|
6
|
+
# Add project root to sys.path to import the package
|
|
4
7
|
sys.path.insert(0, os.path.abspath(".."))
|
|
5
8
|
|
|
6
9
|
# -- Project information -----------------------------------------------------
|
|
7
10
|
project = "synkit"
|
|
8
11
|
author = "Tieu-Long Phan"
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
release = _get_version("synkit")
|
|
16
|
+
except PackageNotFoundError:
|
|
17
|
+
try:
|
|
18
|
+
import synkit
|
|
19
|
+
|
|
20
|
+
release = synkit.__version__
|
|
21
|
+
except (ImportError, AttributeError):
|
|
22
|
+
# Fallback default
|
|
23
|
+
release = "0.0.10"
|
|
24
|
+
# Use only major.minor for short version
|
|
25
|
+
version = ".".join(release.split(".")[:2])
|
|
11
26
|
|
|
12
27
|
# -- General configuration ---------------------------------------------------
|
|
13
28
|
extensions = [
|
|
Binary file
|
|
@@ -66,7 +66,7 @@ After installation, verify that **synkit** is available and check its version:
|
|
|
66
66
|
|
|
67
67
|
.. code-block:: bash
|
|
68
68
|
|
|
69
|
-
python -c "import
|
|
69
|
+
python -c "import importlib.metadata as m; print(m.version('synkit'))"
|
|
70
70
|
# Should print the installed synkit version
|
|
71
71
|
|
|
72
72
|
Further Resources
|
|
@@ -254,6 +254,50 @@ This example builds two reaction-center ITS graphs, computes their MCS mapping,
|
|
|
254
254
|
(C) Composite ITS graph "gluing" both transformations
|
|
255
255
|
(D) Mechanistic Transition Graph (MTG) showing step-wise mechanism
|
|
256
256
|
|
|
257
|
+
Context graph
|
|
258
|
+
-------------
|
|
259
|
+
|
|
260
|
+
The ``synkit.Graph.Context`` submodule provides tools for expanding reaction center graphs to include nearest neighbors, enabling context‑aware analysis of reaction networks.
|
|
261
|
+
|
|
262
|
+
.. code-block:: python
|
|
263
|
+
:caption: Context graph expansion example
|
|
264
|
+
:linenos:
|
|
265
|
+
|
|
266
|
+
from synkit.IO import rsmi_to_its
|
|
267
|
+
from synkit.Graph.Context.radius_expand import RadiusExpand
|
|
268
|
+
from synkit.Vis.graph_visualizer import GraphVisualizer
|
|
269
|
+
|
|
270
|
+
smart = (
|
|
271
|
+
'[CH3:1][O:2][C:3](=[O:4])[CH:5]([CH2:6][CH2:7][CH2:8][CH2:9]'
|
|
272
|
+
'[NH:10][C:11](=[O:12])[O:13][CH2:14][c:15]1[cH:16][cH:17]'
|
|
273
|
+
'[cH:18][cH:19][cH:20]1)[NH:21][C:22](=[O:23])[NH:24][c:25]1'
|
|
274
|
+
'[cH:26][c:27]([O:28][CH3:29])[cH:30][c:31]([C:32]([CH3:33])'
|
|
275
|
+
'([CH3:34])[CH3:35])[c:36]1[OH:37].[OH:38][H:39]>>'
|
|
276
|
+
'[C:11](=[O:12])([O:13][CH2:14][c:15]1[cH:16][cH:17][cH:18]'
|
|
277
|
+
'[cH:19][cH:20]1)[OH:38].[CH3:1][O:2][C:3](=[O:4])[CH:5]'
|
|
278
|
+
'([CH2:6][CH2:7][CH2:8][CH2:9][NH:10][H:39])[NH:21][C:22]'
|
|
279
|
+
'(=[O:23])[NH:24][c:25]1[cH:26][c:27]([O:28][CH3:29])[cH:30]'
|
|
280
|
+
'[c:31]([C:32]([CH3:33])([CH3:34])[CH3:35])[c:36]1[OH:37]'
|
|
281
|
+
)
|
|
282
|
+
its = rsmi_to_its(smart)
|
|
283
|
+
rc = rsmi_to_its(smart, core=True)
|
|
284
|
+
exp = RadiusExpand()
|
|
285
|
+
k1 = exp.extract_k(its, n_knn=1)
|
|
286
|
+
|
|
287
|
+
gv = GraphVisualizer()
|
|
288
|
+
gv.visualize_its_grid([rc, k1])
|
|
289
|
+
|
|
290
|
+
.. container:: figure
|
|
291
|
+
|
|
292
|
+
.. image:: ./figures/context.png
|
|
293
|
+
:alt: Context graph expansion example
|
|
294
|
+
:align: center
|
|
295
|
+
:width: 1000px
|
|
296
|
+
|
|
297
|
+
*Figure:*
|
|
298
|
+
(A) Minimal reaction center subgraph obtained by contracting all atoms that participate directly in bond‑order changes.
|
|
299
|
+
Nodes are colour‑coded by element; edges in **red** indicate bonds being broken, while edges in **blue** mark bonds being formed.
|
|
300
|
+
(B) First shell ($k=1$) context expansion: every reaction center atom is augmented with all of its immediate neighbours.
|
|
257
301
|
|
|
258
302
|
|
|
259
303
|
See Also
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from rdkit import Chem
|
|
2
|
+
from rdkit.Chem import SanitizeFlags
|
|
3
|
+
import re
|
|
4
|
+
from typing import Tuple, List, Optional, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RadicalWildcardAdder:
|
|
8
|
+
"""
|
|
9
|
+
A utility for adding wildcard dummy atoms ([*]) to radical centers in reaction SMILES,
|
|
10
|
+
with unique incremental atom-map indices and correct propagation into products.
|
|
11
|
+
|
|
12
|
+
Each reactive radical atom in the reactant block is identified by its unpaired electron count,
|
|
13
|
+
assigned one or more wildcard map indices, and recorded. The same wildcard(s) are then appended
|
|
14
|
+
to the corresponding atom(s) in the product block, ensuring consistent mapping.
|
|
15
|
+
|
|
16
|
+
:param start_map: If provided, this integer will be the first atom-map index
|
|
17
|
+
used for wildcard dummy atoms; subsequent radicals get incremented indices.
|
|
18
|
+
If None, the next unused index is auto-determined from the input SMILES.
|
|
19
|
+
:type start_map: Optional[int]
|
|
20
|
+
|
|
21
|
+
Example
|
|
22
|
+
-------
|
|
23
|
+
>>> adder = RadicalWildcardAdder(start_map=8)
|
|
24
|
+
>>> rxn = "[C:2][OH:4].[O:6][H:7]>>[C:2][O:6].[OH:4][H:7]"
|
|
25
|
+
>>> print(adder.transform(rxn))
|
|
26
|
+
[C:2]([OH:4])[*:8].[O:6]([H:7])[*:9]>>[C:2]([O:6][*:9])[*:8].[OH:4][H:7]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, start_map: Optional[int] = None) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Initialize the adder with an optional starting map index.
|
|
32
|
+
|
|
33
|
+
:param start_map: Starting atom-map index for wildcards or None to auto-pick.
|
|
34
|
+
:type start_map: Optional[int]
|
|
35
|
+
"""
|
|
36
|
+
self.start_map = start_map
|
|
37
|
+
|
|
38
|
+
def __repr__(self) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Official representation.
|
|
41
|
+
"""
|
|
42
|
+
return f"<RadicalWildcardAdder(start_map={self.start_map})>"
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
User-friendly description.
|
|
47
|
+
"""
|
|
48
|
+
m = self.start_map if self.start_map is not None else "auto"
|
|
49
|
+
return f"RadicalWildcardAdder(start_map={m})"
|
|
50
|
+
|
|
51
|
+
def transform(self, rxn_smiles: str) -> str:
|
|
52
|
+
"""
|
|
53
|
+
Append wildcard dummy atoms to each radical center in the reactant block
|
|
54
|
+
and propagate the same wildcards to the matching atoms in the product block.
|
|
55
|
+
|
|
56
|
+
:param rxn_smiles: Reaction SMILES string, two-component or three-component.
|
|
57
|
+
:type rxn_smiles: str
|
|
58
|
+
:returns: Modified reaction SMILES with consistent wildcard attachments.
|
|
59
|
+
:rtype: str
|
|
60
|
+
:raises ValueError: If the SMILES is not valid or fragments fail to parse.
|
|
61
|
+
"""
|
|
62
|
+
# Split into reactants > agents? > products
|
|
63
|
+
react_blk, agents_blk, prod_blk = self._split_reaction(rxn_smiles)
|
|
64
|
+
|
|
65
|
+
# Determine first wildcard map index
|
|
66
|
+
existing = [int(n) for n in re.findall(r":(\d+)", rxn_smiles)]
|
|
67
|
+
next_map = (
|
|
68
|
+
self.start_map
|
|
69
|
+
if self.start_map is not None
|
|
70
|
+
else max(existing, default=0) + 1
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Record mapping: original atom-map -> list of wildcard_maps
|
|
74
|
+
wildcard_map_for: Dict[int, List[int]] = {}
|
|
75
|
+
|
|
76
|
+
# Build sanitizeOps mask (skip H-adjustment)
|
|
77
|
+
keep_ops = SanitizeFlags.SANITIZE_ALL & ~SanitizeFlags.SANITIZE_ADJUSTHS
|
|
78
|
+
|
|
79
|
+
# Process one block (helper)
|
|
80
|
+
def _process(frags: List[str], propagate: bool) -> List[str]:
|
|
81
|
+
nonlocal next_map
|
|
82
|
+
out = []
|
|
83
|
+
for smi in frags:
|
|
84
|
+
if not smi:
|
|
85
|
+
continue
|
|
86
|
+
# Load unsanitized then re-sanitize to preserve explicit H
|
|
87
|
+
mol = Chem.MolFromSmiles(smi, sanitize=False)
|
|
88
|
+
if mol is None:
|
|
89
|
+
raise ValueError(f"Cannot parse SMILES fragment: {smi}")
|
|
90
|
+
Chem.SanitizeMol(mol, sanitizeOps=keep_ops)
|
|
91
|
+
rw = Chem.RWMol(mol)
|
|
92
|
+
|
|
93
|
+
atoms = list(rw.GetAtoms())
|
|
94
|
+
changed = False
|
|
95
|
+
|
|
96
|
+
for atom in atoms:
|
|
97
|
+
rad = atom.GetNumRadicalElectrons()
|
|
98
|
+
orig_map = atom.GetAtomMapNum()
|
|
99
|
+
if rad > 0:
|
|
100
|
+
# Initialize list for this orig_map
|
|
101
|
+
if propagate and orig_map not in wildcard_map_for:
|
|
102
|
+
wildcard_map_for[orig_map] = []
|
|
103
|
+
# For each unpaired electron, attach a wildcard
|
|
104
|
+
for _ in range(rad):
|
|
105
|
+
if propagate:
|
|
106
|
+
wm = next_map
|
|
107
|
+
wildcard_map_for[orig_map].append(wm)
|
|
108
|
+
next_map += 1
|
|
109
|
+
else:
|
|
110
|
+
# in products, use already-recorded wm sequentially
|
|
111
|
+
wm_list = wildcard_map_for.get(orig_map, [])
|
|
112
|
+
if not wm_list:
|
|
113
|
+
continue
|
|
114
|
+
wm = wm_list.pop(0)
|
|
115
|
+
# add dummy wildcard
|
|
116
|
+
dummy = Chem.Atom(0)
|
|
117
|
+
dummy.SetAtomMapNum(wm)
|
|
118
|
+
dummy.SetNoImplicit(True)
|
|
119
|
+
rw.AddAtom(dummy)
|
|
120
|
+
rw.AddBond(
|
|
121
|
+
atom.GetIdx(),
|
|
122
|
+
rw.GetNumAtoms() - 1,
|
|
123
|
+
Chem.BondType.SINGLE,
|
|
124
|
+
)
|
|
125
|
+
changed = True
|
|
126
|
+
|
|
127
|
+
if changed:
|
|
128
|
+
Chem.SanitizeMol(rw.GetMol(), sanitizeOps=keep_ops)
|
|
129
|
+
|
|
130
|
+
out.append(
|
|
131
|
+
Chem.MolToSmiles(
|
|
132
|
+
rw.GetMol(), isomericSmiles=True, allHsExplicit=True
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
return out
|
|
136
|
+
|
|
137
|
+
react_frags = react_blk.split(".") if react_blk else []
|
|
138
|
+
new_reacts = _process(react_frags, propagate=True)
|
|
139
|
+
|
|
140
|
+
prod_frags = prod_blk.split(".") if prod_blk else []
|
|
141
|
+
new_prods = _process(prod_frags, propagate=False)
|
|
142
|
+
|
|
143
|
+
react_str = ".".join(new_reacts)
|
|
144
|
+
prod_str = ".".join(new_prods)
|
|
145
|
+
if agents_blk is None:
|
|
146
|
+
return f"{react_str}>>{prod_str}"
|
|
147
|
+
return f"{react_str}>{agents_blk}>{prod_str}"
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _split_reaction(rxn: str) -> Tuple[str, Optional[str], str]:
|
|
151
|
+
"""
|
|
152
|
+
Split a reaction SMILES into reactants, agents (optional), and products.
|
|
153
|
+
|
|
154
|
+
:param rxn: The reaction SMILES string.
|
|
155
|
+
:type rxn: str
|
|
156
|
+
:returns: Tuple of (reactants_block, agents_block or None, products_block).
|
|
157
|
+
:rtype: Tuple[str, Optional[str], str]
|
|
158
|
+
:raises ValueError: If the SMILES does not contain 2 or 3 '>' symbols.
|
|
159
|
+
"""
|
|
160
|
+
parts = rxn.split(">")
|
|
161
|
+
if len(parts) == 2:
|
|
162
|
+
return parts[0], None, parts[1]
|
|
163
|
+
if len(parts) == 3:
|
|
164
|
+
return parts[0], parts[1], parts[2]
|
|
165
|
+
raise ValueError("Reaction SMILES must contain 2 or 3 '>' symbols")
|