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,274 @@
|
|
|
1
|
+
"""Secondary label metrics, structural classes and shared annotation policies.
|
|
2
|
+
|
|
3
|
+
Inputs must retain complete joint labels, not merely one representative per
|
|
4
|
+
changed-bond set. Reference annotations are revealed only to this evaluation
|
|
5
|
+
stage; they are never used to generate predictions or candidate sets.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from numbers import Integral
|
|
10
|
+
|
|
11
|
+
from .bonds import IncompleteSymmetry, ScoreWitness, bond_f1
|
|
12
|
+
from .identifiability import extract_label
|
|
13
|
+
from .orbits import SupportOrbitEvaluator
|
|
14
|
+
from ..solvers.slap.lap import _adjacency_and_elements
|
|
15
|
+
from ..reporting.spectrum import exact_its_and_template_codes
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def features(label, kind):
|
|
19
|
+
if kind == "bond":
|
|
20
|
+
return frozenset(("bond", i, j) for i, j in label.changed_bonds)
|
|
21
|
+
if kind == "atom":
|
|
22
|
+
return frozenset(("atom", i) for i in label.centre_atoms)
|
|
23
|
+
if kind == "typed":
|
|
24
|
+
return frozenset(("bond", *edit) for edit in label.typed_bond_edits)
|
|
25
|
+
if kind == "joint":
|
|
26
|
+
return features(label, "typed") | frozenset(
|
|
27
|
+
("unary", *edit) for edit in label.unary_changes
|
|
28
|
+
)
|
|
29
|
+
raise ValueError("Unknown label kind")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FeatureOrbitEvaluator(SupportOrbitEvaluator):
|
|
33
|
+
"""The same exact support action on typed bonds, unary edits or atom sets."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, reactant, *, exact_match=False, **limits):
|
|
36
|
+
super().__init__(reactant, **limits)
|
|
37
|
+
self.exact_match = exact_match
|
|
38
|
+
|
|
39
|
+
def _validate(self, label):
|
|
40
|
+
label = frozenset(tuple(item) for item in label)
|
|
41
|
+
for item in label:
|
|
42
|
+
if not item or item[0] not in {"atom", "bond", "unary"}:
|
|
43
|
+
raise ValueError("Unknown feature token")
|
|
44
|
+
if item[0] == "atom" and len(item) == 2:
|
|
45
|
+
indices = item[1:2]
|
|
46
|
+
elif item[0] == "bond" and len(item) in (3, 5):
|
|
47
|
+
indices = item[1:3]
|
|
48
|
+
if not item[1] < item[2]:
|
|
49
|
+
raise ValueError("Bond coordinates must be ordered")
|
|
50
|
+
if len(item) == 5 and (
|
|
51
|
+
any(not isinstance(x, Integral) for x in item[3:])
|
|
52
|
+
or item[3] == item[4]
|
|
53
|
+
):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"Typed edits need distinct integer before/after orders"
|
|
56
|
+
)
|
|
57
|
+
elif item[0] == "unary" and len(item) == 5:
|
|
58
|
+
indices = item[1:2]
|
|
59
|
+
if item[2] not in {"charges", "hcounts"} or any(
|
|
60
|
+
not isinstance(x, Integral) for x in item[3:]
|
|
61
|
+
):
|
|
62
|
+
raise ValueError("Unsupported unary edit")
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("Invalid feature shape")
|
|
65
|
+
if any(not isinstance(i, Integral) or not 0 <= i < self.n for i in indices):
|
|
66
|
+
raise ValueError("Out-of-range feature coordinate")
|
|
67
|
+
return label
|
|
68
|
+
|
|
69
|
+
def _support(self, label):
|
|
70
|
+
return tuple(
|
|
71
|
+
sorted({i for x in label for i in (x[1:3] if x[0] == "bond" else x[1:2])})
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def _transport(self, label, permutation):
|
|
75
|
+
result = []
|
|
76
|
+
for item in label:
|
|
77
|
+
if item[0] == "bond":
|
|
78
|
+
i, j = sorted((permutation[item[1]], permutation[item[2]]))
|
|
79
|
+
result.append(("bond", i, j, *item[3:]))
|
|
80
|
+
else:
|
|
81
|
+
result.append((item[0], permutation[item[1]], *item[2:]))
|
|
82
|
+
return frozenset(result)
|
|
83
|
+
|
|
84
|
+
def score(self, prediction, label):
|
|
85
|
+
prediction = self._validate(prediction)
|
|
86
|
+
best = None
|
|
87
|
+
for image, witness in self.orbit(label).items():
|
|
88
|
+
self._check()
|
|
89
|
+
value = (
|
|
90
|
+
int(prediction == image)
|
|
91
|
+
if self.exact_match
|
|
92
|
+
else bond_f1(prediction, image)
|
|
93
|
+
)
|
|
94
|
+
if best is None or value > best.score:
|
|
95
|
+
best = ScoreWitness(value, witness)
|
|
96
|
+
return best
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _witness(value):
|
|
100
|
+
return {
|
|
101
|
+
"difference": str(value.difference),
|
|
102
|
+
"label": sorted(value.label),
|
|
103
|
+
"a_score": str(value.a.score),
|
|
104
|
+
"b_score": str(value.b.score),
|
|
105
|
+
"a_transporter": value.a.transporter,
|
|
106
|
+
"b_transporter": value.b.transporter,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def analyze_annotations( # noqa: C901
|
|
111
|
+
reactant,
|
|
112
|
+
product,
|
|
113
|
+
candidate_mappings,
|
|
114
|
+
prediction_a,
|
|
115
|
+
prediction_b,
|
|
116
|
+
*,
|
|
117
|
+
joint_labels_complete,
|
|
118
|
+
minimum,
|
|
119
|
+
reference_mapping=None,
|
|
120
|
+
metric_seconds=10,
|
|
121
|
+
canonical_seconds=0.25,
|
|
122
|
+
template_radius=1,
|
|
123
|
+
):
|
|
124
|
+
"""Evaluate complete joint witnesses with independently reported stages.
|
|
125
|
+
|
|
126
|
+
Canonical full ITS codes choose a deterministic class convention. Policies
|
|
127
|
+
nearest to A/B use primary bond F1 with that canonical code as tie-break;
|
|
128
|
+
every method is evaluated against the resulting *shared* label. These
|
|
129
|
+
conventions are diagnostics, not chemical ground truth.
|
|
130
|
+
"""
|
|
131
|
+
if not joint_labels_complete:
|
|
132
|
+
raise ValueError("Secondary evaluation requires complete joint labels")
|
|
133
|
+
mappings = [tuple(m) for m in candidate_mappings]
|
|
134
|
+
if not mappings:
|
|
135
|
+
raise ValueError("A nonempty candidate set is required")
|
|
136
|
+
labels = [extract_label(reactant, product, m) for m in mappings]
|
|
137
|
+
if any(x.weighted_distance != minimum for x in labels):
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"Candidate objective does not match the declared weighted minimum"
|
|
140
|
+
)
|
|
141
|
+
pa = extract_label(reactant, product, prediction_a)
|
|
142
|
+
pb = extract_label(reactant, product, prediction_b)
|
|
143
|
+
reference = (
|
|
144
|
+
None
|
|
145
|
+
if reference_mapping is None
|
|
146
|
+
else extract_label(reactant, product, reference_mapping)
|
|
147
|
+
)
|
|
148
|
+
output = {"status": "evaluated", "metrics": {}, "reference": {}, "policies": {}}
|
|
149
|
+
if reference is not None:
|
|
150
|
+
output["reference"] = {
|
|
151
|
+
"weighted_distance": str(reference.weighted_distance),
|
|
152
|
+
"mapping_in_minimum": reference.weighted_distance == minimum,
|
|
153
|
+
}
|
|
154
|
+
primary_scores, primary_keys = None, None
|
|
155
|
+
for name, kind, exact in (
|
|
156
|
+
("bond_f1", "bond", False),
|
|
157
|
+
("atom_f1", "atom", False),
|
|
158
|
+
("typed_f1", "typed", False),
|
|
159
|
+
("bond_exact", "bond", True),
|
|
160
|
+
("joint_exact", "joint", True),
|
|
161
|
+
):
|
|
162
|
+
try:
|
|
163
|
+
scorer = FeatureOrbitEvaluator(
|
|
164
|
+
reactant, exact_match=exact, time_limit_seconds=metric_seconds
|
|
165
|
+
)
|
|
166
|
+
candidates = [features(x, kind) for x in labels]
|
|
167
|
+
a, b = features(pa, kind), features(pb, kind)
|
|
168
|
+
lo, hi = scorer.paired_envelope(a, b, candidates, labels_complete=True)
|
|
169
|
+
keys = [scorer.orbit_key(x) for x in candidates]
|
|
170
|
+
values = [
|
|
171
|
+
(scorer.score(a, x).score, scorer.score(b, x).score) for x in candidates
|
|
172
|
+
]
|
|
173
|
+
record = {
|
|
174
|
+
"status": "complete",
|
|
175
|
+
"lower": _witness(lo),
|
|
176
|
+
"upper": _witness(hi),
|
|
177
|
+
"width": str(hi.difference - lo.difference),
|
|
178
|
+
"fixed_labels": len(set(candidates)),
|
|
179
|
+
"label_orbits": len(set(keys)),
|
|
180
|
+
}
|
|
181
|
+
output["metrics"][name] = record
|
|
182
|
+
if name == "bond_f1":
|
|
183
|
+
primary_scores, primary_keys = values, keys
|
|
184
|
+
if reference is not None:
|
|
185
|
+
try:
|
|
186
|
+
ref = features(reference, kind)
|
|
187
|
+
membership = scorer.orbit_key(ref) in set(keys)
|
|
188
|
+
ref_scores = {
|
|
189
|
+
"a": str(scorer.score(a, ref).score),
|
|
190
|
+
"b": str(scorer.score(b, ref).score),
|
|
191
|
+
}
|
|
192
|
+
record.update(
|
|
193
|
+
reference_status="complete",
|
|
194
|
+
reference_orbit_in_minimum=membership,
|
|
195
|
+
reference_scores=ref_scores,
|
|
196
|
+
)
|
|
197
|
+
except IncompleteSymmetry as exc:
|
|
198
|
+
record.update(
|
|
199
|
+
reference_status="unresolved", reference_reason=str(exc)
|
|
200
|
+
)
|
|
201
|
+
except IncompleteSymmetry as exc:
|
|
202
|
+
output["metrics"][name] = {"status": "unresolved", "reason": str(exc)}
|
|
203
|
+
rmatrix, elements = _adjacency_and_elements(reactant.graph(), False)
|
|
204
|
+
pmatrix, _ = _adjacency_and_elements(product.graph(), False)
|
|
205
|
+
properties = {
|
|
206
|
+
name: (getattr(reactant, name), getattr(product, name))
|
|
207
|
+
for name in ("charges", "hcounts")
|
|
208
|
+
}
|
|
209
|
+
codes = []
|
|
210
|
+
for mapping in mappings:
|
|
211
|
+
full, template, reason = exact_its_and_template_codes(
|
|
212
|
+
rmatrix,
|
|
213
|
+
pmatrix,
|
|
214
|
+
elements,
|
|
215
|
+
properties,
|
|
216
|
+
mapping,
|
|
217
|
+
template_radius=template_radius,
|
|
218
|
+
timeout_seconds=canonical_seconds,
|
|
219
|
+
)
|
|
220
|
+
if reason is not None:
|
|
221
|
+
output["structure"] = {"status": "unresolved", "reason": reason}
|
|
222
|
+
break
|
|
223
|
+
codes.append(
|
|
224
|
+
(
|
|
225
|
+
json.dumps(full, separators=(",", ":")),
|
|
226
|
+
json.dumps(template, separators=(",", ":")),
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
output["structure"] = {
|
|
231
|
+
"status": "complete",
|
|
232
|
+
"its_classes": len({x[0] for x in codes}),
|
|
233
|
+
"template_classes": len({x[1] for x in codes}),
|
|
234
|
+
"template_radius": template_radius,
|
|
235
|
+
"codes": [
|
|
236
|
+
{"mapping": m, "its_code": code[0], "template_code": code[1]}
|
|
237
|
+
for m, code in zip(mappings, codes)
|
|
238
|
+
],
|
|
239
|
+
}
|
|
240
|
+
joint = output["metrics"]["joint_exact"]
|
|
241
|
+
if (
|
|
242
|
+
joint["status"] == "complete"
|
|
243
|
+
and joint["label_orbits"] != output["structure"]["its_classes"]
|
|
244
|
+
):
|
|
245
|
+
raise ValueError("Full ITS and joint-label orbit counts disagree")
|
|
246
|
+
if primary_scores is not None:
|
|
247
|
+
code_labels = {}
|
|
248
|
+
for code, key in zip(codes, primary_keys):
|
|
249
|
+
if code[0] in code_labels and code_labels[code[0]] != key:
|
|
250
|
+
raise ValueError(
|
|
251
|
+
"One ITS class induced multiple primary bond orbits"
|
|
252
|
+
)
|
|
253
|
+
code_labels[code[0]] = key
|
|
254
|
+
choices = {
|
|
255
|
+
"canonical_its": min(range(len(codes)), key=lambda i: codes[i][0]),
|
|
256
|
+
"nearest_a": min(
|
|
257
|
+
range(len(codes)),
|
|
258
|
+
key=lambda i: (-primary_scores[i][0], codes[i][0]),
|
|
259
|
+
),
|
|
260
|
+
"nearest_b": min(
|
|
261
|
+
range(len(codes)),
|
|
262
|
+
key=lambda i: (-primary_scores[i][1], codes[i][0]),
|
|
263
|
+
),
|
|
264
|
+
}
|
|
265
|
+
for name, index in choices.items():
|
|
266
|
+
a, b = primary_scores[index]
|
|
267
|
+
output["policies"][name] = {
|
|
268
|
+
"mapping": mappings[index],
|
|
269
|
+
"its_code": codes[index][0],
|
|
270
|
+
"a_score": str(a),
|
|
271
|
+
"b_score": str(b),
|
|
272
|
+
"difference": str(a - b),
|
|
273
|
+
}
|
|
274
|
+
return output
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Exact, shared-label changed-bond evaluation on fully resolved inputs.
|
|
2
|
+
|
|
3
|
+
The exhaustive full-reactant symmetry route is a bounded development oracle.
|
|
4
|
+
It raises on incomplete symmetry rather than silently reporting exact scores.
|
|
5
|
+
Production callers must also establish minimum and label-enumeration closure.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from fractions import Fraction
|
|
10
|
+
from time import monotonic
|
|
11
|
+
|
|
12
|
+
import networkx as nx
|
|
13
|
+
|
|
14
|
+
from .identifiability import Endpoint
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class IncompleteSymmetry(RuntimeError):
|
|
18
|
+
"""The full attributed automorphism group was not established."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _graph(endpoint):
|
|
22
|
+
graph = nx.Graph()
|
|
23
|
+
for i, z in enumerate(endpoint.atomic_numbers):
|
|
24
|
+
graph.add_node(i, color=(z, endpoint.charges[i], endpoint.hcounts[i]))
|
|
25
|
+
for i, j, order in endpoint.bonds:
|
|
26
|
+
graph.add_edge(i, j, order=order)
|
|
27
|
+
return graph
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def full_automorphisms(endpoint: Endpoint, *, max_automorphisms=100000,
|
|
31
|
+
time_limit_seconds=30):
|
|
32
|
+
"""Enumerate all symmetries, preserving elements, charges, H and orders.
|
|
33
|
+
|
|
34
|
+
The time check is cooperative between matcher yields; a campaign runner
|
|
35
|
+
still needs an external process deadline. Component roles are not supported
|
|
36
|
+
by this contract and must not be claimed as preserved attributes.
|
|
37
|
+
"""
|
|
38
|
+
if max_automorphisms < 1 or time_limit_seconds <= 0:
|
|
39
|
+
raise ValueError("Symmetry limits must be positive")
|
|
40
|
+
graph = _graph(endpoint)
|
|
41
|
+
matcher = nx.algorithms.isomorphism.GraphMatcher(
|
|
42
|
+
graph, graph,
|
|
43
|
+
node_match=nx.algorithms.isomorphism.categorical_node_match("color", None),
|
|
44
|
+
edge_match=nx.algorithms.isomorphism.categorical_edge_match("order", None),
|
|
45
|
+
)
|
|
46
|
+
start = monotonic()
|
|
47
|
+
permutations = []
|
|
48
|
+
for mapping in matcher.isomorphisms_iter():
|
|
49
|
+
if monotonic() - start > time_limit_seconds:
|
|
50
|
+
raise IncompleteSymmetry("Automorphism deadline exceeded")
|
|
51
|
+
if len(permutations) >= max_automorphisms:
|
|
52
|
+
raise IncompleteSymmetry("Automorphism output cap exceeded")
|
|
53
|
+
permutations.append(tuple(mapping[i] for i in range(len(graph))))
|
|
54
|
+
if monotonic() - start > time_limit_seconds:
|
|
55
|
+
raise IncompleteSymmetry("Automorphism deadline exceeded")
|
|
56
|
+
return tuple(sorted(permutations))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def bond_f1(prediction, label):
|
|
60
|
+
prediction, label = frozenset(prediction), frozenset(label)
|
|
61
|
+
denominator = len(prediction) + len(label)
|
|
62
|
+
return Fraction(2 * len(prediction & label), denominator) if denominator else Fraction(1)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def transport_bonds(label, permutation):
|
|
66
|
+
return frozenset(tuple(sorted((permutation[i], permutation[j]))) for i, j in label)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class ScoreWitness:
|
|
71
|
+
score: Fraction
|
|
72
|
+
transporter: tuple[int, ...]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ExactBondEvaluator:
|
|
76
|
+
"""Scorer bound to one endpoint and its completely enumerated symmetry."""
|
|
77
|
+
|
|
78
|
+
def __init__(self, reactant: Endpoint, **symmetry_limits):
|
|
79
|
+
self.n = len(reactant.atomic_numbers)
|
|
80
|
+
self.automorphisms = full_automorphisms(reactant, **symmetry_limits)
|
|
81
|
+
|
|
82
|
+
def _validate(self, bonds):
|
|
83
|
+
bonds = frozenset(bonds)
|
|
84
|
+
if any(len(pair) != 2 or not 0 <= pair[0] < pair[1] < self.n for pair in bonds):
|
|
85
|
+
raise ValueError("Bond labels require canonical in-range unordered pairs")
|
|
86
|
+
return bonds
|
|
87
|
+
|
|
88
|
+
def score(self, prediction, label):
|
|
89
|
+
prediction, label = self._validate(prediction), self._validate(label)
|
|
90
|
+
best = None
|
|
91
|
+
for permutation in self.automorphisms:
|
|
92
|
+
score = bond_f1(prediction, transport_bonds(label, permutation))
|
|
93
|
+
if best is None or score > best.score:
|
|
94
|
+
best = ScoreWitness(score, permutation)
|
|
95
|
+
return best
|
|
96
|
+
|
|
97
|
+
def orbit_key(self, label):
|
|
98
|
+
label = self._validate(label)
|
|
99
|
+
return min(tuple(sorted(transport_bonds(label, p))) for p in self.automorphisms)
|
|
100
|
+
|
|
101
|
+
def paired_envelope(self, prediction_a, prediction_b, labels, *, labels_complete):
|
|
102
|
+
"""Return attained min/max differences with shared candidate witnesses.
|
|
103
|
+
|
|
104
|
+
``labels_complete`` is an explicit caller obligation, not a proof
|
|
105
|
+
generated by the scorer. An incomplete set is refused by this API.
|
|
106
|
+
"""
|
|
107
|
+
if not labels_complete:
|
|
108
|
+
raise ValueError("Exact envelopes require complete admitted labels")
|
|
109
|
+
values = []
|
|
110
|
+
for label in labels:
|
|
111
|
+
label = self._validate(label)
|
|
112
|
+
a, b = self.score(prediction_a, label), self.score(prediction_b, label)
|
|
113
|
+
values.append(PairedWitness(a.score - b.score, label, a, b))
|
|
114
|
+
if not values:
|
|
115
|
+
raise ValueError("At least one admitted label is required")
|
|
116
|
+
return min(values, key=lambda x: x.difference), max(values, key=lambda x: x.difference)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class PairedWitness:
|
|
121
|
+
difference: Fraction
|
|
122
|
+
label: frozenset[tuple[int, int]]
|
|
123
|
+
a: ScoreWitness
|
|
124
|
+
b: ScoreWitness
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Separate intrinsic label sensitivity from unresolved computation.
|
|
2
|
+
|
|
3
|
+
Each case's paired F1 difference is in [-1,1]. Weights and scores are exact
|
|
4
|
+
rationals; these are identification bounds, not sampling confidence intervals.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from fractions import Fraction
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def rational(value):
|
|
12
|
+
if isinstance(value, bool) or not isinstance(value, (int, str, Fraction)):
|
|
13
|
+
raise TypeError("Use an integer, Fraction or rational string, not a float")
|
|
14
|
+
return Fraction(value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class CohortBounds:
|
|
19
|
+
unresolved_weight: Fraction
|
|
20
|
+
outer_lower: Fraction
|
|
21
|
+
outer_upper: Fraction
|
|
22
|
+
width_lower: Fraction
|
|
23
|
+
width_upper: Fraction
|
|
24
|
+
conditional_lower: Fraction | None
|
|
25
|
+
conditional_upper: Fraction | None
|
|
26
|
+
lower_witness_assignment_upper: Fraction
|
|
27
|
+
upper_witness_assignment_lower: Fraction
|
|
28
|
+
robust_a: bool
|
|
29
|
+
robust_b: bool
|
|
30
|
+
guaranteed_substantive_reversal: bool
|
|
31
|
+
exact: bool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def paired_cohort_bounds(intervals, *, weights=None, margin=Fraction(1, 50)):
|
|
35
|
+
"""Bound a fixed common-valid cohort; ``None`` means unresolved.
|
|
36
|
+
|
|
37
|
+
Resolved intervals must be attained shared-label extrema. For a global
|
|
38
|
+
coupled annotation policy these sums need not be attainable; the function
|
|
39
|
+
assumes casewise label choices are independent. Invalid predictions must
|
|
40
|
+
be handled before calling, by an explicit cohort or operational policy.
|
|
41
|
+
"""
|
|
42
|
+
intervals = list(intervals)
|
|
43
|
+
if not intervals:
|
|
44
|
+
raise ValueError("A nonempty comparison cohort is required")
|
|
45
|
+
weights = ([Fraction(1, len(intervals))] * len(intervals) if weights is None
|
|
46
|
+
else [rational(x) for x in weights])
|
|
47
|
+
if len(weights) != len(intervals) or any(w < 0 for w in weights) or sum(weights) != 1:
|
|
48
|
+
raise ValueError("Weights must be nonnegative, aligned and sum to one")
|
|
49
|
+
margin = rational(margin)
|
|
50
|
+
if not 0 <= margin <= 1:
|
|
51
|
+
raise ValueError("Margin must lie in [0,1]")
|
|
52
|
+
lower, upper, unknown = Fraction(), Fraction(), Fraction()
|
|
53
|
+
for interval, weight in zip(intervals, weights):
|
|
54
|
+
if interval is None:
|
|
55
|
+
unknown += weight
|
|
56
|
+
else:
|
|
57
|
+
lo, hi = map(rational, interval)
|
|
58
|
+
if not -1 <= lo <= hi <= 1:
|
|
59
|
+
raise ValueError("Invalid paired score interval")
|
|
60
|
+
lower += weight * lo
|
|
61
|
+
upper += weight * hi
|
|
62
|
+
resolved_weight = 1-unknown
|
|
63
|
+
outer_lo, outer_hi = lower-unknown, upper+unknown
|
|
64
|
+
return CohortBounds(
|
|
65
|
+
unknown, outer_lo, outer_hi, upper-lower, upper-lower+2*unknown,
|
|
66
|
+
lower/resolved_weight if resolved_weight else None,
|
|
67
|
+
upper/resolved_weight if resolved_weight else None,
|
|
68
|
+
lower+unknown, upper-unknown,
|
|
69
|
+
outer_lo > margin, outer_hi < -margin,
|
|
70
|
+
lower+unknown < -margin and upper-unknown > margin,
|
|
71
|
+
unknown == 0,
|
|
72
|
+
)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Strict heavy-atom endpoints and joint reaction labels for evaluation.
|
|
2
|
+
|
|
3
|
+
This study interface deliberately does not balance, repair, or discard atoms.
|
|
4
|
+
Stereochemistry is outside its connectivity-only contract; isotope inputs and
|
|
5
|
+
hydrogens that cannot be folded into heavy atoms are rejected.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections import Counter
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from fractions import Fraction
|
|
11
|
+
from itertools import combinations
|
|
12
|
+
from numbers import Integral
|
|
13
|
+
|
|
14
|
+
from rdkit import Chem
|
|
15
|
+
|
|
16
|
+
from ..graph.labeled_graph import LabeledGraph
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class Endpoint:
|
|
21
|
+
atomic_numbers: tuple[int, ...]
|
|
22
|
+
charges: tuple[int, ...]
|
|
23
|
+
hcounts: tuple[int, ...]
|
|
24
|
+
# Bond orders are represented as exact twice-order integers.
|
|
25
|
+
bonds: tuple[tuple[int, int, int], ...]
|
|
26
|
+
|
|
27
|
+
def __post_init__(self):
|
|
28
|
+
n = len(self.atomic_numbers)
|
|
29
|
+
if len(self.charges) != n or len(self.hcounts) != n:
|
|
30
|
+
raise ValueError("Endpoint property lengths differ")
|
|
31
|
+
if not n or any(z <= 1 for z in self.atomic_numbers):
|
|
32
|
+
raise ValueError("A nonempty heavy-atom inventory is required")
|
|
33
|
+
if any(not isinstance(x, Integral) for values in
|
|
34
|
+
(self.atomic_numbers, self.charges, self.hcounts) for x in values):
|
|
35
|
+
raise ValueError("Atom properties must be integers")
|
|
36
|
+
if any(h < 0 for h in self.hcounts):
|
|
37
|
+
raise ValueError("Hydrogen counts must be nonnegative")
|
|
38
|
+
pairs = set()
|
|
39
|
+
for i, j, order in self.bonds:
|
|
40
|
+
if not all(isinstance(x, Integral) for x in (i, j, order)):
|
|
41
|
+
raise ValueError("Bond records must use integers")
|
|
42
|
+
if not 0 <= i < j < n or order not in (2, 3, 4, 6):
|
|
43
|
+
raise ValueError("Unsupported bond coordinate or order")
|
|
44
|
+
if (i, j) in pairs:
|
|
45
|
+
raise ValueError("Duplicate bond")
|
|
46
|
+
pairs.add((i, j))
|
|
47
|
+
|
|
48
|
+
def graph(self):
|
|
49
|
+
adjacency = {i: {} for i in range(len(self.atomic_numbers))}
|
|
50
|
+
for i, j, order in self.bonds:
|
|
51
|
+
adjacency[i][j] = adjacency[j][i] = order / 2
|
|
52
|
+
result = LabeledGraph(adjacency, self.atomic_numbers)
|
|
53
|
+
for name, values in (("atomic numbers", self.atomic_numbers),
|
|
54
|
+
("charges", self.charges), ("hcounts", self.hcounts)):
|
|
55
|
+
result.set_prop(name, list(values))
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def parse_endpoint(smiles: str) -> Endpoint:
|
|
60
|
+
"""Parse every component, discard map labels, fold ordinary explicit H.
|
|
61
|
+
|
|
62
|
+
Coordinates follow RDKit's retained heavy-atom order, not atom-map numbers.
|
|
63
|
+
The caller must save the exact input string with exported labels.
|
|
64
|
+
"""
|
|
65
|
+
params = Chem.SmilesParserParams()
|
|
66
|
+
params.removeHs = False
|
|
67
|
+
mol = Chem.MolFromSmiles(smiles, params)
|
|
68
|
+
if mol is None:
|
|
69
|
+
raise ValueError("Invalid endpoint SMILES")
|
|
70
|
+
for atom in mol.GetAtoms():
|
|
71
|
+
if atom.GetIsotope():
|
|
72
|
+
raise ValueError("Isotope-specific endpoints are unsupported")
|
|
73
|
+
if atom.GetNumRadicalElectrons():
|
|
74
|
+
raise ValueError("Radical endpoints are unsupported")
|
|
75
|
+
atom.SetAtomMapNum(0)
|
|
76
|
+
mol = Chem.RemoveHs(mol)
|
|
77
|
+
bonds = []
|
|
78
|
+
for bond in mol.GetBonds():
|
|
79
|
+
order = 2 * bond.GetBondTypeAsDouble()
|
|
80
|
+
if order not in (2, 3, 4, 6):
|
|
81
|
+
raise ValueError("Unsupported bond type")
|
|
82
|
+
i, j = sorted((bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()))
|
|
83
|
+
bonds.append((i, j, int(order)))
|
|
84
|
+
return Endpoint(
|
|
85
|
+
tuple(a.GetAtomicNum() for a in mol.GetAtoms()),
|
|
86
|
+
tuple(a.GetFormalCharge() for a in mol.GetAtoms()),
|
|
87
|
+
tuple(a.GetTotalNumHs() for a in mol.GetAtoms()),
|
|
88
|
+
tuple(sorted(bonds)),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def parse_reaction(reaction: str) -> tuple[Endpoint, Endpoint]:
|
|
93
|
+
"""Accept balanced ``reactants>>products`` without a hidden agent policy."""
|
|
94
|
+
fields = reaction.split(">")
|
|
95
|
+
if len(fields) != 3 or fields[1]:
|
|
96
|
+
raise ValueError("Expected reactants>>products with no agent field")
|
|
97
|
+
reactant, product = map(parse_endpoint, (fields[0], fields[2]))
|
|
98
|
+
if Counter(reactant.atomic_numbers) != Counter(product.atomic_numbers):
|
|
99
|
+
raise ValueError("Heavy-atom inventories are not element balanced")
|
|
100
|
+
return reactant, product
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True)
|
|
104
|
+
class JointLabel:
|
|
105
|
+
typed_bond_edits: tuple[tuple[int, int, int, int], ...]
|
|
106
|
+
unary_changes: tuple[tuple[int, str, int, int], ...]
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def changed_bonds(self):
|
|
110
|
+
return frozenset((i, j) for i, j, _, _ in self.typed_bond_edits)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def centre_atoms(self):
|
|
114
|
+
return frozenset(
|
|
115
|
+
[i for pair in self.changed_bonds for i in pair]
|
|
116
|
+
+ [i for i, _, _, _ in self.unary_changes]
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def weighted_distance(self):
|
|
121
|
+
return Fraction(sum(abs(after - before)
|
|
122
|
+
for _, _, before, after in self.typed_bond_edits), 2)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def extract_label(reactant: Endpoint, product: Endpoint, mapping) -> JointLabel:
|
|
126
|
+
"""Derive one *joint* label from a complete element-compatible bijection."""
|
|
127
|
+
mapping = tuple(mapping)
|
|
128
|
+
n = len(reactant.atomic_numbers)
|
|
129
|
+
if (len(product.atomic_numbers) != n or len(mapping) != n
|
|
130
|
+
or any(not isinstance(p, Integral) for p in mapping)
|
|
131
|
+
or set(mapping) != set(range(n))):
|
|
132
|
+
raise ValueError("Mapping must be a complete bijection")
|
|
133
|
+
if any(z != product.atomic_numbers[mapping[i]]
|
|
134
|
+
for i, z in enumerate(reactant.atomic_numbers)):
|
|
135
|
+
raise ValueError("Mapping changes an element")
|
|
136
|
+
rb = {(i, j): order for i, j, order in reactant.bonds}
|
|
137
|
+
pb = {(i, j): order for i, j, order in product.bonds}
|
|
138
|
+
edits = []
|
|
139
|
+
for i, j in combinations(range(n), 2):
|
|
140
|
+
before = rb.get((i, j), 0)
|
|
141
|
+
after = pb.get(tuple(sorted((mapping[i], mapping[j]))), 0)
|
|
142
|
+
if before != after:
|
|
143
|
+
edits.append((i, j, before, after))
|
|
144
|
+
unary = []
|
|
145
|
+
for name in ("charges", "hcounts"):
|
|
146
|
+
before_values, after_values = getattr(reactant, name), getattr(product, name)
|
|
147
|
+
for i, p in enumerate(mapping):
|
|
148
|
+
if before_values[i] != after_values[p]:
|
|
149
|
+
unary.append((i, name, before_values[i], after_values[p]))
|
|
150
|
+
return JointLabel(tuple(edits), tuple(sorted(unary)))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Declared finite chemical-lattice domain for the identifiability study.
|
|
2
|
+
|
|
3
|
+
This guard is not a repair of the generic floating-point solver API. The study
|
|
4
|
+
uses undirected loop-free endpoints with doubled orders in {2,3,4,6}, at most
|
|
5
|
+
512 atoms, and exact integer independent witness rescoring. The current Python
|
|
6
|
+
assignment route verifies bounded lattice LAP answers using integer primal--dual
|
|
7
|
+
certificates; its operation-level argument is in the assignment-proof supplement.
|
|
8
|
+
This input guard alone does not certify another execution route or an archived source
|
|
9
|
+
version, and does not broaden the generic floating-point API guarantee.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
MAX_STUDY_ATOMS = 512
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate_study_domain(reactant, product):
|
|
16
|
+
from collections import Counter
|
|
17
|
+
|
|
18
|
+
n = len(reactant.atomic_numbers)
|
|
19
|
+
if not 1 <= n <= MAX_STUDY_ATOMS or len(product.atomic_numbers) != n:
|
|
20
|
+
raise ValueError("Study arithmetic domain requires 1–512 balanced heavy atoms")
|
|
21
|
+
if Counter(reactant.atomic_numbers) != Counter(product.atomic_numbers):
|
|
22
|
+
raise ValueError("Study endpoints must be element balanced")
|
|
23
|
+
for endpoint in (reactant, product):
|
|
24
|
+
endpoint.__post_init__()
|
|
25
|
+
# 4*value must fit exactly in binary64. This deliberately loose polynomial
|
|
26
|
+
# envelope is far above the O(n^2) matrix/DFS/profile quantities actually
|
|
27
|
+
# used; it also leaves headroom for LAP potential/shortest-path updates.
|
|
28
|
+
scaled_working_envelope = 4 * 64 * n**4
|
|
29
|
+
if scaled_working_envelope >= 2**53:
|
|
30
|
+
raise ValueError("Study arithmetic envelope exceeds binary64 integer range")
|
|
31
|
+
return {"domain": "bounded-half-integer-chemical-v1", "heavy_atoms": n,
|
|
32
|
+
"max_heavy_atoms": MAX_STUDY_ATOMS,
|
|
33
|
+
"doubled_orders": [2, 3, 4, 6],
|
|
34
|
+
"scaled_working_envelope": scaled_working_envelope,
|
|
35
|
+
"binary64_exact_integer_limit": 2**53,
|
|
36
|
+
"generic_float_api_certified": False}
|