synkit 1.6.5__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- synkit/CRN/Benchmark/__init__.py +53 -0
- synkit/CRN/Benchmark/crosschecks.py +13 -0
- synkit/CRN/Benchmark/kegg.py +12 -0
- synkit/CRN/Benchmark/networks.py +13 -0
- synkit/CRN/Benchmark/scaling.py +13 -0
- synkit/CRN/Benchmark/validate.py +13 -0
- synkit/CRN/Construct/__init__.py +16 -0
- synkit/CRN/Construct/abstract.py +668 -0
- synkit/CRN/Construct/arity.py +36 -0
- synkit/CRN/Construct/builder.py +619 -0
- synkit/CRN/Construct/derivation.py +63 -0
- synkit/CRN/Construct/flattener.py +100 -0
- synkit/CRN/Construct/keys.py +14 -0
- synkit/CRN/Construct/mixtures.py +117 -0
- synkit/CRN/Construct/rdkit_worker.py +69 -0
- synkit/CRN/Construct/smiles.py +99 -0
- synkit/CRN/Construct/state.py +31 -0
- synkit/CRN/Construct/strategy.py +75 -0
- synkit/CRN/Construct/worker.py +42 -0
- synkit/CRN/IO/__init__.py +36 -0
- synkit/CRN/IO/sbml.py +925 -0
- synkit/CRN/Pathway/__init__.py +72 -0
- synkit/CRN/Pathway/_adapter.py +333 -0
- synkit/CRN/Pathway/pathfinder.py +500 -0
- synkit/CRN/Pathway/reachability.py +796 -0
- synkit/CRN/Pathway/realizability.py +1322 -0
- synkit/CRN/Petrinet/__init__.py +93 -0
- synkit/CRN/Petrinet/analyzer.py +486 -0
- synkit/CRN/Petrinet/minimal_semiflows.py +436 -0
- synkit/CRN/Petrinet/net.py +1041 -0
- synkit/CRN/Petrinet/persistence.py +97 -0
- synkit/CRN/Petrinet/semiflows.py +483 -0
- synkit/CRN/Petrinet/siphon_search.py +402 -0
- synkit/CRN/Petrinet/structure.py +286 -0
- synkit/CRN/Props/__init__.py +130 -0
- synkit/CRN/Props/_rank.py +29 -0
- synkit/CRN/Props/deficiency.py +953 -0
- synkit/CRN/Props/dynamics.py +832 -0
- synkit/CRN/Props/helper.py +151 -0
- synkit/CRN/Props/stoich.py +751 -0
- synkit/CRN/Props/thermo.py +584 -0
- synkit/CRN/Query/__init__.py +66 -0
- synkit/CRN/Query/cache.py +41 -0
- synkit/CRN/Query/kegg_api.py +99 -0
- synkit/CRN/Query/kegg_extract.py +714 -0
- synkit/CRN/Query/kegg_impute.py +707 -0
- synkit/CRN/Query/kegg_parse.py +480 -0
- synkit/CRN/Query/to_syncrn.py +290 -0
- synkit/CRN/README.md +234 -0
- synkit/CRN/Structure/__init__.py +12 -0
- synkit/CRN/Structure/_graph_io.py +713 -0
- synkit/CRN/Structure/_matrices.py +88 -0
- synkit/CRN/Structure/_parse.py +544 -0
- synkit/CRN/Structure/reaction.py +237 -0
- synkit/CRN/Structure/rule.py +55 -0
- synkit/CRN/Structure/species.py +46 -0
- synkit/CRN/Structure/syncrn.py +929 -0
- synkit/CRN/Symmetry/__init__.py +20 -0
- synkit/CRN/Symmetry/_common.py +1059 -0
- synkit/CRN/Symmetry/_ir.py +940 -0
- synkit/CRN/Symmetry/automorphism.py +472 -0
- synkit/CRN/Symmetry/canon.py +441 -0
- synkit/CRN/Symmetry/isomorphism.py +412 -0
- synkit/CRN/Symmetry/symmetry.py +61 -0
- synkit/CRN/Symmetry/wl_canon.py +967 -0
- synkit/CRN/Visualize/__init__.py +11 -0
- synkit/CRN/Visualize/labels.py +113 -0
- synkit/CRN/Visualize/layout.py +708 -0
- synkit/CRN/Visualize/palette.py +292 -0
- synkit/CRN/Visualize/validation.py +122 -0
- synkit/CRN/Visualize/vis.py +1152 -0
- synkit/CRN/__init__.py +233 -0
- synkit/CRN/_build.py +50 -0
- synkit/CRN/_native_build.py +175 -0
- synkit/CRN/analysis.py +629 -0
- synkit/CRN/backend.py +74 -0
- synkit/CRN/cpp/CMakeLists.txt +58 -0
- synkit/CRN/cpp/algebra.cpp +46 -0
- synkit/CRN/cpp/closure.cpp +29 -0
- synkit/CRN/cpp/exact.h +55 -0
- synkit/CRN/cpp/pathway.cpp +46 -0
- synkit/CRN/cpp/semiflows.cpp +209 -0
- synkit/CRN/cpp/structural.cpp +240 -0
- synkit/CRN/cpp/symmetry.cpp +45 -0
- synkit/CRN/cpp/write_manifest.cmake +28 -0
- synkit/CRN/kinds.py +137 -0
- synkit/CRN/lib/synkit_crn.dll +0 -0
- synkit/CRN/lib/synkit_crn.json +33 -0
- synkit/CRN/native.py +388 -0
- synkit/CRN/native_analysis.py +459 -0
- synkit/Chem/Cluster/__init__.py +0 -0
- synkit/Chem/Cluster/butina.py +140 -0
- synkit/Chem/Fingerprint/__init__.py +0 -0
- synkit/Chem/Fingerprint/fp_calculator.py +155 -0
- synkit/Chem/Fingerprint/smiles_featurizer.py +258 -0
- synkit/Chem/Fingerprint/transformation_fp.py +139 -0
- synkit/Chem/Mapper/README.rst +43 -0
- synkit/Chem/Mapper/__init__.py +255 -0
- synkit/Chem/Mapper/_compat.py +130 -0
- synkit/Chem/Mapper/adapters/__init__.py +1 -0
- synkit/Chem/Mapper/adapters/prediction.py +126 -0
- synkit/Chem/Mapper/adapters/template.py +412 -0
- synkit/Chem/Mapper/chem/__init__.py +52 -0
- synkit/Chem/Mapper/chem/aam.py +673 -0
- synkit/Chem/Mapper/chem/blind.py +160 -0
- synkit/Chem/Mapper/chem/its.py +309 -0
- synkit/Chem/Mapper/chem/smiles.py +564 -0
- synkit/Chem/Mapper/graph/__init__.py +66 -0
- synkit/Chem/Mapper/graph/automorphism.py +395 -0
- synkit/Chem/Mapper/graph/blockcut.py +98 -0
- synkit/Chem/Mapper/graph/labeled_graph.py +104 -0
- synkit/Chem/Mapper/graph/refine.py +176 -0
- synkit/Chem/Mapper/graph/synkit_adapter.py +110 -0
- synkit/Chem/Mapper/io/__init__.py +13 -0
- synkit/Chem/Mapper/io/idxmapstr.py +82 -0
- synkit/Chem/Mapper/mapper.py +685 -0
- synkit/Chem/Mapper/reaction_search.py +554 -0
- synkit/Chem/Mapper/reporting/__init__.py +1 -0
- synkit/Chem/Mapper/reporting/alternatives.py +451 -0
- synkit/Chem/Mapper/reporting/analysis.py +681 -0
- synkit/Chem/Mapper/reporting/native_analysis.py +485 -0
- synkit/Chem/Mapper/reporting/spectrum.py +642 -0
- synkit/Chem/Mapper/solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/__init__.py +141 -0
- synkit/Chem/Mapper/solvers/exact/assignment/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_certificate.py +113 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_domains.py +162 -0
- synkit/Chem/Mapper/solvers/exact/assignment/assignment_verification.py +58 -0
- synkit/Chem/Mapper/solvers/exact/assignment/incremental_assignment.py +247 -0
- synkit/Chem/Mapper/solvers/exact/certificate.py +128 -0
- synkit/Chem/Mapper/solvers/exact/core.py +183 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/bounds.py +319 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/exhaustive.py +179 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/inputs.py +283 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/records.py +242 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/search.py +1025 -0
- synkit/Chem/Mapper/solvers/exact/distance_search/verify.py +378 -0
- synkit/Chem/Mapper/solvers/exact/edit_support.py +375 -0
- synkit/Chem/Mapper/solvers/exact/hybrid.py +224 -0
- synkit/Chem/Mapper/solvers/exact/hydrogen.py +264 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/branching.py +499 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumerate.py +1050 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/enumeration_result.py +30 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/kernel.py +256 -0
- synkit/Chem/Mapper/solvers/exact/kernel_solvers/milp.py +194 -0
- synkit/Chem/Mapper/solvers/exact/native/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/native/build.py +154 -0
- synkit/Chem/Mapper/solvers/exact/native/candidates.py +593 -0
- synkit/Chem/Mapper/solvers/exact/native/canonical.py +396 -0
- synkit/Chem/Mapper/solvers/exact/native/disk_frontier.py +415 -0
- synkit/Chem/Mapper/solvers/exact/native/frontier.py +390 -0
- synkit/Chem/Mapper/solvers/exact/native/hydrogen_quotient.py +213 -0
- synkit/Chem/Mapper/solvers/exact/native/its.py +528 -0
- synkit/Chem/Mapper/solvers/exact/native/native_distance.cpp +2803 -0
- synkit/Chem/Mapper/solvers/exact/native/orbit_aggregation.py +328 -0
- synkit/Chem/Mapper/solvers/exact/native/parallel.py +205 -0
- synkit/Chem/Mapper/solvers/exact/native/pmcd.py +203 -0
- synkit/Chem/Mapper/solvers/exact/native/runtime.py +40 -0
- synkit/Chem/Mapper/solvers/exact/native/search.py +294 -0
- synkit/Chem/Mapper/solvers/exact/native/separator.py +224 -0
- synkit/Chem/Mapper/solvers/exact/native_build.py +12 -0
- synkit/Chem/Mapper/solvers/exact/pabs/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/pabs/cost_lattice.py +43 -0
- synkit/Chem/Mapper/solvers/exact/pabs/factor_spectrum.py +110 -0
- synkit/Chem/Mapper/solvers/exact/pabs/multi_shell.py +368 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation.py +679 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_bounds.py +436 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_limits.py +13 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_search.py +856 -0
- synkit/Chem/Mapper/solvers/exact/pabs/propagation_symmetry.py +66 -0
- synkit/Chem/Mapper/solvers/exact/pabs/residual_typed_bonds.py +136 -0
- synkit/Chem/Mapper/solvers/exact/pabs/reward_frontier.py +201 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_bound.py +258 -0
- synkit/Chem/Mapper/solvers/exact/pabs/separator_spectrum.py +308 -0
- synkit/Chem/Mapper/solvers/exact/pabs/sparse_reward_bound.py +207 -0
- synkit/Chem/Mapper/solvers/exact/pabs/suffix_spectrum.py +299 -0
- synkit/Chem/Mapper/solvers/exact/search.py +55 -0
- synkit/Chem/Mapper/solvers/exact/seeds/__init__.py +1 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed.py +104 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_fragments.py +148 -0
- synkit/Chem/Mapper/solvers/exact/seeds/seed_relaxation.py +181 -0
- synkit/Chem/Mapper/solvers/exact/symmetry.py +223 -0
- synkit/Chem/Mapper/solvers/native/__init__.py +4 -0
- synkit/Chem/Mapper/solvers/native/assignment.py +99 -0
- synkit/Chem/Mapper/solvers/native/astar.py +22 -0
- synkit/Chem/Mapper/solvers/native/build.py +385 -0
- synkit/Chem/Mapper/solvers/native/classification.py +641 -0
- synkit/Chem/Mapper/solvers/native/core.py +378 -0
- synkit/Chem/Mapper/solvers/native/cpp/CMakeLists.txt +120 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/map_reaction.cpp +29 -0
- synkit/Chem/Mapper/solvers/native/cpp/examples/slap_graph.cpp +63 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/assignment/assignment.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/astar.h +18 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/astar/bound.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/pmcd_witness.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/core/runtime.h +130 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/hydrogen/hydrogen.h +73 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mapper.h +175 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/mces/mces.h +9 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/pabs/seed.h +34 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/reaction/reaction.h +30 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/sepdp/sepdp.h +21 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/slap.h +72 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/solvers/solvers.h +16 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/classification.h +140 -0
- synkit/Chem/Mapper/solvers/native/cpp/include/synkit_mapper/symmetry/cpabs.h +33 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/assignment/abb.cpp +302 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/CMakeLists.txt +12 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/README.md +47 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar.cpp +228 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/astar_graph.cpp +37 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/astar/bound.cpp +212 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/dispatch.cpp +272 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/core/pmcd_witness.cpp +36 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/hydrogen/hydrogen.cpp +380 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/README.md +57 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/mces/mces.cpp +287 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/bridge.cpp +334 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/chemical_floor.inc +181 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/endpoint_symmetry.inc +104 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/exact_helpers.h +20 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_assignment_certificate.h +165 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_certificate.h +125 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_model.h +365 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_provider.inc +319 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_rounding.inc +170 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/lp_test_hooks.h +342 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/objective_reduction.inc +137 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/overlap_dual.inc +351 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/profile_seed.inc +214 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/seed_heuristics.inc +640 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_embedding.cpp +430 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/sparse_root_lp.h +176 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/pabs/test_hooks.inc +262 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/reaction/reaction.cpp +611 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/README.md +61 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/sepdp/sepdp.cpp +253 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/slap.cpp +810 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/formulation.h +138 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/maxsat.cpp +526 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/ortools.cpp +475 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/seed.h +80 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/solvers/weighted_cnf.h +256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/README.md +43 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/dfs.cpp +123 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/supporting/edit_support.cpp +224 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification.cpp +553 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_pipeline.cpp +124 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/classification_stream.h +13 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/cpabs.cpp +455 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/edge_deck.cpp +743 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naugraph.c +732 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.c +94 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naurng.h +49 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.c +1771 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nausparse.h +133 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nautil.c +750 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/naututil.h +355 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.c +1256 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/nauty.h +1544 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.c +1131 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/schreier.h +70 -0
- synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/sorttemplates.c +580 -0
- synkit/Chem/Mapper/solvers/native/hydrogen.py +711 -0
- synkit/Chem/Mapper/solvers/native/implicit.py +506 -0
- synkit/Chem/Mapper/solvers/native/implicit_certificate.py +187 -0
- synkit/Chem/Mapper/solvers/native/implicit_preparation.py +309 -0
- synkit/Chem/Mapper/solvers/native/lib/synkit_mapper.dll +0 -0
- synkit/Chem/Mapper/solvers/native/mces.py +16 -0
- synkit/Chem/Mapper/solvers/native/reaction.py +179 -0
- synkit/Chem/Mapper/solvers/native/reaction_build.py +277 -0
- synkit/Chem/Mapper/solvers/native/reaction_text.py +26 -0
- synkit/Chem/Mapper/solvers/native/runtime.py +242 -0
- synkit/Chem/Mapper/solvers/native/sepdp.py +47 -0
- synkit/Chem/Mapper/solvers/native/slap.py +266 -0
- synkit/Chem/Mapper/solvers/native/solvers.py +67 -0
- synkit/Chem/Mapper/solvers/native/stereo.py +86 -0
- synkit/Chem/Mapper/solvers/native/supporting.py +34 -0
- synkit/Chem/Mapper/solvers/native/symmetry.py +91 -0
- synkit/Chem/Mapper/solvers/slap/__init__.py +29 -0
- synkit/Chem/Mapper/solvers/slap/lap.py +174 -0
- synkit/Chem/Mapper/solvers/slap/sequential.py +696 -0
- synkit/Chem/Mapper/validation/__init__.py +1 -0
- synkit/Chem/Mapper/validation/aam_validator.py +442 -0
- synkit/Chem/Mapper/validation/annotations.py +274 -0
- synkit/Chem/Mapper/validation/bonds.py +124 -0
- synkit/Chem/Mapper/validation/cohorts.py +72 -0
- synkit/Chem/Mapper/validation/identifiability.py +150 -0
- synkit/Chem/Mapper/validation/numerical_scope.py +36 -0
- synkit/Chem/Mapper/validation/orbits.py +137 -0
- synkit/Chem/Molecule/__init__.py +0 -0
- synkit/Chem/Molecule/_chirality_loci.py +165 -0
- synkit/Chem/Molecule/_chirality_types.py +231 -0
- synkit/Chem/Molecule/_helical_loci.py +167 -0
- synkit/Chem/Molecule/_stereo_axis_evidence.py +202 -0
- synkit/Chem/Molecule/_stereo_element_detection.py +193 -0
- synkit/Chem/Molecule/_stereo_orientation_constraints.py +511 -0
- synkit/Chem/Molecule/_tetrahedral_carriers.py +63 -0
- synkit/Chem/Molecule/atom_features.py +379 -0
- synkit/Chem/Molecule/chirality.py +1041 -0
- synkit/Chem/Molecule/cip_assignment.py +569 -0
- synkit/Chem/Molecule/cip_ranking.py +843 -0
- synkit/Chem/Molecule/coordinate_stereo.py +503 -0
- synkit/Chem/Molecule/derived_stereo_naming.py +165 -0
- synkit/Chem/Molecule/descriptors.py +452 -0
- synkit/Chem/Molecule/formula.py +249 -0
- synkit/Chem/Molecule/global_stereo.py +175 -0
- synkit/Chem/Molecule/graph_annotator.py +300 -0
- synkit/Chem/Molecule/standardize.py +817 -0
- synkit/Chem/Molecule/stereo_evidence.py +179 -0
- synkit/Chem/Molecule/stereo_perception.py +1032 -0
- synkit/Chem/Molecule/valence.py +123 -0
- synkit/Chem/Reaction/Mapper/__init__.py +44 -0
- synkit/Chem/Reaction/__init__.py +20 -0
- synkit/Chem/Reaction/aam_validator.py +5 -0
- synkit/Chem/Reaction/balance_check.py +138 -0
- synkit/Chem/Reaction/canon_rsmi.py +249 -0
- synkit/Chem/Reaction/cleaning.py +58 -0
- synkit/Chem/Reaction/deionize.py +199 -0
- synkit/Chem/Reaction/explicit_h_audit.py +232 -0
- synkit/Chem/Reaction/fix_aam.py +64 -0
- synkit/Chem/Reaction/neutralize.py +207 -0
- synkit/Chem/Reaction/radical_wildcard.py +224 -0
- synkit/Chem/Reaction/standardize.py +203 -0
- synkit/Chem/Reaction/tautomerize.py +205 -0
- synkit/Chem/__init__.py +1 -0
- synkit/Chem/utils.py +315 -0
- synkit/Data/__init__.py +0 -0
- synkit/Data/aldol.json.gz +1 -0
- synkit/Data/gen_partial_aam.py +147 -0
- synkit/Data/paracetamol.json.gz +1 -0
- synkit/Graph/Canon/__init__.py +16 -0
- synkit/Graph/Canon/_native/__init__.py +13 -0
- synkit/Graph/Canon/_native/build.py +93 -0
- synkit/Graph/Canon/_native/lib/synkit_canon.dll +0 -0
- synkit/Graph/Canon/_native/prepare.py +49 -0
- synkit/Graph/Canon/_native/runtime.py +140 -0
- synkit/Graph/Canon/_native/search.py +137 -0
- synkit/Graph/Canon/canon_algs.py +228 -0
- synkit/Graph/Canon/canon_graph.py +531 -0
- synkit/Graph/Canon/cpp/CMakeLists.txt +42 -0
- synkit/Graph/Canon/cpp/README.md +123 -0
- synkit/Graph/Canon/cpp/examples/canonical_graph.cpp +16 -0
- synkit/Graph/Canon/cpp/include/synkit_canon/canon.h +85 -0
- synkit/Graph/Canon/cpp/src/canonical.cpp +391 -0
- synkit/Graph/Canon/cpp/src/symmetry.h +83 -0
- synkit/Graph/Canon/cpp/tests/symmetry.cpp +93 -0
- synkit/Graph/Canon/exact.py +1020 -0
- synkit/Graph/Canon/exact_refinement.py +368 -0
- synkit/Graph/Canon/nauty.py +143 -0
- synkit/Graph/Context/__init__.py +0 -0
- synkit/Graph/Context/hier_context.py +244 -0
- synkit/Graph/Context/radius_expand.py +252 -0
- synkit/Graph/FG/__init__.py +19 -0
- synkit/Graph/FG/api.py +39 -0
- synkit/Graph/FG/audit.py +155 -0
- synkit/Graph/FG/catalog.py +919 -0
- synkit/Graph/FG/detector.py +288 -0
- synkit/Graph/FG/model.py +97 -0
- synkit/Graph/FG/recognizers.py +274 -0
- synkit/Graph/FG/ring_system.py +154 -0
- synkit/Graph/Feature/Descriptors/topology.py +857 -0
- synkit/Graph/Feature/Fingerprint/__init__.py +0 -0
- synkit/Graph/Feature/Fingerprint/wl_rxn_fps.py +222 -0
- synkit/Graph/Feature/__init__.py +5 -0
- synkit/Graph/Feature/graph_descriptors.py +323 -0
- synkit/Graph/Feature/graph_fps.py +96 -0
- synkit/Graph/Feature/graph_signature.py +239 -0
- synkit/Graph/Feature/hash_fps.py +133 -0
- synkit/Graph/Feature/morgan_fps.py +88 -0
- synkit/Graph/Feature/path_fps.py +81 -0
- synkit/Graph/Feature/wl_hash.py +134 -0
- synkit/Graph/Fusion/__init__.py +85 -0
- synkit/Graph/Fusion/candidate.py +559 -0
- synkit/Graph/Fusion/constructor.py +877 -0
- synkit/Graph/Fusion/identity.py +184 -0
- synkit/Graph/Fusion/interface.py +700 -0
- synkit/Graph/Fusion/search.py +241 -0
- synkit/Graph/Hyrogen/__init__.py +6 -0
- synkit/Graph/Hyrogen/_misc.py +591 -0
- synkit/Graph/Hyrogen/hcomplete.py +995 -0
- synkit/Graph/Hyrogen/hcomplete_legacy.py +368 -0
- synkit/Graph/Hyrogen/hcompletion_algorithms.py +713 -0
- synkit/Graph/Hyrogen/hextend.py +570 -0
- synkit/Graph/Hyrogen/hextend_legacy.py +215 -0
- synkit/Graph/ITS/__init__.py +4 -0
- synkit/Graph/ITS/its_builder.py +120 -0
- synkit/Graph/ITS/its_construction.py +719 -0
- synkit/Graph/ITS/its_decompose.py +572 -0
- synkit/Graph/ITS/its_destruction.py +331 -0
- synkit/Graph/ITS/its_expand.py +1251 -0
- synkit/Graph/ITS/its_relabel.py +187 -0
- synkit/Graph/ITS/its_reverter.py +356 -0
- synkit/Graph/ITS/normalize_aam.py +145 -0
- synkit/Graph/ITS/partial_its.py +238 -0
- synkit/Graph/ITS/rc_extractor.py +656 -0
- synkit/Graph/ITS/stereo.py +74 -0
- synkit/Graph/MTG/__init__.py +107 -0
- synkit/Graph/MTG/gluing.py +451 -0
- synkit/Graph/MTG/history.py +928 -0
- synkit/Graph/MTG/inventory.py +118 -0
- synkit/Graph/MTG/local.py +447 -0
- synkit/Graph/MTG/mcs_matcher.py +246 -0
- synkit/Graph/MTG/mtg.py +611 -0
- synkit/Graph/MTG/mtg_explore.py +74 -0
- synkit/Graph/MTG/observation.py +209 -0
- synkit/Graph/MTG/process.py +577 -0
- synkit/Graph/MTG/queries.py +66 -0
- synkit/Graph/MTG/realization.py +95 -0
- synkit/Graph/MTG/series_parallel.py +107 -0
- synkit/Graph/MTG/utils.py +424 -0
- synkit/Graph/Matcher/__init__.py +10 -0
- synkit/Graph/Matcher/approx_mcs.py +1264 -0
- synkit/Graph/Matcher/auto_est.py +736 -0
- synkit/Graph/Matcher/automorphism.py +314 -0
- synkit/Graph/Matcher/batch_cluster.py +224 -0
- synkit/Graph/Matcher/dedup_matches.py +247 -0
- synkit/Graph/Matcher/graph_cluster.py +191 -0
- synkit/Graph/Matcher/graph_matcher.py +312 -0
- synkit/Graph/Matcher/graph_morphism.py +421 -0
- synkit/Graph/Matcher/mcs_matcher.py +804 -0
- synkit/Graph/Matcher/multi_turbo_iso.py +186 -0
- synkit/Graph/Matcher/orbit.py +284 -0
- synkit/Graph/Matcher/partial_matcher.py +694 -0
- synkit/Graph/Matcher/sing.py +544 -0
- synkit/Graph/Matcher/subgraph_matcher.py +812 -0
- synkit/Graph/Matcher/turbo_iso.py +272 -0
- synkit/Graph/Matcher/wl_sel.py +537 -0
- synkit/Graph/Mech/__init__.py +26 -0
- synkit/Graph/Mech/conversion.py +1466 -0
- synkit/Graph/Mech/conversion_debug.py +237 -0
- synkit/Graph/Mech/electron_accounting.py +200 -0
- synkit/Graph/Mech/lwg_editor.py +406 -0
- synkit/Graph/Mech/lwg_ops.py +313 -0
- synkit/Graph/Morphism/__init__.py +99 -0
- synkit/Graph/Morphism/constraints.py +460 -0
- synkit/Graph/Morphism/llg.py +999 -0
- synkit/Graph/Morphism/morphism.py +349 -0
- synkit/Graph/Morphism/stereo.py +258 -0
- synkit/Graph/Morphism/stereo_morphism.py +383 -0
- synkit/Graph/Morphism/stereo_morphism_helpers.py +957 -0
- synkit/Graph/Morphism/stereo_morphism_types.py +117 -0
- synkit/Graph/Stereo/__init__.py +376 -0
- synkit/Graph/Stereo/_descriptor_core.py +207 -0
- synkit/Graph/Stereo/_descriptor_serialization.py +138 -0
- synkit/Graph/Stereo/canonical.py +648 -0
- synkit/Graph/Stereo/changes.py +964 -0
- synkit/Graph/Stereo/composition.py +391 -0
- synkit/Graph/Stereo/configured.py +946 -0
- synkit/Graph/Stereo/couplings.py +245 -0
- synkit/Graph/Stereo/descriptors.py +1030 -0
- synkit/Graph/Stereo/enumeration.py +597 -0
- synkit/Graph/Stereo/extended_descriptors.py +406 -0
- synkit/Graph/Stereo/global_configured.py +152 -0
- synkit/Graph/Stereo/global_stereo.py +304 -0
- synkit/Graph/Stereo/identity.py +648 -0
- synkit/Graph/Stereo/interchange.py +496 -0
- synkit/Graph/Stereo/legacy.py +460 -0
- synkit/Graph/Stereo/matching.py +899 -0
- synkit/Graph/Stereo/native.py +154 -0
- synkit/Graph/Stereo/orbits.py +668 -0
- synkit/Graph/Stereo/outcomes.py +99 -0
- synkit/Graph/Stereo/pairwise.py +350 -0
- synkit/Graph/Stereo/rdkit_adapter.py +662 -0
- synkit/Graph/Stereo/rdkit_identity.py +118 -0
- synkit/Graph/Stereo/rdkit_resonance.py +140 -0
- synkit/Graph/Stereo/reaction_interchange.py +266 -0
- synkit/Graph/Stereo/semantics.py +296 -0
- synkit/Graph/Stereo/supports.py +257 -0
- synkit/Graph/Stereo/wire.py +339 -0
- synkit/Graph/Wildcard/__init__.py +0 -0
- synkit/Graph/Wildcard/fuse_graph.py +156 -0
- synkit/Graph/Wildcard/graph_wc.py +303 -0
- synkit/Graph/Wildcard/its_merge.py +857 -0
- synkit/Graph/Wildcard/radwc.py +114 -0
- synkit/Graph/Wildcard/wc_matcher.py +576 -0
- synkit/Graph/Wildcard/wildcard.py +225 -0
- synkit/Graph/__init__.py +17 -0
- synkit/Graph/canon_graph.py +14 -0
- synkit/Graph/syn_graph.py +199 -0
- synkit/Graph/utils.py +324 -0
- synkit/IO/__init__.py +4 -0
- synkit/IO/chem_converter.py +1059 -0
- synkit/IO/combinatorial/__init__.py +8 -0
- synkit/IO/combinatorial/gml_to_graph.py +223 -0
- synkit/IO/combinatorial/graph_to_gml.py +280 -0
- synkit/IO/combinatorial/graph_to_smarts.py +186 -0
- synkit/IO/combinatorial/smarts_expander.py +147 -0
- synkit/IO/combinatorial/smarts_generalizer.py +133 -0
- synkit/IO/combinatorial/smarts_to_graph.py +179 -0
- synkit/IO/conversion.py +42 -0
- synkit/IO/data_io.py +269 -0
- synkit/IO/data_process.py +143 -0
- synkit/IO/debug.py +73 -0
- synkit/IO/gml_to_nx.py +150 -0
- synkit/IO/graph_to_mol.py +199 -0
- synkit/IO/mol_to_graph.py +1105 -0
- synkit/IO/mol_to_graph_chemistry.py +545 -0
- synkit/IO/nx_to_gml.py +209 -0
- synkit/IO/smiles_to_id.py +123 -0
- synkit/Mechanism/__init__.py +151 -0
- synkit/Mechanism/adapters.py +136 -0
- synkit/Mechanism/audit.py +323 -0
- synkit/Mechanism/benchmark.py +638 -0
- synkit/Mechanism/compatibility.py +13 -0
- synkit/Mechanism/drawing.py +85 -0
- synkit/Mechanism/electron_models.py +725 -0
- synkit/Mechanism/equivalence.py +521 -0
- synkit/Mechanism/evidence.py +262 -0
- synkit/Mechanism/interchange.py +141 -0
- synkit/Mechanism/model.py +918 -0
- synkit/Mechanism/neighbor_stereo.py +312 -0
- synkit/Mechanism/radical_data.py +740 -0
- synkit/Mechanism/replay.py +986 -0
- synkit/Mechanism/schema.py +264 -0
- synkit/Mechanism/stereo_state.py +416 -0
- synkit/Mechanism/symbols.py +91 -0
- synkit/Rule/Apply/__init__.py +35 -0
- synkit/Rule/Apply/conditions.py +45 -0
- synkit/Rule/Apply/dpo.py +998 -0
- synkit/Rule/Apply/rule_matcher.py +218 -0
- synkit/Rule/Apply/validation.py +354 -0
- synkit/Rule/Compose/__init__.py +106 -0
- synkit/Rule/Compose/_identity.py +43 -0
- synkit/Rule/Compose/composition.py +976 -0
- synkit/Rule/Compose/laws.py +439 -0
- synkit/Rule/Compose/search.py +771 -0
- synkit/Rule/Compose/synrule.py +170 -0
- synkit/Rule/Modify/__init__.py +0 -0
- synkit/Rule/Modify/implict_rule.py +56 -0
- synkit/Rule/Modify/longest_path.py +92 -0
- synkit/Rule/Modify/molecule_rule.py +115 -0
- synkit/Rule/Modify/prune_templates.py +78 -0
- synkit/Rule/Modify/rule_utils.py +194 -0
- synkit/Rule/Modify/strip_rule.py +97 -0
- synkit/Rule/__init__.py +28 -0
- synkit/Rule/generic_stereo.py +884 -0
- synkit/Rule/stereo_identity.py +271 -0
- synkit/Rule/syn_rule.py +1090 -0
- synkit/Synthesis/MSR/__init__.py +0 -0
- synkit/Synthesis/MSR/path_finder.py +239 -0
- synkit/Synthesis/Metrics/__init__.py +0 -0
- synkit/Synthesis/Metrics/_base.py +52 -0
- synkit/Synthesis/Metrics/_plot.py +129 -0
- synkit/Synthesis/Metrics/_ranking.py +179 -0
- synkit/Synthesis/RBL/__init__.py +80 -0
- synkit/Synthesis/RBL/engine.py +1249 -0
- synkit/Synthesis/RBL/matching.py +652 -0
- synkit/Synthesis/RBL/overlap.py +336 -0
- synkit/Synthesis/RBL/pipeline.py +376 -0
- synkit/Synthesis/RBL/policy.py +139 -0
- synkit/Synthesis/RBL/proof.py +370 -0
- synkit/Synthesis/RBL/reaction.py +506 -0
- synkit/Synthesis/RBL/state.py +169 -0
- synkit/Synthesis/RBL/validation.py +1060 -0
- synkit/Synthesis/Reactor/__init__.py +60 -0
- synkit/Synthesis/Reactor/core/__init__.py +1 -0
- synkit/Synthesis/Reactor/core/engine.py +967 -0
- synkit/Synthesis/Reactor/core/product.py +621 -0
- synkit/Synthesis/Reactor/core/rewrite.py +561 -0
- synkit/Synthesis/Reactor/core/strategy.py +44 -0
- synkit/Synthesis/Reactor/matching/__init__.py +1 -0
- synkit/Synthesis/Reactor/matching/mixin.py +920 -0
- synkit/Synthesis/Reactor/matching/policy.py +345 -0
- synkit/Synthesis/Reactor/matching/symmetry.py +309 -0
- synkit/Synthesis/Reactor/output/__init__.py +1 -0
- synkit/Synthesis/Reactor/output/deduplication.py +703 -0
- synkit/Synthesis/Reactor/output/policy.py +16 -0
- synkit/Synthesis/Reactor/output/serialization.py +485 -0
- synkit/Synthesis/Reactor/output/structural.py +893 -0
- synkit/Synthesis/Reactor/stereo/__init__.py +1 -0
- synkit/Synthesis/Reactor/stereo/assignment.py +35 -0
- synkit/Synthesis/Reactor/stereo/mixin.py +780 -0
- synkit/Synthesis/Reactor/variants/__init__.py +1 -0
- synkit/Synthesis/Reactor/variants/imbalanced.py +180 -0
- synkit/Synthesis/Reactor/variants/partial.py +84 -0
- synkit/Synthesis/Reactor/workflow/__init__.py +1 -0
- synkit/Synthesis/Reactor/workflow/batch.py +550 -0
- synkit/Synthesis/Reactor/workflow/benchmark.py +143 -0
- synkit/Synthesis/Reactor/workflow/postprocess.py +267 -0
- synkit/Synthesis/Reactor/workflow/rule_filter.py +210 -0
- synkit/Synthesis/__init__.py +0 -0
- synkit/Synthesis/reactor_utils.py +316 -0
- synkit/Utils/__init__.py +0 -0
- synkit/Utils/utils.py +172 -0
- synkit/Vis/__init__.py +91 -0
- synkit/Vis/crn/__init__.py +40 -0
- synkit/Vis/epd/__init__.py +20 -0
- synkit/Vis/epd/arrows.py +425 -0
- synkit/Vis/epd/chem.py +197 -0
- synkit/Vis/epd/constants.py +83 -0
- synkit/Vis/epd/layout.py +283 -0
- synkit/Vis/epd/mapping.py +51 -0
- synkit/Vis/epd/models.py +136 -0
- synkit/Vis/epd/render.py +543 -0
- synkit/Vis/epd/utils.py +108 -0
- synkit/Vis/epd/visualizer.py +811 -0
- synkit/Vis/graph_visualizer.py +375 -0
- synkit/Vis/its/__init__.py +19 -0
- synkit/Vis/its/drawer.py +615 -0
- synkit/Vis/its_drawer.py +35 -0
- synkit/Vis/molecule/__init__.py +31 -0
- synkit/Vis/molecule/drawer.py +563 -0
- synkit/Vis/molecule/reaction.py +285 -0
- synkit/Vis/molecule_drawer.py +33 -0
- synkit/Vis/mtg/__init__.py +17 -0
- synkit/Vis/mtg/drawer.py +769 -0
- synkit/Vis/mtg_drawer.py +34 -0
- synkit/Vis/pdf_writer.py +146 -0
- synkit/Vis/reaction/__init__.py +19 -0
- synkit/Vis/reaction/rule.py +141 -0
- synkit/Vis/reaction/rxn.py +532 -0
- synkit/Vis/reaction_drawer.py +41 -0
- synkit/Vis/space/__init__.py +17 -0
- synkit/Vis/space/chemical.py +80 -0
- synkit/Vis/space/embedding.py +87 -0
- synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
- synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
- synkit/Vis/vis_synedu/rxn_vis.py +377 -0
- synkit/Vis/vis_synedu/vis.py +501 -0
- synkit/Vis/visual_drawer.py +215 -0
- synkit/Vis/visual_model.py +520 -0
- synkit/__init__.py +3 -0
- synkit/_native.py +91 -0
- synkit/examples.py +50 -0
- synkit/version.py +23 -0
- synkit-1.6.5.dist-info/METADATA +156 -0
- synkit-1.6.5.dist-info/RECORD +636 -0
- synkit-1.6.5.dist-info/WHEEL +4 -0
- synkit-1.6.5.dist-info/licenses/LICENSE +21 -0
- synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/COPYRIGHT +44 -0
- synkit-1.6.5.dist-info/licenses/synkit/Chem/Mapper/solvers/native/cpp/src/symmetry/vendor/nauty/LICENSE-2.0.txt +202 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Exact bounded decision diagram for residual assignment cost spectra.
|
|
2
|
+
|
|
3
|
+
The diagram assigns residual source rows in a fixed order. A state retains the
|
|
4
|
+
used product columns and the exact accumulated cost of the assigned prefix for
|
|
5
|
+
every remaining row/image choice. Prefixes with the same future cost table and
|
|
6
|
+
used-column set have identical suffix problems and can be merged safely.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from .propagation_limits import check_deadline
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _StateLimit(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SuffixCostSpectrum:
|
|
24
|
+
"""Build and query an exact residual CD decision diagram.
|
|
25
|
+
|
|
26
|
+
Costs are nonnegative integer quarter units. The unary matrix contains all
|
|
27
|
+
costs from rows assigned before this residual; pair costs within the
|
|
28
|
+
residual are added as rows are assigned. ``max_cost`` bounds total residual
|
|
29
|
+
cost and therefore caps the stored bitset. A state-cap or deadline failure
|
|
30
|
+
leaves no partial spectrum available to callers.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
a,
|
|
36
|
+
b,
|
|
37
|
+
rows,
|
|
38
|
+
columns,
|
|
39
|
+
unary,
|
|
40
|
+
allowed,
|
|
41
|
+
*,
|
|
42
|
+
max_cost,
|
|
43
|
+
max_states=20_000,
|
|
44
|
+
max_seconds=None,
|
|
45
|
+
deadline=None,
|
|
46
|
+
):
|
|
47
|
+
self.a = np.asarray(a, dtype=np.int64)
|
|
48
|
+
self.b = np.asarray(b, dtype=np.int64)
|
|
49
|
+
self.rows, self.columns = tuple(rows), tuple(columns)
|
|
50
|
+
self.unary = np.asarray(unary, dtype=np.int64)
|
|
51
|
+
self.allowed = np.asarray(allowed, dtype=bool)
|
|
52
|
+
self.max_cost = int(max_cost)
|
|
53
|
+
self.max_states = int(max_states)
|
|
54
|
+
self.max_seconds = max_seconds
|
|
55
|
+
self.deadline = deadline
|
|
56
|
+
self.n = len(self.rows)
|
|
57
|
+
self.states = 0
|
|
58
|
+
self.transitions = 0
|
|
59
|
+
self.orbits_pruned = 0
|
|
60
|
+
self._validated_symmetry_groups = set()
|
|
61
|
+
self.memo = {}
|
|
62
|
+
self._prepared = False
|
|
63
|
+
self._prepare_failed = False
|
|
64
|
+
self._prepare_started = None
|
|
65
|
+
if (
|
|
66
|
+
len(self.columns) != self.n
|
|
67
|
+
or self.unary.shape != (self.n, self.n)
|
|
68
|
+
or self.allowed.shape != self.unary.shape
|
|
69
|
+
):
|
|
70
|
+
raise ValueError("residual arrays must be square and match row/column sets")
|
|
71
|
+
if self.max_cost < 0:
|
|
72
|
+
raise ValueError("max_cost must be nonnegative")
|
|
73
|
+
if self.max_states < 1:
|
|
74
|
+
raise ValueError("max_states must be positive")
|
|
75
|
+
if self.max_seconds is not None and (
|
|
76
|
+
isinstance(self.max_seconds, bool)
|
|
77
|
+
or not isinstance(self.max_seconds, (int, float))
|
|
78
|
+
or not math.isfinite(self.max_seconds)
|
|
79
|
+
or self.max_seconds <= 0
|
|
80
|
+
):
|
|
81
|
+
raise ValueError("max_seconds must be finite and positive")
|
|
82
|
+
if np.any(self.unary < 0):
|
|
83
|
+
raise ValueError("suffix spectra require nonnegative unary costs")
|
|
84
|
+
if self.a.ndim != 2 or self.b.ndim != 2:
|
|
85
|
+
raise ValueError("endpoint cost matrices must be two-dimensional")
|
|
86
|
+
if any(i < 0 or i >= len(self.a) for i in self.rows):
|
|
87
|
+
raise ValueError("residual rows are outside the source matrix")
|
|
88
|
+
if any(j < 0 or j >= len(self.b) for j in self.columns):
|
|
89
|
+
raise ValueError("residual columns are outside the product matrix")
|
|
90
|
+
|
|
91
|
+
def _key(self, depth, used, future_cost):
|
|
92
|
+
available = tuple(j for j in range(self.n) if not used & (1 << j))
|
|
93
|
+
future = tuple(
|
|
94
|
+
int(future_cost[row, col])
|
|
95
|
+
for row in range(self.n - depth)
|
|
96
|
+
for col in available
|
|
97
|
+
)
|
|
98
|
+
return depth, used, future
|
|
99
|
+
|
|
100
|
+
def _tick(self):
|
|
101
|
+
self._check()
|
|
102
|
+
self.states += 1
|
|
103
|
+
if self.states > self.max_states:
|
|
104
|
+
raise _StateLimit
|
|
105
|
+
|
|
106
|
+
def _check(self):
|
|
107
|
+
check_deadline(self.deadline)
|
|
108
|
+
if (
|
|
109
|
+
self._prepare_started is not None
|
|
110
|
+
and self.max_seconds is not None
|
|
111
|
+
and time.perf_counter() - self._prepare_started > self.max_seconds
|
|
112
|
+
):
|
|
113
|
+
raise _StateLimit
|
|
114
|
+
|
|
115
|
+
def _state(self, depth, used, future_cost):
|
|
116
|
+
key = self._key(depth, used, future_cost)
|
|
117
|
+
cached = self.memo.get(key)
|
|
118
|
+
if cached is not None:
|
|
119
|
+
return key, cached[0]
|
|
120
|
+
self._tick()
|
|
121
|
+
if depth == self.n:
|
|
122
|
+
self.memo[key] = (1, ())
|
|
123
|
+
return key, 1
|
|
124
|
+
|
|
125
|
+
support = 0
|
|
126
|
+
branches = []
|
|
127
|
+
remaining_columns = tuple(col for col in range(self.n) if not used & (1 << col))
|
|
128
|
+
row = self.rows[depth]
|
|
129
|
+
for col_pos in remaining_columns:
|
|
130
|
+
self._check()
|
|
131
|
+
if not self.allowed[depth, col_pos]:
|
|
132
|
+
continue
|
|
133
|
+
edge_cost = int(future_cost[0, col_pos])
|
|
134
|
+
if edge_cost > self.max_cost:
|
|
135
|
+
continue
|
|
136
|
+
child_costs = future_cost[1:, :].copy()
|
|
137
|
+
if depth + 1 < self.n:
|
|
138
|
+
image = self.columns[col_pos]
|
|
139
|
+
remaining_positions = tuple(
|
|
140
|
+
pos for pos in remaining_columns if pos != col_pos
|
|
141
|
+
)
|
|
142
|
+
for next_depth in range(depth + 1, self.n):
|
|
143
|
+
next_row = self.rows[next_depth]
|
|
144
|
+
for next_col in remaining_positions:
|
|
145
|
+
child_costs[next_depth - depth - 1, next_col] += abs(
|
|
146
|
+
int(self.a[row, next_row])
|
|
147
|
+
- int(self.b[image, self.columns[next_col]])
|
|
148
|
+
)
|
|
149
|
+
if remaining_positions:
|
|
150
|
+
used_columns = set(range(self.n)) - set(remaining_positions)
|
|
151
|
+
if used_columns:
|
|
152
|
+
child_costs[:, tuple(sorted(used_columns))] = 0
|
|
153
|
+
child_key, child_support = self._state(
|
|
154
|
+
depth + 1, used | (1 << col_pos), child_costs
|
|
155
|
+
)
|
|
156
|
+
if child_support:
|
|
157
|
+
branches.append((col_pos, edge_cost, child_key))
|
|
158
|
+
support |= child_support << edge_cost
|
|
159
|
+
self.transitions += 1
|
|
160
|
+
support &= (1 << (self.max_cost + 1)) - 1
|
|
161
|
+
self.memo[key] = (support, tuple(branches))
|
|
162
|
+
return key, support
|
|
163
|
+
|
|
164
|
+
def prepare(self):
|
|
165
|
+
"""Build the bounded exact support, or discard it on a state-cap miss."""
|
|
166
|
+
if self._prepared:
|
|
167
|
+
return not self._prepare_failed
|
|
168
|
+
self._prepared = True
|
|
169
|
+
self._prepare_started = time.perf_counter()
|
|
170
|
+
try:
|
|
171
|
+
self.root_key, self.support = self._state(
|
|
172
|
+
0,
|
|
173
|
+
0,
|
|
174
|
+
self.unary.copy(),
|
|
175
|
+
)
|
|
176
|
+
return True
|
|
177
|
+
except _StateLimit:
|
|
178
|
+
self._prepare_failed = True
|
|
179
|
+
self.memo.clear()
|
|
180
|
+
self.support = 0
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def _has_cost(support, cost):
|
|
185
|
+
return cost >= 0 and bool(support & (1 << cost))
|
|
186
|
+
|
|
187
|
+
def reachable_costs(self, lower=0, upper=None):
|
|
188
|
+
"""Return exact reachable costs in the requested inclusive interval."""
|
|
189
|
+
if not self._prepared and not self.prepare():
|
|
190
|
+
return frozenset()
|
|
191
|
+
if self._prepare_failed:
|
|
192
|
+
return frozenset()
|
|
193
|
+
upper = self.max_cost if upper is None else min(int(upper), self.max_cost)
|
|
194
|
+
lower = max(0, int(lower))
|
|
195
|
+
if upper < lower:
|
|
196
|
+
return frozenset()
|
|
197
|
+
return frozenset(
|
|
198
|
+
cost
|
|
199
|
+
for cost in range(lower, upper + 1)
|
|
200
|
+
if self._has_cost(self.support, cost)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _walk(self, key, target, mapping, symmetry_group):
|
|
204
|
+
check_deadline(self.deadline)
|
|
205
|
+
support, branches = self.memo[key]
|
|
206
|
+
if not self._has_cost(support, target):
|
|
207
|
+
return
|
|
208
|
+
depth = key[0]
|
|
209
|
+
if depth == self.n:
|
|
210
|
+
if target == 0:
|
|
211
|
+
yield tuple(mapping)
|
|
212
|
+
return
|
|
213
|
+
for col_pos, edge_cost, child_key in branches:
|
|
214
|
+
remainder = target - edge_cost
|
|
215
|
+
if remainder < 0:
|
|
216
|
+
continue
|
|
217
|
+
child_support = self.memo[child_key][0]
|
|
218
|
+
if not self._has_cost(child_support, remainder):
|
|
219
|
+
continue
|
|
220
|
+
image = self.columns[col_pos]
|
|
221
|
+
if len(symmetry_group) > 1:
|
|
222
|
+
orbit = {permutation[image] for permutation in symmetry_group}
|
|
223
|
+
if image != min(orbit):
|
|
224
|
+
self.orbits_pruned += 1
|
|
225
|
+
continue
|
|
226
|
+
child_group = tuple(
|
|
227
|
+
permutation
|
|
228
|
+
for permutation in symmetry_group
|
|
229
|
+
if permutation[image] == image
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
child_group = symmetry_group
|
|
233
|
+
mapping[depth] = image
|
|
234
|
+
yield from self._walk(child_key, remainder, mapping, child_group)
|
|
235
|
+
mapping[depth] = -1
|
|
236
|
+
|
|
237
|
+
def _validate_symmetry_group(self, symmetry_group):
|
|
238
|
+
"""Check that a supplied product group preserves this residual problem."""
|
|
239
|
+
group = tuple(tuple(permutation) for permutation in symmetry_group)
|
|
240
|
+
if not group:
|
|
241
|
+
return group
|
|
242
|
+
cache_key = frozenset(group)
|
|
243
|
+
if cache_key in self._validated_symmetry_groups:
|
|
244
|
+
return group
|
|
245
|
+
identity = tuple(range(len(self.b)))
|
|
246
|
+
if identity not in group:
|
|
247
|
+
raise ValueError("symmetry_group must contain the identity")
|
|
248
|
+
if len(group) == 1:
|
|
249
|
+
self._validated_symmetry_groups.add(cache_key)
|
|
250
|
+
return group
|
|
251
|
+
positions = {column: index for index, column in enumerate(self.columns)}
|
|
252
|
+
for permutation in group:
|
|
253
|
+
if (
|
|
254
|
+
len(permutation) != len(self.b)
|
|
255
|
+
or set(permutation) != set(range(len(self.b)))
|
|
256
|
+
):
|
|
257
|
+
raise ValueError("symmetry_group entries must be permutations")
|
|
258
|
+
if any(permutation[column] not in positions for column in self.columns):
|
|
259
|
+
raise ValueError("symmetry_group must preserve residual columns")
|
|
260
|
+
image_positions = np.asarray(
|
|
261
|
+
[positions[permutation[column]] for column in self.columns],
|
|
262
|
+
dtype=int,
|
|
263
|
+
)
|
|
264
|
+
if not np.array_equal(
|
|
265
|
+
self.allowed, self.allowed[:, image_positions]
|
|
266
|
+
) or not np.array_equal(self.unary, self.unary[:, image_positions]):
|
|
267
|
+
raise ValueError(
|
|
268
|
+
"symmetry_group must preserve residual domains and costs"
|
|
269
|
+
)
|
|
270
|
+
if not np.array_equal(
|
|
271
|
+
self.b[np.ix_(permutation, permutation)], self.b
|
|
272
|
+
):
|
|
273
|
+
raise ValueError("symmetry_group must preserve product bonds")
|
|
274
|
+
self._validated_symmetry_groups.add(cache_key)
|
|
275
|
+
return group
|
|
276
|
+
|
|
277
|
+
def mappings_at(self, target, *, symmetry_group=()):
|
|
278
|
+
"""Yield residual mappings at one cost, optionally one per orbit.
|
|
279
|
+
|
|
280
|
+
``symmetry_group`` must be a product-index permutation group that
|
|
281
|
+
preserves this residual instance's allowed entries, unary costs, and
|
|
282
|
+
pair costs. The yielded mapping is the lexicographically least member
|
|
283
|
+
of its orbit in the fixed ``rows`` order.
|
|
284
|
+
"""
|
|
285
|
+
if not self._prepared and not self.prepare():
|
|
286
|
+
return
|
|
287
|
+
if self._prepare_failed or not self._has_cost(self.support, int(target)):
|
|
288
|
+
return
|
|
289
|
+
group = self._validate_symmetry_group(symmetry_group)
|
|
290
|
+
yield from self._walk(self.root_key, int(target), [-1] * self.n, group)
|
|
291
|
+
|
|
292
|
+
def mappings_between(self, lower, upper, *, symmetry_group=()):
|
|
293
|
+
"""Yield ``(mapping, cost)`` pairs for an inclusive cost interval."""
|
|
294
|
+
for cost in sorted(self.reachable_costs(lower, upper)):
|
|
295
|
+
for mapping in self.mappings_at(cost, symmetry_group=symmetry_group):
|
|
296
|
+
yield mapping, cost
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
__all__ = ["SuffixCostSpectrum"]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Dispatch full-space exact mapping to Python propagation or C++ shell search.
|
|
2
|
+
|
|
3
|
+
PABS stands for Propagation and Assignment-Bounded Search. It is this package's
|
|
4
|
+
descriptive interface name. The Python implementation combines one-to-one
|
|
5
|
+
domain propagation and LAP cost bounds; the C++ implementation has a separate
|
|
6
|
+
integer-shell traversal. These routes do not use the reduced SLAP uncertainty
|
|
7
|
+
kernel or its MILP solver.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import replace
|
|
11
|
+
|
|
12
|
+
from .pabs.propagation import PropagationConfig, enumerate_synister_cp_mappings
|
|
13
|
+
from .pabs.multi_shell import enumerate_pabs_shells
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def enumerate_pabs_mappings(lgp, *, backend="python", library_path=None, **options):
|
|
17
|
+
"""Enumerate a minimum or selected-distance shell with an exact backend.
|
|
18
|
+
|
|
19
|
+
PABS means Propagation and Assignment-Bounded Search. ``backend='python'``
|
|
20
|
+
is the default and selects full-space propagation with linear-assignment
|
|
21
|
+
bounds. It does not run the SLAP mapper or extract its uncertainty kernel.
|
|
22
|
+
Explicit fixed assignments restrict the search if supplied by the caller.
|
|
23
|
+
Unsupported Python inputs use a recorded full-distance-search fallback.
|
|
24
|
+
|
|
25
|
+
``cpp`` selects the optional
|
|
26
|
+
compiled integer shell search and requires an explicitly built library.
|
|
27
|
+
Both return DistanceEnumerationResult. Backend-specific unsupported options
|
|
28
|
+
raise errors; selecting C++ never silently executes Python search.
|
|
29
|
+
The C++ traversal is distinct from the Python propagation implementation.
|
|
30
|
+
"""
|
|
31
|
+
if backend not in ("python", "cpp"):
|
|
32
|
+
raise ValueError("backend must be 'python' or 'cpp'")
|
|
33
|
+
if backend == "cpp":
|
|
34
|
+
if library_path is None:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
"backend='cpp' requires library_path; build with native_build"
|
|
37
|
+
)
|
|
38
|
+
from .native.search import enumerate_cpp_mappings
|
|
39
|
+
|
|
40
|
+
return enumerate_cpp_mappings(lgp, library_path=library_path, **options)
|
|
41
|
+
if library_path is not None:
|
|
42
|
+
raise ValueError("library_path is only used by backend='cpp'")
|
|
43
|
+
result = enumerate_synister_cp_mappings(lgp, **options)
|
|
44
|
+
statistics = dict(result.backend_statistics or {})
|
|
45
|
+
statistics.update(
|
|
46
|
+
method="PABS", requested_backend="python", implementation=result.backend
|
|
47
|
+
)
|
|
48
|
+
return replace(result, backend_statistics=statistics)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"PropagationConfig",
|
|
53
|
+
"enumerate_pabs_mappings",
|
|
54
|
+
"enumerate_pabs_shells",
|
|
55
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reference-free heuristics for feasible search incumbents."""
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Bounded, reference-free improvement of feasible search incumbents."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _swap_deltas(reactant, mapped_product, left, right):
|
|
7
|
+
"""Exact objective differences for simultaneous evaluation of pair swaps."""
|
|
8
|
+
a, b = reactant, mapped_product
|
|
9
|
+
rows = (
|
|
10
|
+
np.abs(a[left] - b[right])
|
|
11
|
+
- np.abs(a[left] - b[left])
|
|
12
|
+
+ np.abs(a[right] - b[left])
|
|
13
|
+
- np.abs(a[right] - b[right])
|
|
14
|
+
)
|
|
15
|
+
columns = (
|
|
16
|
+
np.abs(a[:, left].T - b[:, right].T)
|
|
17
|
+
- np.abs(a[:, left].T - b[:, left].T)
|
|
18
|
+
+ np.abs(a[:, right].T - b[:, left].T)
|
|
19
|
+
- np.abs(a[:, right].T - b[:, right].T)
|
|
20
|
+
)
|
|
21
|
+
delta = rows + columns
|
|
22
|
+
index = np.arange(len(left))
|
|
23
|
+
delta[index, left] = 0
|
|
24
|
+
delta[index, right] = 0
|
|
25
|
+
internal = (
|
|
26
|
+
np.abs(a[left, left] - b[right, right])
|
|
27
|
+
- np.abs(a[left, left] - b[left, left])
|
|
28
|
+
+ np.abs(a[right, right] - b[left, left])
|
|
29
|
+
- np.abs(a[right, right] - b[right, right])
|
|
30
|
+
+ np.abs(a[left, right] - b[right, left])
|
|
31
|
+
- np.abs(a[left, right] - b[left, right])
|
|
32
|
+
+ np.abs(a[right, left] - b[left, right])
|
|
33
|
+
- np.abs(a[right, left] - b[right, left])
|
|
34
|
+
)
|
|
35
|
+
return 0.5 * (delta.sum(axis=1) + internal)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def improve_seed_mapping(
|
|
39
|
+
reactant,
|
|
40
|
+
product,
|
|
41
|
+
elements,
|
|
42
|
+
product_elements,
|
|
43
|
+
mapping,
|
|
44
|
+
*,
|
|
45
|
+
max_steps=64,
|
|
46
|
+
max_pool_atoms=256,
|
|
47
|
+
):
|
|
48
|
+
"""Improve a typed bijection with bounded best-improvement pair swaps.
|
|
49
|
+
|
|
50
|
+
This supplies an incumbent only: no optimum proof or domain restriction.
|
|
51
|
+
Pool/step limits bound work and batches have at most 256 pairs. Every
|
|
52
|
+
accepted swap must improve the freshly recomputed complete objective.
|
|
53
|
+
"""
|
|
54
|
+
current = np.asarray(mapping, dtype=int).copy()
|
|
55
|
+
n = len(elements)
|
|
56
|
+
if sorted(current.tolist()) != list(range(n)) or any(
|
|
57
|
+
elements[atom] != product_elements[image] for atom, image in enumerate(current)
|
|
58
|
+
):
|
|
59
|
+
raise ValueError("seed must be a complete element-compatible bijection")
|
|
60
|
+
mapped = product[np.ix_(current, current)]
|
|
61
|
+
cost = 0.5 * float(np.abs(reactant - mapped).sum())
|
|
62
|
+
pool = np.arange(n)
|
|
63
|
+
if n > max_pool_atoms:
|
|
64
|
+
mismatch = np.abs(reactant - mapped)
|
|
65
|
+
scores = mismatch.sum(axis=0) + mismatch.sum(axis=1)
|
|
66
|
+
pool = np.asarray(
|
|
67
|
+
sorted(pool, key=lambda atom: (-scores[atom], atom))[:max_pool_atoms]
|
|
68
|
+
)
|
|
69
|
+
pool.sort()
|
|
70
|
+
left, right = np.triu_indices(len(pool), 1)
|
|
71
|
+
left, right = pool[left], pool[right]
|
|
72
|
+
labels = np.asarray(elements)
|
|
73
|
+
compatible = labels[left] == labels[right]
|
|
74
|
+
left, right = left[compatible], right[compatible]
|
|
75
|
+
accepted = 0
|
|
76
|
+
evaluated = 0
|
|
77
|
+
for _ in range(max_steps):
|
|
78
|
+
best_delta, best_index = -1e-9, None
|
|
79
|
+
for start in range(0, len(left), 256):
|
|
80
|
+
deltas = _swap_deltas(
|
|
81
|
+
reactant, mapped, left[start : start + 256], right[start : start + 256]
|
|
82
|
+
)
|
|
83
|
+
evaluated += len(deltas)
|
|
84
|
+
index = int(np.argmin(deltas))
|
|
85
|
+
if deltas[index] < best_delta:
|
|
86
|
+
best_delta, best_index = float(deltas[index]), start + index
|
|
87
|
+
if best_index is None:
|
|
88
|
+
break
|
|
89
|
+
i, j = left[best_index], right[best_index]
|
|
90
|
+
current[i], current[j] = current[j], current[i]
|
|
91
|
+
candidate_product = product[np.ix_(current, current)]
|
|
92
|
+
candidate_cost = 0.5 * float(np.abs(reactant - candidate_product).sum())
|
|
93
|
+
if candidate_cost >= cost - 1e-9:
|
|
94
|
+
current[i], current[j] = current[j], current[i]
|
|
95
|
+
break
|
|
96
|
+
mapped, cost = candidate_product, candidate_cost
|
|
97
|
+
accepted += 1
|
|
98
|
+
return current.tolist(), {
|
|
99
|
+
"accepted_swaps": accepted,
|
|
100
|
+
"evaluated_swaps": evaluated,
|
|
101
|
+
"pool_atoms": len(pool),
|
|
102
|
+
"max_steps": max_steps,
|
|
103
|
+
"max_pool_atoms": max_pool_atoms,
|
|
104
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Bounded common-fragment anchors used only for feasible seed construction."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from rdkit import Chem
|
|
7
|
+
from rdkit.Chem import rdFMCS
|
|
8
|
+
|
|
9
|
+
from .seed import improve_seed_mapping
|
|
10
|
+
from .seed_relaxation import improve_relaxed_seed_mapping
|
|
11
|
+
|
|
12
|
+
_MAX_FRAGMENT_SEARCH_SECONDS = 0.025
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _FragmentProgress(rdFMCS.MCSProgress):
|
|
16
|
+
def __init__(self, deadline):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self.deadline = deadline
|
|
19
|
+
self.search_deadline = None
|
|
20
|
+
|
|
21
|
+
def __call__(self, progress, parameters):
|
|
22
|
+
now = time.process_time()
|
|
23
|
+
if self.search_deadline is None:
|
|
24
|
+
# MCS setup may exceed the tiny slice before any search progress.
|
|
25
|
+
# Keep setup inside the overall cover budget, but start the
|
|
26
|
+
# per-fragment search allowance at its first progress callback.
|
|
27
|
+
self.search_deadline = min(
|
|
28
|
+
self.deadline, now + _MAX_FRAGMENT_SEARCH_SECONDS
|
|
29
|
+
)
|
|
30
|
+
return now < self.search_deadline
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _molecule(matrix, elements):
|
|
34
|
+
molecule = Chem.RWMol()
|
|
35
|
+
for element in elements:
|
|
36
|
+
molecule.AddAtom(Chem.Atom(int(element)))
|
|
37
|
+
orders = {
|
|
38
|
+
1: Chem.BondType.SINGLE,
|
|
39
|
+
1.5: Chem.BondType.AROMATIC,
|
|
40
|
+
2: Chem.BondType.DOUBLE,
|
|
41
|
+
3: Chem.BondType.TRIPLE,
|
|
42
|
+
}
|
|
43
|
+
for i, j in zip(*np.triu_indices(len(elements), 1)):
|
|
44
|
+
if matrix[i, j]:
|
|
45
|
+
molecule.AddBond(int(i), int(j), orders[matrix[i, j]])
|
|
46
|
+
result = molecule.GetMol()
|
|
47
|
+
result.UpdatePropertyCache(strict=False)
|
|
48
|
+
Chem.GetSymmSSSR(result)
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def improve_fragment_seed( # noqa: C901
|
|
53
|
+
a, b, er, ep, mapping, *, max_fragments=16, budget_seconds=0.5
|
|
54
|
+
):
|
|
55
|
+
"""Anchor a greedy common-fragment cover, then polish a feasible bijection.
|
|
56
|
+
|
|
57
|
+
Anchors constrain only this optional heuristic. They are never passed to
|
|
58
|
+
exact search. The fragment finder has a shared cooperative process-CPU budget,
|
|
59
|
+
so scheduler descheduling does not consume its tiny search slices. The
|
|
60
|
+
enclosing analysis still enforces its independent wall-clock deadline, and
|
|
61
|
+
unsupported matrix semantics simply retain the existing seed.
|
|
62
|
+
"""
|
|
63
|
+
if len(mapping) > 256 or not np.array_equal(a, a.T) or not np.array_equal(b, b.T):
|
|
64
|
+
return list(mapping), {"skipped": "size_or_directed_matrix"}
|
|
65
|
+
values = set(np.unique(a)) | set(np.unique(b))
|
|
66
|
+
if not values <= {0, 1, 1.5, 2, 3}:
|
|
67
|
+
return list(mapping), {"skipped": "unsupported_bond_orders"}
|
|
68
|
+
if sorted(mapping) != list(range(len(er))) or any(
|
|
69
|
+
er[i] != ep[p] for i, p in enumerate(mapping)
|
|
70
|
+
):
|
|
71
|
+
raise ValueError("seed must be a complete element-compatible bijection")
|
|
72
|
+
fixed = {}
|
|
73
|
+
working = list(mapping)
|
|
74
|
+
orientation_evaluations = 0
|
|
75
|
+
sizes = []
|
|
76
|
+
canceled = False
|
|
77
|
+
deadline = time.process_time() + budget_seconds
|
|
78
|
+
for _ in range(max_fragments):
|
|
79
|
+
if time.process_time() >= deadline:
|
|
80
|
+
break
|
|
81
|
+
rows = [i for i in range(len(er)) if i not in fixed]
|
|
82
|
+
used = set(fixed.values())
|
|
83
|
+
columns = [i for i in range(len(ep)) if i not in used]
|
|
84
|
+
if len(rows) < 3:
|
|
85
|
+
break
|
|
86
|
+
reactant = _molecule(a[np.ix_(rows, rows)], [er[i] for i in rows])
|
|
87
|
+
product = _molecule(b[np.ix_(columns, columns)], [ep[i] for i in columns])
|
|
88
|
+
parameters = rdFMCS.MCSParameters()
|
|
89
|
+
parameters.Timeout = 1
|
|
90
|
+
parameters.BondCompareParameters.RingMatchesRingOnly = True
|
|
91
|
+
parameters.AtomCompareParameters.RingMatchesRingOnly = True
|
|
92
|
+
callback = _FragmentProgress(deadline)
|
|
93
|
+
parameters.ProgressCallback = callback
|
|
94
|
+
result = rdFMCS.FindMCS([reactant, product], parameters)
|
|
95
|
+
canceled |= result.canceled
|
|
96
|
+
if result.numAtoms < 3:
|
|
97
|
+
break
|
|
98
|
+
query = Chem.MolFromSmarts(result.smartsString)
|
|
99
|
+
left_matches = reactant.GetSubstructMatches(query, uniquify=False, maxMatches=8)
|
|
100
|
+
right_matches = product.GetSubstructMatches(
|
|
101
|
+
query, uniquify=False, maxMatches=32
|
|
102
|
+
)
|
|
103
|
+
best = None
|
|
104
|
+
for left in left_matches:
|
|
105
|
+
for right in right_matches:
|
|
106
|
+
if len(left) != len(right) or len(left) < 3:
|
|
107
|
+
continue
|
|
108
|
+
anchors = {rows[i]: columns[j] for i, j in zip(left, right)}
|
|
109
|
+
if any(er[i] != ep[j] for i, j in anchors.items()):
|
|
110
|
+
continue
|
|
111
|
+
trial = working.copy()
|
|
112
|
+
for atom, image in anchors.items():
|
|
113
|
+
owner = trial.index(image)
|
|
114
|
+
trial[atom], trial[owner] = trial[owner], trial[atom]
|
|
115
|
+
trial_cost = 0.5 * float(np.abs(a - b[np.ix_(trial, trial)]).sum())
|
|
116
|
+
orientation_evaluations += 1
|
|
117
|
+
if best is None or trial_cost < best[0]:
|
|
118
|
+
best = trial_cost, trial, anchors
|
|
119
|
+
if time.process_time() >= deadline:
|
|
120
|
+
break
|
|
121
|
+
if time.process_time() >= deadline:
|
|
122
|
+
break
|
|
123
|
+
if best is None:
|
|
124
|
+
break
|
|
125
|
+
_, working, anchors = best
|
|
126
|
+
fixed.update(anchors)
|
|
127
|
+
sizes.append(len(anchors))
|
|
128
|
+
candidate = working
|
|
129
|
+
if fixed:
|
|
130
|
+
candidate, _ = improve_relaxed_seed_mapping(
|
|
131
|
+
a, b, er, ep, candidate, fixed_mapping=fixed
|
|
132
|
+
)
|
|
133
|
+
candidate, _ = improve_seed_mapping(a, b, er, ep, candidate)
|
|
134
|
+
old_cost = 0.5 * float(np.abs(a - b[np.ix_(mapping, mapping)]).sum())
|
|
135
|
+
new_cost = 0.5 * float(np.abs(a - b[np.ix_(candidate, candidate)]).sum())
|
|
136
|
+
if new_cost >= old_cost:
|
|
137
|
+
candidate, new_cost = list(mapping), old_cost
|
|
138
|
+
return candidate, {
|
|
139
|
+
"fragment_sizes": sizes,
|
|
140
|
+
"orientation_evaluations": orientation_evaluations,
|
|
141
|
+
"anchored_atoms": len(fixed),
|
|
142
|
+
"canceled": canceled,
|
|
143
|
+
"cost": new_cost,
|
|
144
|
+
"budget_seconds": budget_seconds,
|
|
145
|
+
"budget_clock": "process_cpu",
|
|
146
|
+
"fragment_search_seconds": _MAX_FRAGMENT_SEARCH_SECONDS,
|
|
147
|
+
"fragment_slice_scope": "from_first_progress_callback",
|
|
148
|
+
}
|