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,30 @@
|
|
|
1
|
+
"""Result container and graph-label reconstruction for exact enumeration."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class EnumerationResult:
|
|
9
|
+
"""Symmetry-distinct exact optima for a kernel."""
|
|
10
|
+
|
|
11
|
+
cost: float
|
|
12
|
+
mappings: List[List[int]]
|
|
13
|
+
sub_mappings: List[Dict[int, int]]
|
|
14
|
+
results: List[dict]
|
|
15
|
+
proven_optimal: bool = True
|
|
16
|
+
enumeration_complete: bool = True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def mapping_to_lgp(lgp, mapping):
|
|
20
|
+
"""Return a resolved graph pair whose labels encode ``mapping``."""
|
|
21
|
+
out = [lgp[0].copy(), lgp[1].copy()]
|
|
22
|
+
labels0 = list(range(1, len(mapping) + 1))
|
|
23
|
+
labels1 = [0] * len(mapping)
|
|
24
|
+
for i, p in enumerate(mapping):
|
|
25
|
+
labels1[p] = i + 1
|
|
26
|
+
out[0].labels = labels0
|
|
27
|
+
out[1].labels = labels1
|
|
28
|
+
out[0].build_label2idxs()
|
|
29
|
+
out[1].build_label2idxs()
|
|
30
|
+
return out
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reduced uncertainty subproblems derived from supplied SLAP mapping results.
|
|
3
|
+
|
|
4
|
+
The supplied heuristic mappings determine which atom assignments agree and
|
|
5
|
+
which vary. ``extract_kernel`` holds agreeing assignments fixed and builds
|
|
6
|
+
candidate domains for the remaining atoms, with fallback label-class domains
|
|
7
|
+
and closure of small same-element uncertain blocks where applicable. Agreement
|
|
8
|
+
among supplied SLAP results is not a proof that an assignment is forced across
|
|
9
|
+
all global optima.
|
|
10
|
+
|
|
11
|
+
``Kernel`` is this reduced problem, not a mapping algorithm. Branch-and-bound
|
|
12
|
+
or optional MILP solves its remaining assignments exactly while respecting the
|
|
13
|
+
fixed assignments and candidate domains. ``apply_kernel_solution`` combines
|
|
14
|
+
the subsolution with the fixed part. An exact kernel solution is optimal within
|
|
15
|
+
the supplied restrictions; a full-space global optimum requires a full-space
|
|
16
|
+
search or an independent global proof. WL colour agreement alone is not used
|
|
17
|
+
as a certainty test because a reaction can change an atom's environment.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Dict, List, Optional, Set
|
|
24
|
+
|
|
25
|
+
from ...slap.lap import recover_mapping
|
|
26
|
+
|
|
27
|
+
_MAX_CLOSED_ELEMENT_BLOCK = 4
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Kernel:
|
|
32
|
+
"""Reduced atom-assignment problem with fixed assignments and candidate domains.
|
|
33
|
+
|
|
34
|
+
:ivar r_idx, p_idx: Original reactant/product atom indices that remain uncertain.
|
|
35
|
+
:vartype r_idx, p_idx: list[int]
|
|
36
|
+
:ivar r_colors, p_colors: Atom type (atomic number) of each kernel atom; reactant atom
|
|
37
|
+
``r_idx[a]`` may map to product atom ``p_idx[b]`` only if
|
|
38
|
+
``r_colors[a] == p_colors[b]``.
|
|
39
|
+
:vartype r_colors, p_colors: list[int]
|
|
40
|
+
:ivar fixed_mapping: Assignments held fixed: original reactant index -> product index.
|
|
41
|
+
:vartype fixed_mapping: dict[int, int]
|
|
42
|
+
:ivar candidate_images: Allowed product atom indices for each uncertain atom in ``r_idx``.
|
|
43
|
+
Extraction derives these from supplied SLAP results, with fallback
|
|
44
|
+
label-class domains and closure of small uncertain element blocks.
|
|
45
|
+
The exact kernel solver stays inside the supplied candidate sets.
|
|
46
|
+
:vartype candidate_images: list[list[int]]
|
|
47
|
+
:ivar lgp: The original reactant/product graph pair (for adjacency look-ups).
|
|
48
|
+
:vartype lgp: list
|
|
49
|
+
:ivar binary: Whether bond orders are binarised.
|
|
50
|
+
:vartype binary: bool
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
r_idx: List[int]
|
|
54
|
+
p_idx: List[int]
|
|
55
|
+
r_colors: List[int]
|
|
56
|
+
p_colors: List[int]
|
|
57
|
+
fixed_mapping: Dict[int, int]
|
|
58
|
+
lgp: list
|
|
59
|
+
binary: bool = False
|
|
60
|
+
candidate_images: Optional[List[List[int]]] = None
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def size(self) -> int:
|
|
64
|
+
"""Number of uncertain reactant atoms (== uncertain product atoms)."""
|
|
65
|
+
return len(self.r_idx)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def is_trivial(self) -> bool:
|
|
69
|
+
"""True when the supplied kernel has no uncertain atoms to optimize."""
|
|
70
|
+
return self.size == 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _atomic_numbers(lg):
|
|
74
|
+
nums = lg.props.get("atomic numbers")
|
|
75
|
+
if nums is None:
|
|
76
|
+
nums = list(lg.labels)
|
|
77
|
+
return list(nums)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def extract_kernel(results, lgp, binary=False):
|
|
81
|
+
"""Extract a restricted uncertainty problem from supplied SLAP results.
|
|
82
|
+
|
|
83
|
+
Agreement among the supplied heuristic mappings defines fixed assignments;
|
|
84
|
+
variation defines uncertain domains. These restrictions are local to the
|
|
85
|
+
supplied results and do not establish certainty over every global optimum.
|
|
86
|
+
|
|
87
|
+
:param results: Best observed results from :meth:`GraphMatcher.get_maps`; each must carry a
|
|
88
|
+
fully resolved ``"lgp"`` graph pair.
|
|
89
|
+
:type results: list[dict]
|
|
90
|
+
:param lgp: The original reactant/product graph pair (equal atom counts).
|
|
91
|
+
:type lgp: list[LabeledGraph]
|
|
92
|
+
:param binary: Whether bond orders are binarised (recorded on the kernel).
|
|
93
|
+
:type binary: bool, optional
|
|
94
|
+
|
|
95
|
+
:rtype: Kernel
|
|
96
|
+
"""
|
|
97
|
+
n = len(lgp[0].labels)
|
|
98
|
+
mappings = [recover_mapping(r["lgp"]) for r in results]
|
|
99
|
+
if not mappings:
|
|
100
|
+
mappings = [list(range(n))]
|
|
101
|
+
|
|
102
|
+
images = [set(m[i] for m in mappings) for i in range(n)]
|
|
103
|
+
elements_r = _atomic_numbers(lgp[0])
|
|
104
|
+
elements_p = _atomic_numbers(lgp[1])
|
|
105
|
+
fixed_mapping, r_idx, p_idx, candidate_images = _compact_kernel_parts(images)
|
|
106
|
+
|
|
107
|
+
if not _kernel_parts_are_valid(fixed_mapping, r_idx, p_idx, candidate_images):
|
|
108
|
+
images = _label_class_candidate_images(results, lgp, elements_r, elements_p)
|
|
109
|
+
fixed_mapping, r_idx, p_idx, candidate_images = _propagated_kernel_parts(images)
|
|
110
|
+
|
|
111
|
+
candidate_images = _close_small_element_blocks(
|
|
112
|
+
r_idx,
|
|
113
|
+
p_idx,
|
|
114
|
+
candidate_images,
|
|
115
|
+
elements_r,
|
|
116
|
+
elements_p,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return Kernel(
|
|
120
|
+
r_idx=r_idx,
|
|
121
|
+
p_idx=p_idx,
|
|
122
|
+
r_colors=[elements_r[i] for i in r_idx],
|
|
123
|
+
p_colors=[elements_p[p] for p in p_idx],
|
|
124
|
+
fixed_mapping=fixed_mapping,
|
|
125
|
+
lgp=lgp,
|
|
126
|
+
binary=binary,
|
|
127
|
+
candidate_images=candidate_images,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _compact_kernel_parts(images):
|
|
132
|
+
fixed_mapping = {}
|
|
133
|
+
r_idx = []
|
|
134
|
+
for i, candidates in enumerate(images):
|
|
135
|
+
if len(candidates) == 1:
|
|
136
|
+
fixed_mapping[i] = next(iter(candidates))
|
|
137
|
+
else:
|
|
138
|
+
r_idx.append(i)
|
|
139
|
+
|
|
140
|
+
p_set = set()
|
|
141
|
+
for i in r_idx:
|
|
142
|
+
p_set.update(images[i])
|
|
143
|
+
p_idx = sorted(p_set)
|
|
144
|
+
candidate_images = [sorted(images[i]) for i in r_idx]
|
|
145
|
+
return fixed_mapping, r_idx, p_idx, candidate_images
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _kernel_parts_are_valid(fixed_mapping, r_idx, p_idx, candidate_images):
|
|
149
|
+
if len(p_idx) != len(r_idx):
|
|
150
|
+
return False
|
|
151
|
+
p_set = set(p_idx)
|
|
152
|
+
return all(
|
|
153
|
+
candidates and set(candidates) <= p_set for candidates in candidate_images
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _label_class_candidate_images(results, lgp, elements_r, elements_p):
|
|
158
|
+
n = len(lgp[0].labels)
|
|
159
|
+
images: List[Set[int]] = [set() for _ in range(n)]
|
|
160
|
+
for result in results:
|
|
161
|
+
left, right = result["lgp"]
|
|
162
|
+
fallback = recover_mapping(result["lgp"])
|
|
163
|
+
for i, label in enumerate(left.labels):
|
|
164
|
+
for p in right.label2idxs.get(label, []):
|
|
165
|
+
if 0 <= p < n and elements_r[i] == elements_p[p]:
|
|
166
|
+
images[i].add(p)
|
|
167
|
+
p = fallback[i] if i < len(fallback) else -1
|
|
168
|
+
if 0 <= p < n and elements_r[i] == elements_p[p]:
|
|
169
|
+
images[i].add(p)
|
|
170
|
+
if not results:
|
|
171
|
+
images = [{i} for i in range(n)]
|
|
172
|
+
return images
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _propagated_kernel_parts(images):
|
|
176
|
+
images = [set(candidates) for candidates in images]
|
|
177
|
+
fixed_mapping = {}
|
|
178
|
+
unresolved = set(range(len(images)))
|
|
179
|
+
used_products = set()
|
|
180
|
+
changed = True
|
|
181
|
+
while changed:
|
|
182
|
+
changed = False
|
|
183
|
+
for i in list(unresolved):
|
|
184
|
+
images[i].difference_update(used_products)
|
|
185
|
+
if len(images[i]) != 1:
|
|
186
|
+
continue
|
|
187
|
+
p = next(iter(images[i]))
|
|
188
|
+
fixed_mapping[i] = p
|
|
189
|
+
used_products.add(p)
|
|
190
|
+
unresolved.remove(i)
|
|
191
|
+
for j in unresolved:
|
|
192
|
+
images[j].discard(p)
|
|
193
|
+
changed = True
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
r_idx = sorted(unresolved)
|
|
197
|
+
p_set = set()
|
|
198
|
+
for i in r_idx:
|
|
199
|
+
p_set.update(images[i])
|
|
200
|
+
p_idx = sorted(p_set)
|
|
201
|
+
candidate_images = [sorted(images[i]) for i in r_idx]
|
|
202
|
+
return fixed_mapping, r_idx, p_idx, candidate_images
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _close_small_element_blocks(r_idx, p_idx, candidate_images, elements_r, elements_p):
|
|
206
|
+
"""Let exact mode fully permute small same-element uncertain blocks."""
|
|
207
|
+
if not candidate_images:
|
|
208
|
+
return candidate_images
|
|
209
|
+
|
|
210
|
+
out = [sorted(set(candidates)) for candidates in candidate_images]
|
|
211
|
+
positions_by_element = {}
|
|
212
|
+
products_by_element = {}
|
|
213
|
+
for a, i in enumerate(r_idx):
|
|
214
|
+
positions_by_element.setdefault(elements_r[i], []).append(a)
|
|
215
|
+
for p in p_idx:
|
|
216
|
+
products_by_element.setdefault(elements_p[p], []).append(p)
|
|
217
|
+
|
|
218
|
+
for element, positions in positions_by_element.items():
|
|
219
|
+
pool = sorted(products_by_element.get(element, []))
|
|
220
|
+
if (
|
|
221
|
+
not pool
|
|
222
|
+
or len(pool) != len(positions)
|
|
223
|
+
or len(pool) > _MAX_CLOSED_ELEMENT_BLOCK
|
|
224
|
+
):
|
|
225
|
+
continue
|
|
226
|
+
pool_set = set(pool)
|
|
227
|
+
if any(not set(out[a]) <= pool_set for a in positions):
|
|
228
|
+
continue
|
|
229
|
+
for a in positions:
|
|
230
|
+
out[a] = pool
|
|
231
|
+
return out
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def apply_kernel_solution(kernel, sub_mapping):
|
|
235
|
+
"""Combine a kernel sub-mapping with its fixed assignments into a full mapping.
|
|
236
|
+
|
|
237
|
+
:param kernel:
|
|
238
|
+
:type kernel: Kernel
|
|
239
|
+
:param sub_mapping: Mapping over kernel positions: either a dict ``a -> b`` (positions into
|
|
240
|
+
``kernel.r_idx`` / ``kernel.p_idx``) or a sequence ``sub_mapping[a] = b``.
|
|
241
|
+
:type sub_mapping: dict[int, int] or sequence[int]
|
|
242
|
+
|
|
243
|
+
:return: ``mapping[i] = p`` over original atom indices.
|
|
244
|
+
:rtype: list[int]
|
|
245
|
+
"""
|
|
246
|
+
n = len(kernel.lgp[0].labels)
|
|
247
|
+
mapping = [-1] * n
|
|
248
|
+
for i, p in kernel.fixed_mapping.items():
|
|
249
|
+
mapping[i] = p
|
|
250
|
+
if isinstance(sub_mapping, dict):
|
|
251
|
+
items = sub_mapping.items()
|
|
252
|
+
else:
|
|
253
|
+
items = enumerate(sub_mapping)
|
|
254
|
+
for a, b in items:
|
|
255
|
+
mapping[kernel.r_idx[a]] = kernel.p_idx[b]
|
|
256
|
+
return mapping
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exact MILP/QAP formulation of the kernel mapping problem (PuLP + CBC backend).
|
|
3
|
+
|
|
4
|
+
The chemical-distance objective is a quadratic assignment problem (QAP). Over the
|
|
5
|
+
small uncertainty-region kernel (:mod:`mapper.exact.kernel`) we write it as a
|
|
6
|
+
Koopmans-Beckmann QAP and linearise it to a mixed-integer linear program:
|
|
7
|
+
|
|
8
|
+
* binary ``x[a,b] = 1`` iff kernel reactant atom ``a`` maps to kernel product
|
|
9
|
+
atom ``b`` (created only for element-compatible pairs);
|
|
10
|
+
* assignment constraints ``sum_b x[a,b] = 1`` and ``sum_a x[a,b] = 1``;
|
|
11
|
+
* products ``y[a,b,a',b'] = x[a,b] * x[a',b']`` linearised by
|
|
12
|
+
``y <= x[a,b]``, ``y <= x[a',b']``, ``y >= x[a,b] + x[a',b'] - 1``.
|
|
13
|
+
|
|
14
|
+
Writing the chemical distance ``1/2 * sum_{i,j} |A_R[i,j] - A_P[m(i),m(j)]|`` and
|
|
15
|
+
splitting atoms into certain/kernel, the certain-certain part is a constant, the
|
|
16
|
+
kernel-certain part becomes linear coefficients on ``x``, and the kernel-kernel
|
|
17
|
+
part becomes coefficients on ``y`` (the factor 1/2 and the two ordered directions
|
|
18
|
+
of each undirected edge cancel). CBC solves the MILP to proven optimality, so the
|
|
19
|
+
returned cost certifies the kernel optimum (see :mod:`mapper.exact.certificate`).
|
|
20
|
+
|
|
21
|
+
The solver is isolated behind :func:`solve_qap`; :func:`solve_kernel_milp` adapts
|
|
22
|
+
a :class:`~mapper.exact.kernel.Kernel` to it and returns the same
|
|
23
|
+
:class:`~mapper.exact.branching.KernelSolution` type as the orbital-branching
|
|
24
|
+
solver, so callers can use either interchangeably.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from typing import Dict, Tuple
|
|
30
|
+
|
|
31
|
+
import warnings
|
|
32
|
+
|
|
33
|
+
from ...slap.lap import _adjacency_and_elements
|
|
34
|
+
from .branching import KernelSolution
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import pulp
|
|
38
|
+
|
|
39
|
+
HAS_PULP = True
|
|
40
|
+
except Exception: # pragma: no cover - exercised only without pulp installed
|
|
41
|
+
HAS_PULP = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _cbc_solver():
|
|
45
|
+
"""Return a silent CBC solver, preferring the non-deprecated entry point."""
|
|
46
|
+
with warnings.catch_warnings():
|
|
47
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
48
|
+
try:
|
|
49
|
+
solver = pulp.COIN_CMD(msg=False)
|
|
50
|
+
if solver.available():
|
|
51
|
+
return solver
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
return pulp.PULP_CBC_CMD(msg=False)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def solve_qap(lin, quad, n, compatible, const=0.0):
|
|
58
|
+
"""Solve a linearised QAP assignment with PuLP/CBC.
|
|
59
|
+
|
|
60
|
+
:param lin: Linear cost ``lin[a, b]`` for assigning ``a -> b``.
|
|
61
|
+
:type lin: dict[(int, int), float]
|
|
62
|
+
:param quad: Quadratic cost ``quad[a, b, a', b']`` (defined for ``a < a'``) added when
|
|
63
|
+
both ``a -> b`` and ``a' -> b'`` hold.
|
|
64
|
+
:type quad: dict[(int, int, int, int), float]
|
|
65
|
+
:param n: Number of reactant positions (== product positions).
|
|
66
|
+
:type n: int
|
|
67
|
+
:param compatible: Allowed ``(a, b)`` assignments (element-compatible pairs).
|
|
68
|
+
:type compatible: set[(int, int)]
|
|
69
|
+
:param const: Constant added to the objective.
|
|
70
|
+
:type const: float
|
|
71
|
+
|
|
72
|
+
:return: **(cost, mapping, proven)** -- Optimal cost, the assignment ``a -> b``, and whether CBC proved
|
|
73
|
+
optimality.
|
|
74
|
+
:rtype: (float, dict[int, int], bool)
|
|
75
|
+
"""
|
|
76
|
+
if not HAS_PULP:
|
|
77
|
+
raise ImportError("pulp is required for the MILP/QAP solver")
|
|
78
|
+
|
|
79
|
+
prob = pulp.LpProblem("kernel_qap", pulp.LpMinimize)
|
|
80
|
+
|
|
81
|
+
# PuLP 3.x emits 4.0-migration DeprecationWarnings from the supported
|
|
82
|
+
# LpVariable API; silence that noise around variable construction.
|
|
83
|
+
with warnings.catch_warnings():
|
|
84
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
85
|
+
x = {
|
|
86
|
+
(a, b): pulp.LpVariable(f"x_{a}_{b}", cat="Binary") for (a, b) in compatible
|
|
87
|
+
}
|
|
88
|
+
y = {}
|
|
89
|
+
for (a, b, a2, b2), c in quad.items():
|
|
90
|
+
if (a, b) in x and (a2, b2) in x:
|
|
91
|
+
y[(a, b, a2, b2)] = pulp.LpVariable(
|
|
92
|
+
f"y_{a}_{b}_{a2}_{b2}", lowBound=0, upBound=1, cat="Continuous"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Objective.
|
|
96
|
+
obj = [lin[(a, b)] * x[(a, b)] for (a, b) in x if lin.get((a, b))]
|
|
97
|
+
obj += [quad[k] * y[k] for k in y if quad.get(k)]
|
|
98
|
+
prob += pulp.lpSum(obj) + const
|
|
99
|
+
|
|
100
|
+
# Assignment constraints.
|
|
101
|
+
for a in range(n):
|
|
102
|
+
prob += pulp.lpSum(x[(a, b)] for b in range(n) if (a, b) in x) == 1
|
|
103
|
+
for b in range(n):
|
|
104
|
+
prob += pulp.lpSum(x[(a, b)] for a in range(n) if (a, b) in x) == 1
|
|
105
|
+
|
|
106
|
+
# McCormick linearisation of the products.
|
|
107
|
+
for (a, b, a2, b2), yv in y.items():
|
|
108
|
+
prob += yv <= x[(a, b)]
|
|
109
|
+
prob += yv <= x[(a2, b2)]
|
|
110
|
+
prob += yv >= x[(a, b)] + x[(a2, b2)] - 1
|
|
111
|
+
|
|
112
|
+
prob.solve(_cbc_solver())
|
|
113
|
+
|
|
114
|
+
proven = pulp.LpStatus[prob.status] == "Optimal"
|
|
115
|
+
mapping = {}
|
|
116
|
+
for (a, b), var in x.items():
|
|
117
|
+
if var.value() is not None and var.value() > 0.5:
|
|
118
|
+
mapping[a] = b
|
|
119
|
+
cost = pulp.value(prob.objective)
|
|
120
|
+
if cost is None and proven and not obj:
|
|
121
|
+
cost = float(const)
|
|
122
|
+
if cost is None:
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
"MILP solver returned no objective value — problem may be infeasible. "
|
|
125
|
+
"Verify that element-compatible assignments exist for all kernel atoms."
|
|
126
|
+
)
|
|
127
|
+
return float(cost), mapping, proven
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def solve_kernel_milp(kernel): # noqa: C901
|
|
131
|
+
"""Solve a :class:`~mapper.exact.kernel.Kernel` exactly via MILP/QAP.
|
|
132
|
+
|
|
133
|
+
:return: With ``proven_optimal`` reflecting CBC's status.
|
|
134
|
+
:rtype: KernelSolution
|
|
135
|
+
"""
|
|
136
|
+
lgp = kernel.lgp
|
|
137
|
+
binary = kernel.binary
|
|
138
|
+
A, _ = _adjacency_and_elements(lgp[0], binary)
|
|
139
|
+
B, _ = _adjacency_and_elements(lgp[1], binary)
|
|
140
|
+
N = A.shape[0]
|
|
141
|
+
n = kernel.size
|
|
142
|
+
|
|
143
|
+
base_map = [-1] * N
|
|
144
|
+
for i, p in kernel.fixed_mapping.items():
|
|
145
|
+
base_map[i] = p
|
|
146
|
+
certain = [i for i in range(N) if base_map[i] != -1]
|
|
147
|
+
|
|
148
|
+
# Certain-certain constant (0.5 * ordered sum).
|
|
149
|
+
const = 0.0
|
|
150
|
+
for i in certain:
|
|
151
|
+
for j in certain:
|
|
152
|
+
const += abs(A[i, j] - B[base_map[i], base_map[j]])
|
|
153
|
+
const *= 0.5
|
|
154
|
+
|
|
155
|
+
if n == 0:
|
|
156
|
+
return KernelSolution(cost=const, sub_mappings=[{}], proven_optimal=True)
|
|
157
|
+
|
|
158
|
+
compatible = set()
|
|
159
|
+
lin: Dict[Tuple[int, int], float] = {}
|
|
160
|
+
for a in range(n):
|
|
161
|
+
i = kernel.r_idx[a]
|
|
162
|
+
for b in range(n):
|
|
163
|
+
if kernel.r_colors[a] != kernel.p_colors[b]:
|
|
164
|
+
continue
|
|
165
|
+
p = kernel.p_idx[b]
|
|
166
|
+
if (
|
|
167
|
+
kernel.candidate_images is not None
|
|
168
|
+
and p not in kernel.candidate_images[a]
|
|
169
|
+
):
|
|
170
|
+
continue
|
|
171
|
+
compatible.add((a, b))
|
|
172
|
+
# Kernel-certain: 0.5 * (ordered i,j + ordered j,i) = sum_j |A-B|.
|
|
173
|
+
lin[(a, b)] = sum(abs(A[i, j] - B[p, base_map[j]]) for j in certain)
|
|
174
|
+
|
|
175
|
+
quad: Dict[Tuple[int, int, int, int], float] = {}
|
|
176
|
+
for a in range(n):
|
|
177
|
+
i = kernel.r_idx[a]
|
|
178
|
+
for a2 in range(a + 1, n):
|
|
179
|
+
i2 = kernel.r_idx[a2]
|
|
180
|
+
for b in range(n):
|
|
181
|
+
if (a, b) not in compatible:
|
|
182
|
+
continue
|
|
183
|
+
p = kernel.p_idx[b]
|
|
184
|
+
for b2 in range(n):
|
|
185
|
+
if (a2, b2) not in compatible or b2 == b:
|
|
186
|
+
continue
|
|
187
|
+
p2 = kernel.p_idx[b2]
|
|
188
|
+
# 0.5 * (ordered (i,i') + (i',i)) = |A_R[i,i'] - A_P[p,p']|.
|
|
189
|
+
c = abs(A[i, i2] - B[p, p2])
|
|
190
|
+
if c:
|
|
191
|
+
quad[(a, b, a2, b2)] = c
|
|
192
|
+
|
|
193
|
+
cost, mapping, proven = solve_qap(lin, quad, n, compatible, const=const)
|
|
194
|
+
return KernelSolution(cost=cost, sub_mappings=[mapping], proven_optimal=proven)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Optional C++ search, explicit compilation, canonicalization, and orbit scheduling."""
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Explicitly build the optional native solver into an immutable, hashed artifact."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _verify_library(library):
|
|
19
|
+
"""Check loadability and ABI in a child so Windows can release the DLL file."""
|
|
20
|
+
script = (
|
|
21
|
+
"import ctypes, sys; library = ctypes.CDLL(sys.argv[1]); "
|
|
22
|
+
"abi = library.synkit_distance_abi(); "
|
|
23
|
+
"assert abi == 3, f'unsupported native ABI: {abi}'"
|
|
24
|
+
)
|
|
25
|
+
result = subprocess.run(
|
|
26
|
+
[sys.executable, "-c", script, str(library)], capture_output=True, text=True
|
|
27
|
+
)
|
|
28
|
+
if result.returncode:
|
|
29
|
+
raise RuntimeError(f"Native artifact cannot be loaded: {library}\n{result.stderr}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_native(output_dir, compiler="g++", *, native_arch=False):
|
|
33
|
+
"""Build and verify a source/compiler-bound optional C++ solver artifact."""
|
|
34
|
+
source = Path(__file__).with_name("native_distance.cpp")
|
|
35
|
+
compiler_path = shutil.which(compiler)
|
|
36
|
+
if compiler_path is None:
|
|
37
|
+
raise RuntimeError(f"C++17 compiler unavailable: {compiler}")
|
|
38
|
+
version = subprocess.run(
|
|
39
|
+
[compiler_path, "--version"], check=True, capture_output=True, text=True
|
|
40
|
+
).stdout
|
|
41
|
+
flags = ["-std=c++17", "-O3", "-Wall", "-Wextra"]
|
|
42
|
+
if sys.platform == "win32":
|
|
43
|
+
# MinGW runtime DLLs on PATH are not searched by modern ctypes loaders.
|
|
44
|
+
# Link their code into the artifact and explicitly export the C ABI.
|
|
45
|
+
flags += ["-shared", "-static", "-Wl,--export-all-symbols"]
|
|
46
|
+
suffix = ".dll"
|
|
47
|
+
elif sys.platform == "darwin":
|
|
48
|
+
flags += ["-fPIC", "-dynamiclib"]
|
|
49
|
+
suffix = ".dylib"
|
|
50
|
+
else:
|
|
51
|
+
flags += ["-fPIC", "-shared"]
|
|
52
|
+
suffix = ".so"
|
|
53
|
+
target_options = None
|
|
54
|
+
if native_arch:
|
|
55
|
+
flags += ["-march=native", "-mtune=native"]
|
|
56
|
+
# Include resolved CPU options in the cache key: the same source and
|
|
57
|
+
# literal -march=native flags can generate different code on another host.
|
|
58
|
+
target_options = subprocess.run(
|
|
59
|
+
[
|
|
60
|
+
compiler_path,
|
|
61
|
+
*flags,
|
|
62
|
+
"-Q",
|
|
63
|
+
"--help=target",
|
|
64
|
+
"-x",
|
|
65
|
+
"c++",
|
|
66
|
+
"-c",
|
|
67
|
+
"-",
|
|
68
|
+
"-o",
|
|
69
|
+
os.devnull,
|
|
70
|
+
],
|
|
71
|
+
check=True,
|
|
72
|
+
capture_output=True,
|
|
73
|
+
text=True,
|
|
74
|
+
input="",
|
|
75
|
+
).stdout
|
|
76
|
+
provenance = {
|
|
77
|
+
"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
|
|
78
|
+
"compiler": compiler_path,
|
|
79
|
+
"compiler_version": version,
|
|
80
|
+
"flags": flags,
|
|
81
|
+
"platform": sys.platform,
|
|
82
|
+
"machine": platform.machine(),
|
|
83
|
+
}
|
|
84
|
+
if native_arch:
|
|
85
|
+
provenance["resolved_target_options"] = target_options
|
|
86
|
+
provenance["portability"] = (
|
|
87
|
+
"requires the recorded host CPU instruction features"
|
|
88
|
+
)
|
|
89
|
+
build_key = hashlib.sha256(
|
|
90
|
+
json.dumps(provenance, sort_keys=True).encode()
|
|
91
|
+
).hexdigest()
|
|
92
|
+
output_dir = Path(output_dir).resolve()
|
|
93
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
library = output_dir / f"libsynkit_distance_{build_key[:20]}{suffix}"
|
|
95
|
+
manifest = library.with_suffix(".json")
|
|
96
|
+
if library.exists():
|
|
97
|
+
if not manifest.exists():
|
|
98
|
+
raise RuntimeError(f"Existing library lacks build provenance: {library}")
|
|
99
|
+
recorded = json.loads(manifest.read_text())
|
|
100
|
+
if (
|
|
101
|
+
recorded["build_key"] != build_key
|
|
102
|
+
or recorded["library_sha256"]
|
|
103
|
+
!= hashlib.sha256(library.read_bytes()).hexdigest()
|
|
104
|
+
):
|
|
105
|
+
raise RuntimeError(
|
|
106
|
+
f"Existing native artifact failed integrity verification: {library}"
|
|
107
|
+
)
|
|
108
|
+
_verify_library(library)
|
|
109
|
+
return library
|
|
110
|
+
# A distinct path avoids overwriting code already mapped into another process.
|
|
111
|
+
with tempfile.TemporaryDirectory(
|
|
112
|
+
prefix="synkit-build-", dir=output_dir
|
|
113
|
+
) as temporary:
|
|
114
|
+
candidate = Path(temporary) / f"kernel{suffix}"
|
|
115
|
+
result = subprocess.run(
|
|
116
|
+
[compiler_path, *flags, str(source), "-o", str(candidate)],
|
|
117
|
+
check=True,
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
)
|
|
121
|
+
_verify_library(candidate)
|
|
122
|
+
provenance.update(
|
|
123
|
+
build_key=build_key,
|
|
124
|
+
library=str(library),
|
|
125
|
+
library_sha256=hashlib.sha256(candidate.read_bytes()).hexdigest(),
|
|
126
|
+
compiler_stderr=result.stderr,
|
|
127
|
+
)
|
|
128
|
+
# Hard-link installation refuses to replace any concurrently installed binary.
|
|
129
|
+
try:
|
|
130
|
+
os.link(candidate, library)
|
|
131
|
+
except FileExistsError:
|
|
132
|
+
raise RuntimeError(
|
|
133
|
+
f"Concurrent build installed {library}; rerun to verify it"
|
|
134
|
+
)
|
|
135
|
+
manifest.write_text(json.dumps(provenance, indent=2) + "\n")
|
|
136
|
+
return library
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def main():
|
|
140
|
+
"""Build the optional C++ solver from an installed SynKit package."""
|
|
141
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
142
|
+
parser.add_argument("--output-dir", type=Path, default=Path(tempfile.gettempdir()) / "synkit-native")
|
|
143
|
+
parser.add_argument("--compiler", default="g++")
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
"--native-arch",
|
|
146
|
+
action="store_true",
|
|
147
|
+
help="Opt in to host CPU instructions; resulting library is host-specific.",
|
|
148
|
+
)
|
|
149
|
+
args = parser.parse_args()
|
|
150
|
+
print(build_native(args.output_dir, args.compiler, native_arch=args.native_arch))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
main()
|