synkit 0.0.8__tar.gz → 0.0.9__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.8 → synkit-0.0.9}/.gitignore +2 -1
- synkit-0.0.9/.readthedocs.yml +24 -0
- {synkit-0.0.8 → synkit-0.0.9}/PKG-INFO +5 -1
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_canon_rsmi.py +5 -1
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Modify/test_molecule_rule.py +1 -1
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Modify/test_rule_utils.py +2 -1
- {synkit-0.0.8 → synkit-0.0.9}/doc/conf.py +14 -11
- {synkit-0.0.8 → synkit-0.0.9}/doc/index.rst +1 -0
- synkit-0.0.9/doc/requirements.txt +3 -0
- {synkit-0.0.8 → synkit-0.0.9}/pyproject.toml +6 -2
- synkit-0.0.9/synkit/Graph/Canon/__init__.py +3 -0
- {synkit-0.0.8/synkit/Graph → synkit-0.0.9/synkit/Graph/Canon}/canon_graph.py +11 -2
- synkit-0.0.9/synkit/Graph/Canon/nauty.py +292 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/_misc.py +19 -2
- synkit-0.0.9/synkit/Graph/Matcher/__init__.py +10 -0
- synkit-0.0.9/synkit/Graph/Matcher/mcs_matcher.py +202 -0
- synkit-0.0.9/synkit/Graph/Matcher/multi_turbo_iso.py +178 -0
- synkit-0.0.9/synkit/Graph/Matcher/partial_matcher.py +214 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/turbo_iso.py +8 -5
- synkit-0.0.9/synkit/Graph/__init__.py +16 -0
- synkit-0.0.9/synkit/Graph/canon_graph.py +538 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/chem_converter.py +23 -17
- synkit-0.0.9/synkit/Synthesis/Reactor/batch_reactor.py +231 -0
- synkit-0.0.9/synkit/Synthesis/Reactor/rule_filter.py +201 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/strategy.py +2 -0
- synkit-0.0.8/synkit/Graph/Matcher/__init__.py +0 -10
- synkit-0.0.8/synkit/Graph/Matcher/mcs_matcher.py +0 -202
- synkit-0.0.8/synkit/Graph/__init__.py +0 -6
- synkit-0.0.8/synkit/Synthesis/Reactor/old_syn_reactor.py +0 -443
- {synkit-0.0.8 → synkit-0.0.9}/.github/workflows/build-doc.yml +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/.github/workflows/publish-package.yml +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/.github/workflows/test-and-lint.yml +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Data/Figure/synkit.png +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/ComposeRule/data.txt +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/SingleRule/R0/0.gml +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/SingleRule/R0/1.gml +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Data/Testcase/Compose/SingleRule/R0/2.gml +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/LICENSE +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Makefile +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/README.md +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/test_fp_calculator.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/test_smiles_featurizer.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Fingerprint/test_transformation_fp.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Molecule/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Molecule/test_standardize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_aam_utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_aam_validator.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_balance_checker.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_cleanning.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_deionize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_fix_aam.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_neutralize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_rsmi_utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_standardize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/Reaction/test_tautomerize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Chem/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Context/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Context/test_hier_context.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Context/test_radius_expand.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_graph_descriptors.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_graph_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_graph_signature.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_hash_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_morgan_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Feature/test_path_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/test_graph_hydrogen.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/test_hcomplete.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Hydrogen/test_misc.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/test_its_construction.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/test_its_expand.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/ITS/test_normalize_aam.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/test_group_comp.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/test_groupoid.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/MTG/test_mtg.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_batch_cluster.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_graph_cluster.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_graph_matcher.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_graph_morphism.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/Matcher/test_subgraph_matcher.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/test_canon_graph.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Graph/test_syn_graph.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/IO/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_chemical_converter.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_dg_to_gml.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_gml_to_nx.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_graph_to_mol.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_mol_to_graph.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/IO/test_nx_to_gml.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/test_reactor_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/test_retro_reactor.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Apply/test_rule_rbl.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Compose/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Compose/test_rule_compose.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Compose/test_valance_constrain.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/Modify/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Rule/test_syn_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/CRN/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/CRN/test_crn.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/CRN/test_mod_crn.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/MSR/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/MSR/test_multi_steps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/MSR/test_path_finder.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_core_engine.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_mod_aam.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_mod_reactor.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/Reactor/test_strategy.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Synthesis/test_reactor_utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Vis/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/Vis/test_embedding.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/Test/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/build-doc.sh +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/api.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/changelog.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/chem.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/figures/aldol.png +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/figures/aldol_its.png +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/figures/mtg.png +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/getting_started.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/graph.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/io.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/reference.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/refs.bib +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/rule.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/doc/synthesis.rst +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/environment.yml +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/lint.sh +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/make.bat +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/pytest.sh +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/requirements.txt +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/fp_calculator.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/smiles_featurizer.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Fingerprint/transformation_fp.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Molecule/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Molecule/standardize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/aam_utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/aam_validator.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/balance_check.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/canon_rsmi.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/cleanning.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/deionize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/fix_aam.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/neutralize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/rsmi_utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/standardize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/Reaction/tautomerize.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Chem/utils.py +0 -0
- {synkit-0.0.8/synkit/Graph → synkit-0.0.9/synkit/Graph/Canon}/canon_algs.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Context/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Context/hier_context.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Context/radius_expand.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/graph_descriptors.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/graph_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/graph_signature.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/hash_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/morgan_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/path_fps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Feature/wl_hash.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/hcomplete.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Hyrogen/hextend.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_builder.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_construction.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_decompose.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/its_expand.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/ITS/normalize_aam.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/group_comp.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/groupoid.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/mcs_matcher.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/MTG/mtg.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/batch_cluster.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/graph_cluster.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/graph_matcher.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/graph_morphism.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/sing.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/Matcher/subgraph_matcher.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/syn_graph.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Graph/utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/data_io.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/data_process.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/debug.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/dg_to_gml.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/gml_to_nx.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/graph_to_mol.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/mol_to_graph.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/nx_to_gml.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/IO/smiles_to_id.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/reactor_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/retro_reactor.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Apply/rule_rbl.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/compose_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/rule_compose.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/rule_mapping.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/seq_comp.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Compose/valence_constrain.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/implict_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/longest_path.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/molecule_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/prune_templates.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/rule_utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/Modify/strip_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Rule/syn_rule.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/crn.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/dcrn.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/CRN/mod_crn.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/MSR/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/MSR/multi_steps.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/MSR/path_finder.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/_base.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/_plot.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Metrics/_ranking.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/core_engine.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/mod_aam.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/mod_reactor.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/single_predictor.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/Reactor/syn_reactor.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Synthesis/reactor_utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Utils/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Utils/utils.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/__init__.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/chemical_space.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/embedding.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/graph_visualizer.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/pdf_writer.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/rule_vis.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/Vis/rxn_vis.py +0 -0
- {synkit-0.0.8 → synkit-0.0.9}/synkit/__init__.py +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Read the Docs configuration file
|
|
2
|
+
# https://docs.readthedocs.io/en/stable/config-file/v2.html
|
|
3
|
+
|
|
4
|
+
# 1) Tell RTD this is v2 of the schema
|
|
5
|
+
version: 2
|
|
6
|
+
|
|
7
|
+
# 2) Pick your build image and interpreter
|
|
8
|
+
build:
|
|
9
|
+
os: ubuntu-22.04
|
|
10
|
+
tools:
|
|
11
|
+
python: "3.11"
|
|
12
|
+
|
|
13
|
+
# 3) Install your package + any doc-only extras
|
|
14
|
+
python:
|
|
15
|
+
install:
|
|
16
|
+
- method: pip
|
|
17
|
+
path: .
|
|
18
|
+
extra_requirements:
|
|
19
|
+
- docs
|
|
20
|
+
|
|
21
|
+
# 4) Point Sphinx at your conf.py and choose HTML
|
|
22
|
+
sphinx:
|
|
23
|
+
configuration: doc/conf.py
|
|
24
|
+
builder: html
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: synkit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: Utility for reaction modeling using graph grammar
|
|
5
5
|
Project-URL: homepage, https://github.com/TieuLongPhan/SynKit
|
|
6
6
|
Project-URL: source, https://github.com/TieuLongPhan/SynKit
|
|
@@ -22,6 +22,10 @@ Requires-Dist: seaborn>=0.13.2
|
|
|
22
22
|
Provides-Extra: all
|
|
23
23
|
Requires-Dist: numpy>=2.2.0; extra == 'all'
|
|
24
24
|
Requires-Dist: torch>=2.2.0; extra == 'all'
|
|
25
|
+
Provides-Extra: docs
|
|
26
|
+
Requires-Dist: sphinx-rtd-theme; extra == 'docs'
|
|
27
|
+
Requires-Dist: sphinx>=6.0; extra == 'docs'
|
|
28
|
+
Requires-Dist: sphinxcontrib-bibtex; extra == 'docs'
|
|
25
29
|
Description-Content-Type: text/markdown
|
|
26
30
|
|
|
27
31
|
# SynKit
|
|
@@ -7,7 +7,11 @@ from synkit.IO.chem_converter import rsmi_to_graph
|
|
|
7
7
|
class TestCanonRSMI(unittest.TestCase):
|
|
8
8
|
def setUp(self):
|
|
9
9
|
# Use generic backend for deterministic behavior
|
|
10
|
-
self.canon = CanonRSMI(
|
|
10
|
+
self.canon = CanonRSMI(
|
|
11
|
+
backend="wl",
|
|
12
|
+
wl_iterations=5,
|
|
13
|
+
node_attrs=["element", "aromatic", "charge", "hcount", "neighbors"],
|
|
14
|
+
)
|
|
11
15
|
# Example reaction SMILES with atom-map labels
|
|
12
16
|
self.input_rsmi = "[CH3:3][CH2:5][OH:10]>>[CH2:3]=[CH2:5].[OH2:10]"
|
|
13
17
|
# After expand_aam and canonicalisation, since backend is generic,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
from synkit.Rule.Modify.molecule_rule import MoleculeRule
|
|
3
|
-
from synkit.Graph.Matcher import GraphMatcherEngine
|
|
3
|
+
from synkit.Graph.Matcher.graph_matcher import GraphMatcherEngine
|
|
4
4
|
import importlib
|
|
5
5
|
|
|
6
6
|
MOD_AVAILABLE = importlib.util.find_spec("mod") is not None
|
|
@@ -7,7 +7,8 @@ from synkit.Rule.Modify.rule_utils import (
|
|
|
7
7
|
strip_context,
|
|
8
8
|
_increment_gml_ids,
|
|
9
9
|
)
|
|
10
|
-
from synkit.Graph.Matcher import GraphMatcherEngine
|
|
10
|
+
from synkit.Graph.Matcher.graph_matcher import GraphMatcherEngine
|
|
11
|
+
from synkit.Graph.Matcher.subgraph_matcher import SubgraphMatch
|
|
11
12
|
import importlib
|
|
12
13
|
|
|
13
14
|
MOD_AVAILABLE = importlib.util.find_spec("mod") is not None
|
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# For the full list of built-in configuration values, see the documentation:
|
|
4
|
-
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
4
|
+
sys.path.insert(0, os.path.abspath(".."))
|
|
8
5
|
|
|
6
|
+
# -- Project information -----------------------------------------------------
|
|
9
7
|
project = "synkit"
|
|
10
|
-
copyright = "2025, Tieu-Long Phan"
|
|
11
8
|
author = "Tieu-Long Phan"
|
|
9
|
+
release = "0.0.8"
|
|
10
|
+
version = release
|
|
12
11
|
|
|
13
12
|
# -- General configuration ---------------------------------------------------
|
|
14
|
-
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
15
|
-
|
|
16
13
|
extensions = [
|
|
17
14
|
"sphinx.ext.autodoc",
|
|
18
15
|
"sphinx.ext.autosectionlabel",
|
|
19
16
|
"sphinx.ext.githubpages",
|
|
20
17
|
"sphinxcontrib.bibtex",
|
|
18
|
+
# "sphinx.ext.napoleon", # un-comment if using Google/NumPy docstrings
|
|
21
19
|
]
|
|
20
|
+
|
|
22
21
|
bibtex_bibfiles = ["refs.bib"]
|
|
23
22
|
templates_path = ["_templates"]
|
|
24
23
|
exclude_patterns = []
|
|
25
24
|
autosectionlabel_prefix_document = True
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
autodoc_default_options = {
|
|
27
|
+
"members": True,
|
|
28
|
+
"undoc-members": True,
|
|
29
|
+
"show-inheritance": True,
|
|
30
|
+
}
|
|
29
31
|
|
|
32
|
+
# -- Options for HTML output -------------------------------------------------
|
|
30
33
|
html_theme = "sphinx_rtd_theme"
|
|
31
34
|
html_static_path = ["_static"]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "synkit"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.9"
|
|
8
8
|
authors = [
|
|
9
9
|
{name="Tieu Long Phan", email="tieu@bioinf.uni-leipzig.de"}
|
|
10
10
|
]
|
|
@@ -30,7 +30,11 @@ dependencies = [
|
|
|
30
30
|
|
|
31
31
|
[project.optional-dependencies]
|
|
32
32
|
all = ["numpy>=2.2.0", "torch>=2.2.0"]
|
|
33
|
-
|
|
33
|
+
docs = [
|
|
34
|
+
"sphinx>=6.0",
|
|
35
|
+
"sphinx-rtd-theme",
|
|
36
|
+
"sphinxcontrib-bibtex",
|
|
37
|
+
]
|
|
34
38
|
[project.urls]
|
|
35
39
|
homepage = "https://github.com/TieuLongPhan/SynKit"
|
|
36
40
|
source = "https://github.com/TieuLongPhan/SynKit"
|
|
@@ -63,6 +63,7 @@ from networkx.algorithms.graph_hashing import (
|
|
|
63
63
|
weisfeiler_lehman_subgraph_hashes as _wl_hashes,
|
|
64
64
|
)
|
|
65
65
|
from .canon_algs import canon_morgan
|
|
66
|
+
from .nauty import NautyCanonicalizer
|
|
66
67
|
|
|
67
68
|
__all__: list[str] = ["CanonicalGraph", "GraphCanonicaliser", "CanonicalRule"]
|
|
68
69
|
|
|
@@ -145,14 +146,14 @@ class GraphCanonicaliser:
|
|
|
145
146
|
def __init__(
|
|
146
147
|
self,
|
|
147
148
|
*,
|
|
148
|
-
backend: Literal["generic", "wl", "morgan"] = "generic",
|
|
149
|
+
backend: Literal["generic", "wl", "morgan", "nauty"] = "generic",
|
|
149
150
|
wl_iterations: int = 3,
|
|
150
151
|
morgan_radius: int = 3,
|
|
151
152
|
node_attrs: List[str] = ["element", "aromatic", "charge", "hcount"],
|
|
152
153
|
node_sort_key: T_NodeSortKey = _default_node_key,
|
|
153
154
|
edge_sort_key: T_EdgeSortKey = _default_edge_key,
|
|
154
155
|
) -> None:
|
|
155
|
-
if backend not in {"generic", "wl", "morgan"}:
|
|
156
|
+
if backend not in {"generic", "wl", "morgan", "nauty"}:
|
|
156
157
|
raise ValueError("backend must be 'generic' or 'wl' or 'morgan' ")
|
|
157
158
|
self.backend: Literal["generic", "wl", "morgan"] = backend
|
|
158
159
|
self._wl_k: int = wl_iterations
|
|
@@ -161,6 +162,8 @@ class GraphCanonicaliser:
|
|
|
161
162
|
self._edge_key: T_EdgeSortKey = edge_sort_key
|
|
162
163
|
self._wl_node_attrs: Tuple[str, ...] = tuple(node_attrs)
|
|
163
164
|
self._mg_node_attrs: List[str] = node_attrs
|
|
165
|
+
if self.backend == "nauty":
|
|
166
|
+
self.nauty = NautyCanonicalizer(node_attrs=node_attrs, edge_attrs=["order"])
|
|
164
167
|
|
|
165
168
|
# ------------------------------------------------------------------ #
|
|
166
169
|
# High‑level helpers #
|
|
@@ -220,6 +223,8 @@ class GraphCanonicaliser:
|
|
|
220
223
|
return self._canon_generic(g)
|
|
221
224
|
elif self.backend == "wl":
|
|
222
225
|
return self._canon_wl(g)
|
|
226
|
+
elif self.backend == "nauty":
|
|
227
|
+
return self._canon_nauty(g)
|
|
223
228
|
else:
|
|
224
229
|
return canon_morgan(
|
|
225
230
|
g, morgan_radius=self._mg_k, node_attributes=self._mg_node_attrs
|
|
@@ -228,6 +233,9 @@ class GraphCanonicaliser:
|
|
|
228
233
|
# self._canon_generic(g) if self.backend == "generic" else self._canon_wl(g)
|
|
229
234
|
# )
|
|
230
235
|
|
|
236
|
+
def _canon_nauty(self, g: nx.Graph) -> nx.Graph:
|
|
237
|
+
return self.nauty.canonical_form(g)
|
|
238
|
+
|
|
231
239
|
def _canon_generic(self, g: nx.Graph) -> nx.Graph:
|
|
232
240
|
"""Pure attribute‑sort strategy – O(|V| log |V| + |E| log |E|)."""
|
|
233
241
|
nodes_sorted = sorted(g.nodes(data=True), key=lambda x: self._node_key(*x))
|
|
@@ -274,6 +282,7 @@ class GraphCanonicaliser:
|
|
|
274
282
|
wl_hash = _wl_hashes(
|
|
275
283
|
g2,
|
|
276
284
|
node_attr=node_attr,
|
|
285
|
+
edge_attr="order",
|
|
277
286
|
iterations=self._wl_k,
|
|
278
287
|
)
|
|
279
288
|
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
import hashlib
|
|
3
|
+
from synkit.IO import setup_logging
|
|
4
|
+
|
|
5
|
+
logger = setup_logging()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NautyCanonicalizer:
|
|
9
|
+
__slots__ = ("node_attrs", "edge_attrs")
|
|
10
|
+
|
|
11
|
+
def __init__(self, node_attrs=None, edge_attrs=None):
|
|
12
|
+
self.node_attrs = list(node_attrs) if node_attrs else []
|
|
13
|
+
self.edge_attrs = list(edge_attrs) if edge_attrs else []
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def _freeze(x):
|
|
17
|
+
if isinstance(x, list):
|
|
18
|
+
return tuple(NautyCanonicalizer._freeze(v) for v in x)
|
|
19
|
+
if isinstance(x, dict):
|
|
20
|
+
return frozenset(
|
|
21
|
+
(k, NautyCanonicalizer._freeze(v)) for k, v in sorted(x.items())
|
|
22
|
+
)
|
|
23
|
+
return x
|
|
24
|
+
|
|
25
|
+
def canonical_form(
|
|
26
|
+
self,
|
|
27
|
+
G,
|
|
28
|
+
return_aut=False,
|
|
29
|
+
remap_aut=False,
|
|
30
|
+
return_orbits=False,
|
|
31
|
+
return_perm=False,
|
|
32
|
+
max_depth=None,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Compute canonical form of graph G with optional automorphisms, orbits, and early stopping.
|
|
36
|
+
|
|
37
|
+
:param G: NetworkX graph to canonicalize.
|
|
38
|
+
:param return_aut: bool, whether to return list of automorphism permutations.
|
|
39
|
+
Default: False.
|
|
40
|
+
:param remap_aut: bool, whether to remap automorphisms to canonical labels
|
|
41
|
+
(only valid if return_aut=True). Default: False.
|
|
42
|
+
:param return_orbits: bool, whether to return node orbits (symmetry groups). Default: False.
|
|
43
|
+
:param return_perm: bool, whether to return canonical permutation (ordering of nodes). Default: False.
|
|
44
|
+
:param max_depth: int or None, max recursion depth for backtracking search (early stopping).
|
|
45
|
+
Default: None (unlimited).
|
|
46
|
+
:return: tuple containing requested results and a boolean early_stop flag indicating if search terminated early.
|
|
47
|
+
The order of outputs is (G_canon, perm?, automorphisms?, orbits?, early_stop).
|
|
48
|
+
"""
|
|
49
|
+
logger.debug(
|
|
50
|
+
f"Starting canonical_form: max_depth={max_depth},"
|
|
51
|
+
+ f" return_aut={return_aut}, remap_aut={remap_aut},"
|
|
52
|
+
+ f" return_orbits={return_orbits}, return_perm={return_perm}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
best = {"label": None, "perm": None}
|
|
56
|
+
aut_perms = []
|
|
57
|
+
|
|
58
|
+
initial_partition = self._initial_partition(G)
|
|
59
|
+
logger.debug(f"Initial partition: {initial_partition}")
|
|
60
|
+
|
|
61
|
+
early_stop_occurred = self._search(
|
|
62
|
+
G, initial_partition, [], best, aut_perms, depth=0, max_depth=max_depth
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
perm = best["perm"]
|
|
66
|
+
if perm is None:
|
|
67
|
+
logger.error(
|
|
68
|
+
f"Canonical form not found: search stopped early (max_depth={max_depth} too small)."
|
|
69
|
+
)
|
|
70
|
+
raise RuntimeError(
|
|
71
|
+
f"Canonical form not found: search stopped early (max_depth={max_depth} too small)."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
mapping = {v: i + 1 for i, v in enumerate(perm)}
|
|
75
|
+
G_can = nx.relabel_nodes(G, mapping, copy=True)
|
|
76
|
+
self._update_atom_map(G_can)
|
|
77
|
+
|
|
78
|
+
results = [G_can]
|
|
79
|
+
if return_perm:
|
|
80
|
+
results.append(perm)
|
|
81
|
+
|
|
82
|
+
if return_aut:
|
|
83
|
+
if remap_aut:
|
|
84
|
+
remapped = [[mapping[v] for v in p] for p in aut_perms]
|
|
85
|
+
results.append(remapped)
|
|
86
|
+
else:
|
|
87
|
+
results.append(aut_perms)
|
|
88
|
+
|
|
89
|
+
if return_orbits:
|
|
90
|
+
orbits = self.compute_orbits(aut_perms)
|
|
91
|
+
if remap_aut and return_aut:
|
|
92
|
+
orbits = [set(mapping[v] for v in orbit) for orbit in orbits]
|
|
93
|
+
results.append(orbits)
|
|
94
|
+
|
|
95
|
+
results.append(early_stop_occurred)
|
|
96
|
+
|
|
97
|
+
logger.debug(
|
|
98
|
+
f"canonical_form completed, early_stop_occurred={early_stop_occurred}"
|
|
99
|
+
)
|
|
100
|
+
return tuple(results) if len(results) > 2 else results[0]
|
|
101
|
+
|
|
102
|
+
def _update_atom_map(self, G):
|
|
103
|
+
for n in G.nodes():
|
|
104
|
+
G.nodes[n]["atom_map"] = n
|
|
105
|
+
|
|
106
|
+
def _initial_partition(self, G):
|
|
107
|
+
if not self.node_attrs:
|
|
108
|
+
return [sorted(G.nodes())]
|
|
109
|
+
buckets = {}
|
|
110
|
+
for v in G.nodes():
|
|
111
|
+
key = tuple(
|
|
112
|
+
self._freeze(G.nodes[v].get(attr, None)) for attr in self.node_attrs
|
|
113
|
+
)
|
|
114
|
+
buckets.setdefault(key, []).append(v)
|
|
115
|
+
return [sorted(nodes) for _, nodes in sorted(buckets.items())]
|
|
116
|
+
|
|
117
|
+
def _node_signature(self, G, v, partition):
|
|
118
|
+
node_attrs = tuple(
|
|
119
|
+
self._freeze(G.nodes[v].get(a, None)) for a in self.node_attrs
|
|
120
|
+
)
|
|
121
|
+
degree = G.degree[v]
|
|
122
|
+
|
|
123
|
+
nbr_part_counts = []
|
|
124
|
+
for cell in partition:
|
|
125
|
+
count = sum(1 for nbr in G.neighbors(v) if nbr in cell)
|
|
126
|
+
nbr_part_counts.append(count)
|
|
127
|
+
nbr_part_counts = tuple(nbr_part_counts)
|
|
128
|
+
|
|
129
|
+
edge_attr_multiset = []
|
|
130
|
+
for nbr in G.neighbors(v):
|
|
131
|
+
attrs = G[v][nbr]
|
|
132
|
+
edge_attrs = []
|
|
133
|
+
for a in self.edge_attrs:
|
|
134
|
+
val = attrs.get(a, None)
|
|
135
|
+
if a == "order" and isinstance(val, tuple):
|
|
136
|
+
val = tuple(sorted(round(float(x), 3) for x in val))
|
|
137
|
+
edge_attrs.append(self._freeze(val))
|
|
138
|
+
edge_attr_multiset.append(tuple(edge_attrs))
|
|
139
|
+
edge_attr_multiset = tuple(sorted(edge_attr_multiset))
|
|
140
|
+
|
|
141
|
+
return (node_attrs, degree, nbr_part_counts, edge_attr_multiset)
|
|
142
|
+
|
|
143
|
+
def _refine(self, G, partition):
|
|
144
|
+
changed = True
|
|
145
|
+
while changed:
|
|
146
|
+
changed = False
|
|
147
|
+
new_partition = []
|
|
148
|
+
sig_cache = {}
|
|
149
|
+
for cell in partition:
|
|
150
|
+
if len(cell) <= 1:
|
|
151
|
+
new_partition.append(cell)
|
|
152
|
+
continue
|
|
153
|
+
sigs = {}
|
|
154
|
+
for v in cell:
|
|
155
|
+
if v not in sig_cache:
|
|
156
|
+
sig_cache[v] = self._node_signature(G, v, partition)
|
|
157
|
+
sig = sig_cache[v]
|
|
158
|
+
sigs.setdefault(sig, []).append(v)
|
|
159
|
+
if len(sigs) > 1:
|
|
160
|
+
changed = True
|
|
161
|
+
for sig in sorted(sigs):
|
|
162
|
+
new_partition.append(sorted(sigs[sig]))
|
|
163
|
+
else:
|
|
164
|
+
new_partition.append(cell)
|
|
165
|
+
partition = new_partition
|
|
166
|
+
return partition
|
|
167
|
+
|
|
168
|
+
def _search(self, G, partition, prefix, best, aut_perms, depth=0, max_depth=None):
|
|
169
|
+
if max_depth is not None and depth > max_depth:
|
|
170
|
+
logger.debug(
|
|
171
|
+
f"Early stopping at depth {depth} due to max_depth={max_depth}"
|
|
172
|
+
)
|
|
173
|
+
return True # early stop triggered
|
|
174
|
+
|
|
175
|
+
partition = self._refine(G, partition)
|
|
176
|
+
if all(len(c) == 1 for c in partition):
|
|
177
|
+
perm = prefix + [v for c in partition for v in c]
|
|
178
|
+
label = self._build_label(G, perm)
|
|
179
|
+
if best["label"] is None or label < best["label"]:
|
|
180
|
+
best["label"], best["perm"] = label, perm
|
|
181
|
+
aut_perms.clear()
|
|
182
|
+
aut_perms.append(perm)
|
|
183
|
+
logger.debug(f"New best label found at depth {depth}")
|
|
184
|
+
elif label == best["label"]:
|
|
185
|
+
aut_perms.append(perm)
|
|
186
|
+
logger.debug(f"Equivalent label found at depth {depth}")
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
idx = next(i for i, c in enumerate(partition) if len(c) > 1)
|
|
190
|
+
cell = partition[idx]
|
|
191
|
+
sorted_cell = sorted(cell, key=lambda n: G.nodes[n].get("atom_map", n))
|
|
192
|
+
|
|
193
|
+
for v in sorted_cell:
|
|
194
|
+
rest = [w for w in cell if w != v]
|
|
195
|
+
# fmt: off
|
|
196
|
+
new_partition = (
|
|
197
|
+
partition[:idx]
|
|
198
|
+
+ [[v]]
|
|
199
|
+
+ ([sorted(rest)] if rest else [])
|
|
200
|
+
+ partition[idx + 1:]
|
|
201
|
+
)
|
|
202
|
+
# fmt: on
|
|
203
|
+
candidate_prefix = prefix + [v]
|
|
204
|
+
|
|
205
|
+
partial_label = self._build_partial_label(G, candidate_prefix)
|
|
206
|
+
|
|
207
|
+
if best["label"] is not None and partial_label > best["label"]:
|
|
208
|
+
logger.debug(f"Pruning branch at depth {depth} due to partial label")
|
|
209
|
+
continue # prune branch early
|
|
210
|
+
|
|
211
|
+
if self._search(
|
|
212
|
+
G,
|
|
213
|
+
new_partition,
|
|
214
|
+
candidate_prefix,
|
|
215
|
+
best,
|
|
216
|
+
aut_perms,
|
|
217
|
+
depth=depth + 1,
|
|
218
|
+
max_depth=max_depth,
|
|
219
|
+
):
|
|
220
|
+
return True # propagate early stop upward
|
|
221
|
+
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def _build_label(self, G, perm):
|
|
225
|
+
node_segment = "|".join(
|
|
226
|
+
":".join(
|
|
227
|
+
str(self._freeze(G.nodes[v].get(attr, ""))) for attr in self.node_attrs
|
|
228
|
+
)
|
|
229
|
+
for v in perm
|
|
230
|
+
)
|
|
231
|
+
n = len(perm)
|
|
232
|
+
edge_bits = []
|
|
233
|
+
for i in range(n):
|
|
234
|
+
vi = perm[i]
|
|
235
|
+
for j in range(i + 1, n):
|
|
236
|
+
vj = perm[j]
|
|
237
|
+
if G.has_edge(vi, vj):
|
|
238
|
+
attrs = G[vi][vj]
|
|
239
|
+
frozen_attrs = tuple(
|
|
240
|
+
self._freeze(attrs.get(a, "")) for a in self.edge_attrs
|
|
241
|
+
)
|
|
242
|
+
edge_bits.append("1:" + ":".join(str(x) for x in frozen_attrs))
|
|
243
|
+
else:
|
|
244
|
+
edge_bits.append("0:" + ":".join("" for _ in self.edge_attrs))
|
|
245
|
+
edge_segment = "|".join(edge_bits)
|
|
246
|
+
return node_segment + "||" + edge_segment
|
|
247
|
+
|
|
248
|
+
def _build_partial_label(self, G, prefix):
|
|
249
|
+
node_segment = "|".join(
|
|
250
|
+
":".join(
|
|
251
|
+
str(self._freeze(G.nodes[v].get(attr, ""))) for attr in self.node_attrs
|
|
252
|
+
)
|
|
253
|
+
for v in prefix
|
|
254
|
+
)
|
|
255
|
+
suffix = "{" * 1000 # lexicographically larger than any label char
|
|
256
|
+
return node_segment + suffix
|
|
257
|
+
|
|
258
|
+
def compute_orbits(self, aut_perms):
|
|
259
|
+
if not aut_perms:
|
|
260
|
+
return []
|
|
261
|
+
|
|
262
|
+
orbit_map = {}
|
|
263
|
+
orbits = []
|
|
264
|
+
|
|
265
|
+
def union_orbits(i, j):
|
|
266
|
+
if i == j:
|
|
267
|
+
return
|
|
268
|
+
o1 = orbits[i]
|
|
269
|
+
o2 = orbits[j]
|
|
270
|
+
if len(o1) < len(o2):
|
|
271
|
+
i, j = j, i
|
|
272
|
+
o1, o2 = o2, o1
|
|
273
|
+
o1.update(o2)
|
|
274
|
+
orbits[j] = set()
|
|
275
|
+
for v in o2:
|
|
276
|
+
orbit_map[v] = i
|
|
277
|
+
|
|
278
|
+
first_perm = aut_perms[0]
|
|
279
|
+
for idx, node in enumerate(first_perm):
|
|
280
|
+
orbit_map[node] = idx
|
|
281
|
+
orbits.append({node})
|
|
282
|
+
|
|
283
|
+
for perm in aut_perms:
|
|
284
|
+
for idx, node in enumerate(perm):
|
|
285
|
+
union_orbits(idx, orbit_map[node])
|
|
286
|
+
|
|
287
|
+
return [o for o in orbits if o]
|
|
288
|
+
|
|
289
|
+
def graph_signature(self, G):
|
|
290
|
+
G_canon = self.canonical_form(G)
|
|
291
|
+
label = self._build_label(G_canon, sorted(G_canon.nodes()))
|
|
292
|
+
return hashlib.sha256(label.encode("utf-8")).hexdigest()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from copy import copy
|
|
2
2
|
import networkx as nx
|
|
3
3
|
from operator import eq
|
|
4
|
-
from typing import List,
|
|
4
|
+
from typing import List, Set, Any, Tuple
|
|
5
5
|
from networkx.algorithms.isomorphism import generic_node_match, generic_edge_match
|
|
6
6
|
|
|
7
7
|
from synkit.Graph.Feature.graph_descriptors import GraphDescriptor
|
|
@@ -91,7 +91,22 @@ def h_to_implicit(G: nx.Graph) -> nx.Graph:
|
|
|
91
91
|
return H2
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
def
|
|
94
|
+
def normalize_edge_orders(G: nx.Graph) -> None:
|
|
95
|
+
"""
|
|
96
|
+
In-place normalize all edge attributes in G:
|
|
97
|
+
- If 'order' is a float or int, replace it with (order, order).
|
|
98
|
+
- If 'standard_order' is missing, set it to 0.0.
|
|
99
|
+
"""
|
|
100
|
+
for _, _, data in G.edges(data=True):
|
|
101
|
+
o = data.get("order")
|
|
102
|
+
# Wrap scalar orders into tuples
|
|
103
|
+
if isinstance(o, (int, float)):
|
|
104
|
+
data["order"] = (float(o), float(o))
|
|
105
|
+
if "standard_order" not in data:
|
|
106
|
+
data["standard_order"] = 0.0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def h_to_explicit(G: nx.Graph, nodes: List[int] = None, its: bool = False) -> nx.Graph:
|
|
95
110
|
"""
|
|
96
111
|
Convert implicit hydrogen counts on heavy atoms into explicit hydrogen nodes.
|
|
97
112
|
|
|
@@ -146,6 +161,8 @@ def h_to_explicit(G: nx.Graph, nodes: List[int] = None) -> nx.Graph:
|
|
|
146
161
|
tgh_list = [list(row) for row in tgh]
|
|
147
162
|
tgh_list[0][2] -= count # Assume hcount is stored at position [0][2]
|
|
148
163
|
H2.nodes[heavy]["typesGH"] = tuple(tuple(row) for row in tgh_list)
|
|
164
|
+
if its:
|
|
165
|
+
normalize_edge_orders(H2)
|
|
149
166
|
|
|
150
167
|
return H2
|
|
151
168
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# from .graph_matcher import GraphMatcherEngine
|
|
2
|
+
# from .subgraph_matcher import SubgraphMatch, SubgraphSearchEngine
|
|
3
|
+
# from .graph_cluster import GraphCluster
|
|
4
|
+
|
|
5
|
+
# __all__ = [
|
|
6
|
+
# "GraphMatcherEngine",
|
|
7
|
+
# "SubgraphMatch",
|
|
8
|
+
# "SubgraphSearchEngine",
|
|
9
|
+
# "GraphCluster",
|
|
10
|
+
# ]
|