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,66 @@
|
|
|
1
|
+
"""Budgeted verified cyclic-subgroup selection for exact propagation search."""
|
|
2
|
+
|
|
3
|
+
from .propagation_limits import PropagationDeadline, check_deadline
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def bounded_cyclic_subgroup(permutations, *, max_order, deadline):
|
|
7
|
+
"""Retain a fully closed subgroup; interruption never returns partial powers."""
|
|
8
|
+
if not permutations:
|
|
9
|
+
return ()
|
|
10
|
+
identity = tuple(range(len(permutations[0])))
|
|
11
|
+
best = (identity,)
|
|
12
|
+
for generator in permutations:
|
|
13
|
+
check_deadline(deadline)
|
|
14
|
+
powers, seen, current = [identity], {identity}, identity
|
|
15
|
+
while len(powers) <= max_order:
|
|
16
|
+
check_deadline(deadline)
|
|
17
|
+
current = tuple(generator[image] for image in current)
|
|
18
|
+
if current == identity:
|
|
19
|
+
if len(powers) > len(best):
|
|
20
|
+
best = tuple(powers)
|
|
21
|
+
break
|
|
22
|
+
if current in seen:
|
|
23
|
+
break
|
|
24
|
+
seen.add(current)
|
|
25
|
+
powers.append(current)
|
|
26
|
+
return best
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def bounded_generated_subgroup(permutations, *, max_order, deadline):
|
|
30
|
+
"""Close verified generators, retaining only fully completed subgroups."""
|
|
31
|
+
if not permutations:
|
|
32
|
+
return ()
|
|
33
|
+
identity = tuple(range(len(permutations[0])))
|
|
34
|
+
subgroup = {identity}
|
|
35
|
+
try:
|
|
36
|
+
for generator in permutations:
|
|
37
|
+
check_deadline(deadline)
|
|
38
|
+
if generator in subgroup:
|
|
39
|
+
continue
|
|
40
|
+
inverse = [0] * len(generator)
|
|
41
|
+
for index, image in enumerate(generator):
|
|
42
|
+
inverse[image] = index
|
|
43
|
+
inverse = tuple(inverse)
|
|
44
|
+
closure = set(subgroup)
|
|
45
|
+
pending = list(subgroup)
|
|
46
|
+
exceeded = False
|
|
47
|
+
factors = (*subgroup, generator, inverse)
|
|
48
|
+
while pending and not exceeded:
|
|
49
|
+
check_deadline(deadline)
|
|
50
|
+
current = pending.pop()
|
|
51
|
+
for factor in factors:
|
|
52
|
+
product = tuple(
|
|
53
|
+
current[factor[index]] for index in range(len(identity))
|
|
54
|
+
)
|
|
55
|
+
if product in closure:
|
|
56
|
+
continue
|
|
57
|
+
if len(closure) >= max_order:
|
|
58
|
+
exceeded = True
|
|
59
|
+
break
|
|
60
|
+
closure.add(product)
|
|
61
|
+
pending.append(product)
|
|
62
|
+
if not exceeded:
|
|
63
|
+
subgroup = closure
|
|
64
|
+
except PropagationDeadline:
|
|
65
|
+
pass
|
|
66
|
+
return tuple(sorted(subgroup))
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Reversible typed bond masses for cheap exact graph-search bounds."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ResidualTypedBonds:
|
|
7
|
+
"""Track signed sums by unordered atom-type pairs and absolute edge mass."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, a, b, labels, product_labels, rows, columns):
|
|
10
|
+
types = {
|
|
11
|
+
label: i
|
|
12
|
+
for i, label in enumerate(dict.fromkeys([*labels, *product_labels]))
|
|
13
|
+
}
|
|
14
|
+
size = len(types)
|
|
15
|
+
self.a, self.b = a, b
|
|
16
|
+
|
|
17
|
+
def buckets(values):
|
|
18
|
+
colors = np.asarray([types[label] for label in values], dtype=int)
|
|
19
|
+
return np.minimum(colors[:, None], colors) * size + np.maximum(
|
|
20
|
+
colors[:, None], colors
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
self.reactant_buckets, self.product_buckets = buckets(labels), buckets(
|
|
24
|
+
product_labels
|
|
25
|
+
)
|
|
26
|
+
# Small exact bond alphabets are common in this objective. Keep a
|
|
27
|
+
# reversible histogram only when its dense representation is bounded;
|
|
28
|
+
# uncommon large alphabets retain the existing linear bound.
|
|
29
|
+
weights = np.concatenate((a[np.triu_indices(len(a), 1)],
|
|
30
|
+
b[np.triu_indices(len(b), 1)]))
|
|
31
|
+
self.levels = np.unique(weights)
|
|
32
|
+
if not len(self.levels):
|
|
33
|
+
self.levels = np.asarray([0], dtype=np.int64)
|
|
34
|
+
self.histogram_enabled = len(self.levels) <= 16
|
|
35
|
+
self.level_index = (
|
|
36
|
+
{int(value): index for index, value in enumerate(self.levels)}
|
|
37
|
+
if self.histogram_enabled
|
|
38
|
+
else None
|
|
39
|
+
)
|
|
40
|
+
self.reactant_histogram = None
|
|
41
|
+
self.product_histogram = None
|
|
42
|
+
if self.histogram_enabled:
|
|
43
|
+
shape = (size * size, len(self.levels))
|
|
44
|
+
self.reactant_histogram = np.zeros(shape, dtype=np.int64)
|
|
45
|
+
self.product_histogram = np.zeros(shape, dtype=np.int64)
|
|
46
|
+
self._initialize_histogram(
|
|
47
|
+
a, self.reactant_buckets, rows, self.reactant_histogram, self.levels
|
|
48
|
+
)
|
|
49
|
+
self._initialize_histogram(
|
|
50
|
+
b, self.product_buckets, columns, self.product_histogram, self.levels
|
|
51
|
+
)
|
|
52
|
+
self.reactant = np.zeros(size * size, dtype=np.int64)
|
|
53
|
+
self.product = np.zeros(size * size, dtype=np.int64)
|
|
54
|
+
self.reactant_absolute = self._initialize(
|
|
55
|
+
a, self.reactant_buckets, rows, self.reactant
|
|
56
|
+
)
|
|
57
|
+
self.product_absolute = self._initialize(
|
|
58
|
+
b, self.product_buckets, columns, self.product
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _initialize(matrix, buckets, positions, mass):
|
|
63
|
+
positions = np.asarray(positions, dtype=int)
|
|
64
|
+
i, j = np.triu_indices(len(positions), 1)
|
|
65
|
+
left, right = positions[i], positions[j]
|
|
66
|
+
weights = matrix[left, right]
|
|
67
|
+
np.add.at(mass, buckets[left, right], weights)
|
|
68
|
+
return int(np.abs(weights).sum())
|
|
69
|
+
|
|
70
|
+
def lower_bound(self):
|
|
71
|
+
"""Sum reverse-triangle bounds over disjoint, preserved type classes."""
|
|
72
|
+
return int(np.abs(self.reactant - self.product).sum())
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _initialize_histogram(matrix, buckets, positions, histogram, levels):
|
|
76
|
+
positions = np.asarray(positions, dtype=int)
|
|
77
|
+
i, j = np.triu_indices(len(positions), 1)
|
|
78
|
+
left, right = positions[i], positions[j]
|
|
79
|
+
weight_index = np.searchsorted(levels, matrix[left, right])
|
|
80
|
+
np.add.at(histogram, (buckets[left, right], weight_index), 1)
|
|
81
|
+
|
|
82
|
+
def transport_lower_bound(self):
|
|
83
|
+
"""Return the typed residual pair-transport bound, if cheaply indexed.
|
|
84
|
+
|
|
85
|
+
Within each unordered atom-type pair, a compatible vertex bijection
|
|
86
|
+
induces a bijection on unordered residual pairs. Matching those pair
|
|
87
|
+
weights without the vertex-consistency constraints gives a relaxation.
|
|
88
|
+
For one-dimensional absolute cost, sorted matching equals the integral
|
|
89
|
+
of the absolute difference between cumulative histograms.
|
|
90
|
+
"""
|
|
91
|
+
if not self.histogram_enabled:
|
|
92
|
+
return None
|
|
93
|
+
cumulative = np.cumsum(
|
|
94
|
+
self.reactant_histogram - self.product_histogram, axis=1
|
|
95
|
+
)
|
|
96
|
+
gaps = np.diff(self.levels)
|
|
97
|
+
# A valid completion has equal pair counts in every compatible type
|
|
98
|
+
# bucket, so the final cumulative count must be zero.
|
|
99
|
+
if np.any(cumulative[:, -1]):
|
|
100
|
+
return None
|
|
101
|
+
return int((np.abs(cumulative[:, :-1]) @ gaps).sum())
|
|
102
|
+
|
|
103
|
+
def upper_bound(self):
|
|
104
|
+
"""Bound residual disagreement by the sum of absolute bond masses."""
|
|
105
|
+
return self.reactant_absolute + self.product_absolute
|
|
106
|
+
|
|
107
|
+
def remove(self, row, image, rows, columns):
|
|
108
|
+
"""Remove incident residual edges and return an exact restoration token."""
|
|
109
|
+
rows, columns = np.asarray(rows, dtype=int), np.asarray(columns, dtype=int)
|
|
110
|
+
rb, pb = self.reactant_buckets[row, rows], self.product_buckets[image, columns]
|
|
111
|
+
rw, pw = self.a[row, rows], self.b[image, columns]
|
|
112
|
+
ra, pa = int(np.abs(rw).sum()), int(np.abs(pw).sum())
|
|
113
|
+
np.add.at(self.reactant, rb, -rw)
|
|
114
|
+
np.add.at(self.product, pb, -pw)
|
|
115
|
+
histogram_token = None
|
|
116
|
+
if self.histogram_enabled:
|
|
117
|
+
ri = np.fromiter((self.level_index[int(x)] for x in rw), dtype=int)
|
|
118
|
+
pi = np.fromiter((self.level_index[int(x)] for x in pw), dtype=int)
|
|
119
|
+
np.add.at(self.reactant_histogram, (rb, ri), -1)
|
|
120
|
+
np.add.at(self.product_histogram, (pb, pi), -1)
|
|
121
|
+
histogram_token = rb, ri, pb, pi
|
|
122
|
+
self.reactant_absolute -= ra
|
|
123
|
+
self.product_absolute -= pa
|
|
124
|
+
return rb, rw, pb, pw, ra, pa, histogram_token
|
|
125
|
+
|
|
126
|
+
def restore(self, token):
|
|
127
|
+
"""Restore the signed typed sums and absolute masses after a child."""
|
|
128
|
+
rb, rw, pb, pw, ra, pa, histogram_token = token
|
|
129
|
+
np.add.at(self.reactant, rb, rw)
|
|
130
|
+
np.add.at(self.product, pb, pw)
|
|
131
|
+
if histogram_token is not None:
|
|
132
|
+
rb, ri, pb, pi = histogram_token
|
|
133
|
+
np.add.at(self.reactant_histogram, (rb, ri), 1)
|
|
134
|
+
np.add.at(self.product_histogram, (pb, pi), 1)
|
|
135
|
+
self.reactant_absolute += ra
|
|
136
|
+
self.product_absolute += pa
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Exact residual spectra using sparse source bonds and a frontier state.
|
|
2
|
+
|
|
3
|
+
For signed weights, ``|a-b| = |a| + |b| - w(a,b)``, where ``w`` is
|
|
4
|
+
twice the smaller magnitude when the signs agree, and zero otherwise.
|
|
5
|
+
Product bond mass is constant under a bijection. Consequently the only
|
|
6
|
+
prefix images needed for future pair rewards are those on the source-bond
|
|
7
|
+
frontier. The used-image subset still retains every assigned image.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
from numbers import Integral
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
from .suffix_spectrum import SuffixCostSpectrum, _StateLimit
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RewardFrontierSpectrum(SuffixCostSpectrum):
|
|
21
|
+
"""Bounded exact CD supports computed from sparse preserved-bond rewards.
|
|
22
|
+
|
|
23
|
+
Domains and unary costs are static within this prepared instance. Source
|
|
24
|
+
rows retain their supplied order. Canonicalization remains in reconstruction
|
|
25
|
+
so the memoized support does not depend on a prefix-specific subgroup.
|
|
26
|
+
An excessive reward envelope or interrupted preparation is a fallback,
|
|
27
|
+
never an infeasibility certificate.
|
|
28
|
+
|
|
29
|
+
At each assignment the increment is unary cost plus source bond mass to
|
|
30
|
+
earlier rows plus product bond mass to used images, minus the conserved
|
|
31
|
+
bond reward. This equals the literal sum of nonnegative pair disagreements,
|
|
32
|
+
so distance-cap pruning remains valid without retaining a future-cost table.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, *args, max_reward=4096, **kwargs):
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
if (
|
|
38
|
+
isinstance(max_reward, bool)
|
|
39
|
+
or not isinstance(max_reward, Integral)
|
|
40
|
+
or max_reward < 1
|
|
41
|
+
):
|
|
42
|
+
raise ValueError("max_reward must be a positive integer")
|
|
43
|
+
if len(set(self.rows)) != self.n or len(set(self.columns)) != self.n:
|
|
44
|
+
raise ValueError("residual rows and columns must be distinct")
|
|
45
|
+
a = self.a[np.ix_(self.rows, self.rows)]
|
|
46
|
+
b = self.b[np.ix_(self.columns, self.columns)]
|
|
47
|
+
if (
|
|
48
|
+
not np.array_equal(a, a.T)
|
|
49
|
+
or not np.array_equal(b, b.T)
|
|
50
|
+
or np.any(np.diag(a))
|
|
51
|
+
or np.any(np.diag(b))
|
|
52
|
+
):
|
|
53
|
+
raise ValueError("reward spectra require symmetric zero-diagonal matrices")
|
|
54
|
+
# Python integers avoid overflowing sums and signed magnitudes.
|
|
55
|
+
self.source = tuple(tuple(int(x) for x in row) for row in a)
|
|
56
|
+
self.product = tuple(tuple(int(x) for x in row) for row in b)
|
|
57
|
+
self.domain_masks = tuple(
|
|
58
|
+
sum(1 << col for col in range(self.n) if self.allowed[row, col])
|
|
59
|
+
for row in range(self.n)
|
|
60
|
+
)
|
|
61
|
+
shifts = tuple(
|
|
62
|
+
max(
|
|
63
|
+
(
|
|
64
|
+
int(self.unary[row, col])
|
|
65
|
+
for col in range(self.n)
|
|
66
|
+
if self.allowed[row, col]
|
|
67
|
+
),
|
|
68
|
+
default=0,
|
|
69
|
+
)
|
|
70
|
+
for row in range(self.n)
|
|
71
|
+
)
|
|
72
|
+
self.unary_costs = tuple(
|
|
73
|
+
tuple(int(value) for value in row) for row in self.unary
|
|
74
|
+
)
|
|
75
|
+
self.prefix_mass = tuple(
|
|
76
|
+
sum(abs(self.source[row][depth]) for row in range(depth))
|
|
77
|
+
for depth in range(self.n)
|
|
78
|
+
)
|
|
79
|
+
self.product_mass_edges = tuple(
|
|
80
|
+
tuple(
|
|
81
|
+
(1 << image, abs(weight)) for image, weight in enumerate(row) if weight
|
|
82
|
+
)
|
|
83
|
+
for row in self.product
|
|
84
|
+
)
|
|
85
|
+
self._product_mass_cache = {}
|
|
86
|
+
self.constant = sum(shifts) + sum(
|
|
87
|
+
abs(self.source[i][k]) + abs(self.product[i][k])
|
|
88
|
+
for i in range(self.n)
|
|
89
|
+
for k in range(i + 1, self.n)
|
|
90
|
+
)
|
|
91
|
+
self.max_reward = int(max_reward)
|
|
92
|
+
self.frontiers = tuple(
|
|
93
|
+
tuple(
|
|
94
|
+
row
|
|
95
|
+
for row in range(depth)
|
|
96
|
+
if any(self.source[row][next_row] for next_row in range(depth, self.n))
|
|
97
|
+
)
|
|
98
|
+
for depth in range(self.n + 1)
|
|
99
|
+
)
|
|
100
|
+
self.max_frontier = max(map(len, self.frontiers), default=0)
|
|
101
|
+
self._advance = tuple(
|
|
102
|
+
tuple(
|
|
103
|
+
-1 if row == depth else self.frontiers[depth].index(row)
|
|
104
|
+
for row in self.frontiers[depth + 1]
|
|
105
|
+
)
|
|
106
|
+
for depth in range(self.n)
|
|
107
|
+
)
|
|
108
|
+
self._incident = tuple(
|
|
109
|
+
tuple(
|
|
110
|
+
(position, self.source[row][depth])
|
|
111
|
+
for position, row in enumerate(self.frontiers[depth])
|
|
112
|
+
if self.source[row][depth]
|
|
113
|
+
)
|
|
114
|
+
for depth in range(self.n)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _reward(source, product):
|
|
119
|
+
if (source > 0 and product > 0) or (source < 0 and product < 0):
|
|
120
|
+
return 2 * min(abs(source), abs(product))
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
def _used_mass(self, used, col):
|
|
124
|
+
if not self.product_mass_edges[col]:
|
|
125
|
+
return 0
|
|
126
|
+
key = used, col
|
|
127
|
+
value = self._product_mass_cache.get(key)
|
|
128
|
+
if value is None:
|
|
129
|
+
value = sum(
|
|
130
|
+
weight for bit, weight in self.product_mass_edges[col] if used & bit
|
|
131
|
+
)
|
|
132
|
+
self._product_mass_cache[key] = value
|
|
133
|
+
return value
|
|
134
|
+
|
|
135
|
+
def _frontier_state(self, depth, used, frontier):
|
|
136
|
+
self._check()
|
|
137
|
+
key = depth, used, frontier
|
|
138
|
+
cached = self.memo.get(key)
|
|
139
|
+
if cached is not None:
|
|
140
|
+
return key, cached[0]
|
|
141
|
+
self._tick()
|
|
142
|
+
if depth == self.n:
|
|
143
|
+
self.memo[key] = (1, ())
|
|
144
|
+
return key, 1
|
|
145
|
+
support, branches = 0, []
|
|
146
|
+
candidates = self.domain_masks[depth] & ~used
|
|
147
|
+
while candidates:
|
|
148
|
+
self._check()
|
|
149
|
+
bit = candidates & -candidates
|
|
150
|
+
col = bit.bit_length() - 1
|
|
151
|
+
candidates ^= bit
|
|
152
|
+
increment = (
|
|
153
|
+
self.unary_costs[depth][col]
|
|
154
|
+
+ self.prefix_mass[depth]
|
|
155
|
+
+ self._used_mass(used, col)
|
|
156
|
+
)
|
|
157
|
+
for position, weight in self._incident[depth]:
|
|
158
|
+
increment -= self._reward(weight, self.product[frontier[position]][col])
|
|
159
|
+
if increment < 0:
|
|
160
|
+
raise ArithmeticError(
|
|
161
|
+
"Sparse reward increment disagrees with a nonnegative CD"
|
|
162
|
+
)
|
|
163
|
+
if increment > self.max_cost:
|
|
164
|
+
continue
|
|
165
|
+
child_frontier = tuple(
|
|
166
|
+
col if position == -1 else frontier[position]
|
|
167
|
+
for position in self._advance[depth]
|
|
168
|
+
)
|
|
169
|
+
child_key, child_support = self._frontier_state(
|
|
170
|
+
depth + 1, used | bit, child_frontier
|
|
171
|
+
)
|
|
172
|
+
if child_support:
|
|
173
|
+
support |= child_support << increment
|
|
174
|
+
branches.append((col, increment, child_key))
|
|
175
|
+
self.transitions += 1
|
|
176
|
+
support &= (1 << (self.max_cost + 1)) - 1
|
|
177
|
+
self.memo[key] = support, tuple(branches)
|
|
178
|
+
return key, support
|
|
179
|
+
|
|
180
|
+
def prepare(self):
|
|
181
|
+
"""Prepare exact supports in the admitted envelope, or request fallback."""
|
|
182
|
+
if self._prepared:
|
|
183
|
+
return not self._prepare_failed
|
|
184
|
+
self._prepared = True
|
|
185
|
+
self._prepare_started = time.perf_counter()
|
|
186
|
+
self.support = 0
|
|
187
|
+
if self.constant > self.max_reward:
|
|
188
|
+
self._prepare_failed = True
|
|
189
|
+
return False
|
|
190
|
+
try:
|
|
191
|
+
self.root_key, self.support = self._frontier_state(0, 0, ())
|
|
192
|
+
return True
|
|
193
|
+
except _StateLimit:
|
|
194
|
+
self._prepare_failed = True
|
|
195
|
+
self.memo.clear()
|
|
196
|
+
self._product_mass_cache.clear()
|
|
197
|
+
self.support = 0
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
__all__ = ["RewardFrontierSpectrum"]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Bounded exact separator lower bound for residual weighted assignments.
|
|
2
|
+
|
|
3
|
+
This is an optimization oracle, not a shell enumerator. It returns the exact
|
|
4
|
+
minimum residual cost when it finishes within its state budget; callers may
|
|
5
|
+
only use that value as an admissible lower bound. If the budget is exceeded it
|
|
6
|
+
returns ``None`` and the ordinary PABS bounds remain in force.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import itertools
|
|
12
|
+
import math
|
|
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
|
+
def _components(rows, adjacency, removed):
|
|
24
|
+
remaining = set(rows) - set(removed)
|
|
25
|
+
result = []
|
|
26
|
+
while remaining:
|
|
27
|
+
root = min(remaining)
|
|
28
|
+
component = {root}
|
|
29
|
+
stack = [root]
|
|
30
|
+
remaining.remove(root)
|
|
31
|
+
while stack:
|
|
32
|
+
node = stack.pop()
|
|
33
|
+
neighbors = [other for other in remaining if adjacency[node, other]]
|
|
34
|
+
for other in neighbors:
|
|
35
|
+
remaining.remove(other)
|
|
36
|
+
component.add(other)
|
|
37
|
+
stack.append(other)
|
|
38
|
+
result.append(tuple(sorted(component)))
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _balanced_component_split(components, n, alpha):
|
|
43
|
+
"""Group disconnected components into two balanced nonempty sides."""
|
|
44
|
+
if len(components) < 2:
|
|
45
|
+
return None
|
|
46
|
+
limit = math.floor(alpha * n)
|
|
47
|
+
best = None
|
|
48
|
+
# Components are disjoint, so a subset determines one side of the cut.
|
|
49
|
+
for mask in range(1, (1 << len(components)) - 1):
|
|
50
|
+
if not (mask & 1):
|
|
51
|
+
continue # remove the symmetric duplicate
|
|
52
|
+
left = tuple(
|
|
53
|
+
sorted(i for k, c in enumerate(components) if mask >> k & 1 for i in c)
|
|
54
|
+
)
|
|
55
|
+
right = tuple(
|
|
56
|
+
sorted(
|
|
57
|
+
i for k, c in enumerate(components) if not (mask >> k & 1) for i in c
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
if not left or not right or max(len(left), len(right)) > limit:
|
|
61
|
+
continue
|
|
62
|
+
candidate = (max(len(left), len(right)), left, right)
|
|
63
|
+
if best is None or candidate < best:
|
|
64
|
+
best = candidate
|
|
65
|
+
return None if best is None else (best[1], best[2])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _find_separator(rows, adjacency, max_separator_size, alpha):
|
|
69
|
+
n = len(rows)
|
|
70
|
+
for size in range(1, min(max_separator_size, n - 2) + 1):
|
|
71
|
+
for separator in itertools.combinations(rows, size):
|
|
72
|
+
components = _components(rows, adjacency, separator)
|
|
73
|
+
split = _balanced_component_split(components, n, alpha)
|
|
74
|
+
if split is not None:
|
|
75
|
+
return tuple(separator), split
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def minimum_separator_residual_cost( # noqa: C901
|
|
80
|
+
a,
|
|
81
|
+
b,
|
|
82
|
+
rows,
|
|
83
|
+
columns,
|
|
84
|
+
unary,
|
|
85
|
+
allowed,
|
|
86
|
+
*,
|
|
87
|
+
deadline=None,
|
|
88
|
+
max_separator_size=2,
|
|
89
|
+
balance_alpha=2 / 3,
|
|
90
|
+
max_states=20_000,
|
|
91
|
+
base_case=4,
|
|
92
|
+
):
|
|
93
|
+
"""Return an exact minimum residual QAP cost in integer quarter units.
|
|
94
|
+
|
|
95
|
+
``unary[x, y]`` contains all costs between residual assignment ``x -> y``
|
|
96
|
+
and already-fixed rows. Pair costs use the mapper's exact absolute edge
|
|
97
|
+
disagreement ``abs(a[i,k] - b[j,l])``; one undirected pair is counted once.
|
|
98
|
+
Element compatibility and propagated domains are represented by ``allowed``.
|
|
99
|
+
|
|
100
|
+
A separator's assigned image is absorbed into child unary costs. Once the
|
|
101
|
+
source sides are disconnected, their cross cost is independent of the
|
|
102
|
+
particular child maps and is the product cut insertion cost. This is the
|
|
103
|
+
SR-GED decomposition specialized to the mapper's fixed-cardinality,
|
|
104
|
+
weighted assignment objective.
|
|
105
|
+
"""
|
|
106
|
+
rows, columns = tuple(rows), tuple(columns)
|
|
107
|
+
unary = np.asarray(unary, dtype=np.int64)
|
|
108
|
+
allowed = np.asarray(allowed, dtype=bool)
|
|
109
|
+
if (
|
|
110
|
+
len(rows) != len(columns)
|
|
111
|
+
or unary.shape != allowed.shape
|
|
112
|
+
or unary.shape != (len(rows), len(columns))
|
|
113
|
+
):
|
|
114
|
+
raise ValueError("residual arrays must be square and match row/column sets")
|
|
115
|
+
if not rows:
|
|
116
|
+
return 0
|
|
117
|
+
if len(rows) > 12:
|
|
118
|
+
return None
|
|
119
|
+
adjacency = np.asarray(a != 0, dtype=bool)
|
|
120
|
+
states = 0
|
|
121
|
+
|
|
122
|
+
def tick():
|
|
123
|
+
nonlocal states
|
|
124
|
+
check_deadline(deadline)
|
|
125
|
+
states += 1
|
|
126
|
+
if states > max_states:
|
|
127
|
+
raise _StateLimit
|
|
128
|
+
|
|
129
|
+
def pair_cost(i, k, j, image):
|
|
130
|
+
return abs(int(a[i, k]) - int(b[j, image]))
|
|
131
|
+
|
|
132
|
+
def brute(rset, cset, costs, domains):
|
|
133
|
+
best = None
|
|
134
|
+
for permutation in itertools.permutations(range(len(cset))):
|
|
135
|
+
tick()
|
|
136
|
+
if any(not domains[x, permutation[x]] for x in range(len(rset))):
|
|
137
|
+
continue
|
|
138
|
+
value = sum(int(costs[x, permutation[x]]) for x in range(len(rset)))
|
|
139
|
+
for x in range(len(rset)):
|
|
140
|
+
for y in range(x + 1, len(rset)):
|
|
141
|
+
value += pair_cost(
|
|
142
|
+
rset[x], rset[y], cset[permutation[x]], cset[permutation[y]]
|
|
143
|
+
)
|
|
144
|
+
best = value if best is None else min(best, value)
|
|
145
|
+
return best
|
|
146
|
+
|
|
147
|
+
def solve(rset, cset, costs, domains):
|
|
148
|
+
tick()
|
|
149
|
+
n = len(rset)
|
|
150
|
+
if n <= base_case:
|
|
151
|
+
return brute(rset, cset, costs, domains)
|
|
152
|
+
found = _find_separator(rset, adjacency, max_separator_size, balance_alpha)
|
|
153
|
+
if found is None:
|
|
154
|
+
return brute(rset, cset, costs, domains)
|
|
155
|
+
separator, (left_rows, right_rows) = found
|
|
156
|
+
separator_positions = [rset.index(i) for i in separator]
|
|
157
|
+
best = None
|
|
158
|
+
for chosen in itertools.permutations(cset, len(separator)):
|
|
159
|
+
tick()
|
|
160
|
+
if any(
|
|
161
|
+
not domains[pos, cset.index(j)]
|
|
162
|
+
for pos, j in zip(separator_positions, chosen)
|
|
163
|
+
):
|
|
164
|
+
continue
|
|
165
|
+
chosen_map = dict(zip(separator, chosen))
|
|
166
|
+
separator_cost = sum(
|
|
167
|
+
int(costs[pos, cset.index(chosen_map[row])])
|
|
168
|
+
for pos, row in zip(separator_positions, separator)
|
|
169
|
+
)
|
|
170
|
+
for p, row in enumerate(separator):
|
|
171
|
+
for q in range(p + 1, len(separator)):
|
|
172
|
+
other = separator[q]
|
|
173
|
+
separator_cost += pair_cost(
|
|
174
|
+
row, other, chosen_map[row], chosen_map[other]
|
|
175
|
+
)
|
|
176
|
+
free_columns = tuple(j for j in cset if j not in set(chosen))
|
|
177
|
+
left_size = len(left_rows)
|
|
178
|
+
if left_size > len(free_columns):
|
|
179
|
+
continue
|
|
180
|
+
left_positions = [rset.index(i) for i in left_rows]
|
|
181
|
+
right_positions = [rset.index(i) for i in right_rows]
|
|
182
|
+
for left_columns in itertools.combinations(free_columns, left_size):
|
|
183
|
+
tick()
|
|
184
|
+
right_columns = tuple(
|
|
185
|
+
j for j in free_columns if j not in set(left_columns)
|
|
186
|
+
)
|
|
187
|
+
if len(right_columns) != len(right_rows):
|
|
188
|
+
continue
|
|
189
|
+
left_costs = (
|
|
190
|
+
np.asarray(
|
|
191
|
+
[
|
|
192
|
+
[costs[x, cset.index(j)] for j in left_columns]
|
|
193
|
+
for x in left_positions
|
|
194
|
+
],
|
|
195
|
+
dtype=np.int64,
|
|
196
|
+
)
|
|
197
|
+
.reshape(len(left_rows), len(left_columns))
|
|
198
|
+
.copy()
|
|
199
|
+
)
|
|
200
|
+
right_costs = (
|
|
201
|
+
np.asarray(
|
|
202
|
+
[
|
|
203
|
+
[costs[x, cset.index(j)] for j in right_columns]
|
|
204
|
+
for x in right_positions
|
|
205
|
+
],
|
|
206
|
+
dtype=np.int64,
|
|
207
|
+
)
|
|
208
|
+
.reshape(len(right_rows), len(right_columns))
|
|
209
|
+
.copy()
|
|
210
|
+
)
|
|
211
|
+
left_allowed = np.asarray(
|
|
212
|
+
[
|
|
213
|
+
[domains[x, cset.index(j)] for j in left_columns]
|
|
214
|
+
for x in left_positions
|
|
215
|
+
],
|
|
216
|
+
dtype=bool,
|
|
217
|
+
).reshape(len(left_rows), len(left_columns))
|
|
218
|
+
right_allowed = np.asarray(
|
|
219
|
+
[
|
|
220
|
+
[domains[x, cset.index(j)] for j in right_columns]
|
|
221
|
+
for x in right_positions
|
|
222
|
+
],
|
|
223
|
+
dtype=bool,
|
|
224
|
+
).reshape(len(right_rows), len(right_columns))
|
|
225
|
+
for x, row in enumerate(left_rows):
|
|
226
|
+
for y, image in enumerate(left_columns):
|
|
227
|
+
left_costs[x, y] += sum(
|
|
228
|
+
pair_cost(row, sep_row, image, chosen_map[sep_row])
|
|
229
|
+
for sep_row in separator
|
|
230
|
+
)
|
|
231
|
+
for x, row in enumerate(right_rows):
|
|
232
|
+
for y, image in enumerate(right_columns):
|
|
233
|
+
right_costs[x, y] += sum(
|
|
234
|
+
pair_cost(row, sep_row, image, chosen_map[sep_row])
|
|
235
|
+
for sep_row in separator
|
|
236
|
+
)
|
|
237
|
+
left_value = solve(left_rows, left_columns, left_costs, left_allowed)
|
|
238
|
+
if left_value is None:
|
|
239
|
+
continue
|
|
240
|
+
right_value = solve(
|
|
241
|
+
right_rows, right_columns, right_costs, right_allowed
|
|
242
|
+
)
|
|
243
|
+
if right_value is None:
|
|
244
|
+
continue
|
|
245
|
+
cut = sum(
|
|
246
|
+
abs(int(b[j, k])) for j in left_columns for k in right_columns
|
|
247
|
+
)
|
|
248
|
+
total = separator_cost + left_value + right_value + cut
|
|
249
|
+
best = total if best is None else min(best, total)
|
|
250
|
+
return best
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
return solve(rows, columns, unary, allowed)
|
|
254
|
+
except _StateLimit:
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
__all__ = ["minimum_separator_residual_cost"]
|