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,918 @@
|
|
|
1
|
+
"""Versioned public models for supplied reaction mechanisms.
|
|
2
|
+
|
|
3
|
+
The models in this module deliberately describe *annotations supplied by a
|
|
4
|
+
user or dataset*. They do not score, enumerate, or select mechanisms.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Iterable, Literal, Mapping
|
|
13
|
+
|
|
14
|
+
from .electron_models import ( # noqa: F401
|
|
15
|
+
ElectronLocus,
|
|
16
|
+
ElectronMove,
|
|
17
|
+
ElectronMoveGroup,
|
|
18
|
+
MechanismModelError,
|
|
19
|
+
VerificationIssue,
|
|
20
|
+
)
|
|
21
|
+
from .symbols import CanonicalLocus
|
|
22
|
+
|
|
23
|
+
SCHEMA_VERSION = "2.0.0-draft1"
|
|
24
|
+
LocusKind = CanonicalLocus
|
|
25
|
+
ArrowType = Literal["curved", "fishhook"]
|
|
26
|
+
StereoEffectKind = Literal[
|
|
27
|
+
"PRESERVE", "INVERT", "BREAK", "FORM", "FLEETING", "UNSPECIFIED"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class StereoDescriptor:
|
|
33
|
+
"""Serializable envelope for SynKit's permutation-aware descriptors."""
|
|
34
|
+
|
|
35
|
+
descriptor_class: Literal[
|
|
36
|
+
"tetrahedral",
|
|
37
|
+
"square_planar",
|
|
38
|
+
"trigonal_bipyramidal",
|
|
39
|
+
"octahedral",
|
|
40
|
+
"planar_bond",
|
|
41
|
+
"atrop_bond",
|
|
42
|
+
"unknown",
|
|
43
|
+
]
|
|
44
|
+
atoms: tuple[int | str, ...]
|
|
45
|
+
parity: int | None = None
|
|
46
|
+
state: Literal["specified", "unknown", "unspecified", "absent"] = "specified"
|
|
47
|
+
provenance: str | None = None
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
expected = {
|
|
51
|
+
"tetrahedral": 5,
|
|
52
|
+
"square_planar": 5,
|
|
53
|
+
"trigonal_bipyramidal": 6,
|
|
54
|
+
"octahedral": 7,
|
|
55
|
+
"planar_bond": 6,
|
|
56
|
+
"atrop_bond": 6,
|
|
57
|
+
"unknown": 0,
|
|
58
|
+
}[self.descriptor_class]
|
|
59
|
+
if expected and len(self.atoms) != expected:
|
|
60
|
+
raise MechanismModelError(
|
|
61
|
+
f"{self.descriptor_class} descriptors require {expected} atom maps."
|
|
62
|
+
)
|
|
63
|
+
allowed = (
|
|
64
|
+
(0, None)
|
|
65
|
+
if self.descriptor_class in {"square_planar", "planar_bond"}
|
|
66
|
+
else (-1, 1, None)
|
|
67
|
+
)
|
|
68
|
+
if self.parity not in allowed:
|
|
69
|
+
raise MechanismModelError(
|
|
70
|
+
f"Invalid parity for {self.descriptor_class} stereo."
|
|
71
|
+
)
|
|
72
|
+
if self.state == "specified" and self.parity is None:
|
|
73
|
+
raise MechanismModelError("Specified stereo descriptors require a parity.")
|
|
74
|
+
if self.state != "specified" and self.parity is not None:
|
|
75
|
+
raise MechanismModelError(
|
|
76
|
+
"Unknown, unspecified, and absent stereo states cannot carry parity."
|
|
77
|
+
)
|
|
78
|
+
if self.descriptor_class == "unknown" and self.state == "specified":
|
|
79
|
+
raise MechanismModelError(
|
|
80
|
+
"Unknown descriptor classes cannot declare specified stereo."
|
|
81
|
+
)
|
|
82
|
+
if self.descriptor_class != "unknown":
|
|
83
|
+
# Reuse the executable descriptor contract so mechanism envelopes
|
|
84
|
+
# cannot reintroduce StereoMolGraph's untyped ``None`` or attach a
|
|
85
|
+
# virtual ligand to the wrong stereochemical owner.
|
|
86
|
+
from synkit.Graph.Stereo.descriptors import stereo_from_dict
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
stereo_from_dict(
|
|
90
|
+
{
|
|
91
|
+
"descriptor_class": self.descriptor_class,
|
|
92
|
+
"atoms": self.atoms,
|
|
93
|
+
"parity": self.parity,
|
|
94
|
+
"provenance": self.provenance,
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
except (TypeError, ValueError) as error:
|
|
98
|
+
raise MechanismModelError(str(error)) from error
|
|
99
|
+
|
|
100
|
+
def to_dict(self) -> dict[str, Any]:
|
|
101
|
+
return {
|
|
102
|
+
"descriptor_class": self.descriptor_class,
|
|
103
|
+
"atoms": list(self.atoms),
|
|
104
|
+
"parity": self.parity,
|
|
105
|
+
"state": self.state,
|
|
106
|
+
"provenance": self.provenance,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def inverted(self) -> "StereoDescriptor":
|
|
110
|
+
"""Return the opposite relative orientation of specified stereo."""
|
|
111
|
+
if self.descriptor_class == "unknown" or self.state != "specified":
|
|
112
|
+
raise MechanismModelError(
|
|
113
|
+
"Only specified executable stereo descriptors can be inverted."
|
|
114
|
+
)
|
|
115
|
+
from synkit.Graph.Stereo import stereo_from_dict
|
|
116
|
+
|
|
117
|
+
value = stereo_from_dict(self.to_dict()).invert().to_dict()
|
|
118
|
+
value["state"] = self.state
|
|
119
|
+
return StereoDescriptor.from_dict(value)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def target_key(self) -> str:
|
|
123
|
+
"""Return the canonical registry key owned by this descriptor."""
|
|
124
|
+
if self.descriptor_class in {
|
|
125
|
+
"tetrahedral",
|
|
126
|
+
"square_planar",
|
|
127
|
+
"trigonal_bipyramidal",
|
|
128
|
+
"octahedral",
|
|
129
|
+
}:
|
|
130
|
+
return f"atom:{self.atoms[0]}"
|
|
131
|
+
if self.descriptor_class in {"planar_bond", "atrop_bond"}:
|
|
132
|
+
return f"bond:{min(self.atoms[2:4])}-{max(self.atoms[2:4])}"
|
|
133
|
+
raise MechanismModelError(
|
|
134
|
+
"Unknown descriptor classes do not own an executable endpoint locus."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def from_dict(cls, value: Mapping[str, Any]) -> "StereoDescriptor":
|
|
139
|
+
return cls(
|
|
140
|
+
descriptor_class=value["descriptor_class"],
|
|
141
|
+
atoms=tuple(value.get("atoms", ())),
|
|
142
|
+
parity=value.get("parity"),
|
|
143
|
+
state=value.get("state", "specified"),
|
|
144
|
+
provenance=value.get("provenance"),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True)
|
|
149
|
+
class StereoEffect:
|
|
150
|
+
descriptor_target: tuple[str, int | tuple[int, int]]
|
|
151
|
+
effect: StereoEffectKind
|
|
152
|
+
before: StereoDescriptor | None = None
|
|
153
|
+
after: StereoDescriptor | None = None
|
|
154
|
+
provenance: str = "annotated"
|
|
155
|
+
|
|
156
|
+
def __post_init__(self) -> None:
|
|
157
|
+
if len(self.descriptor_target) != 2:
|
|
158
|
+
raise MechanismModelError(
|
|
159
|
+
"Stereo descriptor targets require a kind and mapped locus."
|
|
160
|
+
)
|
|
161
|
+
kind, reference = self.descriptor_target
|
|
162
|
+
if kind == "atom":
|
|
163
|
+
if type(reference) is not int or reference <= 0:
|
|
164
|
+
raise MechanismModelError(
|
|
165
|
+
"Atom stereo targets require one positive atom map."
|
|
166
|
+
)
|
|
167
|
+
normalized_reference: int | tuple[int, int] = reference
|
|
168
|
+
elif kind == "bond":
|
|
169
|
+
if (
|
|
170
|
+
not isinstance(reference, (tuple, list))
|
|
171
|
+
or len(reference) != 2
|
|
172
|
+
or any(type(value) is not int or value <= 0 for value in reference)
|
|
173
|
+
or reference[0] == reference[1]
|
|
174
|
+
):
|
|
175
|
+
raise MechanismModelError(
|
|
176
|
+
"Bond stereo targets require two distinct positive atom maps."
|
|
177
|
+
)
|
|
178
|
+
normalized_reference = tuple(sorted(reference))
|
|
179
|
+
else:
|
|
180
|
+
raise MechanismModelError("Stereo target kind must be 'atom' or 'bond'.")
|
|
181
|
+
if self.effect not in {
|
|
182
|
+
"PRESERVE",
|
|
183
|
+
"INVERT",
|
|
184
|
+
"BREAK",
|
|
185
|
+
"FORM",
|
|
186
|
+
"FLEETING",
|
|
187
|
+
"UNSPECIFIED",
|
|
188
|
+
}:
|
|
189
|
+
raise MechanismModelError(f"Unsupported stereo effect: {self.effect!r}.")
|
|
190
|
+
object.__setattr__(self, "descriptor_target", (kind, normalized_reference))
|
|
191
|
+
|
|
192
|
+
target_key = (
|
|
193
|
+
f"atom:{normalized_reference}"
|
|
194
|
+
if kind == "atom"
|
|
195
|
+
else f"bond:{normalized_reference[0]}-{normalized_reference[1]}"
|
|
196
|
+
)
|
|
197
|
+
for descriptor in (self.before, self.after):
|
|
198
|
+
if descriptor is None or descriptor.descriptor_class == "unknown":
|
|
199
|
+
continue
|
|
200
|
+
descriptor_key = (
|
|
201
|
+
f"atom:{descriptor.atoms[0]}"
|
|
202
|
+
if descriptor.descriptor_class
|
|
203
|
+
in {
|
|
204
|
+
"tetrahedral",
|
|
205
|
+
"square_planar",
|
|
206
|
+
"trigonal_bipyramidal",
|
|
207
|
+
"octahedral",
|
|
208
|
+
}
|
|
209
|
+
else f"bond:{min(descriptor.atoms[2:4])}-{max(descriptor.atoms[2:4])}"
|
|
210
|
+
)
|
|
211
|
+
if descriptor_key != target_key:
|
|
212
|
+
raise MechanismModelError(
|
|
213
|
+
f"Stereo descriptor belongs to {descriptor_key}, not {target_key}."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def to_dict(self) -> dict[str, Any]:
|
|
217
|
+
target_kind, target_reference = self.descriptor_target
|
|
218
|
+
return {
|
|
219
|
+
"descriptor_target": [
|
|
220
|
+
target_kind,
|
|
221
|
+
(
|
|
222
|
+
list(target_reference)
|
|
223
|
+
if isinstance(target_reference, tuple)
|
|
224
|
+
else target_reference
|
|
225
|
+
),
|
|
226
|
+
],
|
|
227
|
+
"effect": self.effect,
|
|
228
|
+
"before": self.before.to_dict() if self.before else None,
|
|
229
|
+
"after": self.after.to_dict() if self.after else None,
|
|
230
|
+
"provenance": self.provenance,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
def reversed(self) -> "StereoEffect":
|
|
234
|
+
"""Return the inverse endpoint transition when it is well-defined.
|
|
235
|
+
|
|
236
|
+
``UNSPECIFIED`` is deliberately non-invertible: discarding an
|
|
237
|
+
orientation does not contain enough information to reconstruct it.
|
|
238
|
+
"""
|
|
239
|
+
if self.effect == "UNSPECIFIED":
|
|
240
|
+
raise MechanismModelError(
|
|
241
|
+
"NONREVERSIBLE_STEREO_EFFECT: UNSPECIFIED stereo cannot be "
|
|
242
|
+
"reversed without a supplied recovery descriptor."
|
|
243
|
+
)
|
|
244
|
+
if self.effect == "FORM":
|
|
245
|
+
return StereoEffect(
|
|
246
|
+
self.descriptor_target,
|
|
247
|
+
"BREAK",
|
|
248
|
+
before=self.after,
|
|
249
|
+
provenance=self.provenance,
|
|
250
|
+
)
|
|
251
|
+
if self.effect == "BREAK":
|
|
252
|
+
return StereoEffect(
|
|
253
|
+
self.descriptor_target,
|
|
254
|
+
"FORM",
|
|
255
|
+
after=self.before,
|
|
256
|
+
provenance=self.provenance,
|
|
257
|
+
)
|
|
258
|
+
if self.effect == "INVERT":
|
|
259
|
+
if self.before is None:
|
|
260
|
+
raise MechanismModelError(
|
|
261
|
+
"NONREVERSIBLE_STEREO_EFFECT: INVERT requires a before "
|
|
262
|
+
"descriptor."
|
|
263
|
+
)
|
|
264
|
+
reverse_before = self.after or self.before.inverted()
|
|
265
|
+
return StereoEffect(
|
|
266
|
+
self.descriptor_target,
|
|
267
|
+
"INVERT",
|
|
268
|
+
before=reverse_before,
|
|
269
|
+
after=self.before,
|
|
270
|
+
provenance=self.provenance,
|
|
271
|
+
)
|
|
272
|
+
if self.effect == "PRESERVE" and self.after is None:
|
|
273
|
+
return self
|
|
274
|
+
return StereoEffect(
|
|
275
|
+
self.descriptor_target,
|
|
276
|
+
self.effect,
|
|
277
|
+
before=self.after,
|
|
278
|
+
after=self.before,
|
|
279
|
+
provenance=self.provenance,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def from_dict(cls, value: Mapping[str, Any]) -> "StereoEffect":
|
|
284
|
+
before = value.get("before")
|
|
285
|
+
after = value.get("after")
|
|
286
|
+
return cls(
|
|
287
|
+
descriptor_target=tuple(value["descriptor_target"]),
|
|
288
|
+
effect=value["effect"],
|
|
289
|
+
before=StereoDescriptor.from_dict(before) if before else None,
|
|
290
|
+
after=StereoDescriptor.from_dict(after) if after else None,
|
|
291
|
+
provenance=value.get("provenance", "annotated"),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@dataclass(frozen=True)
|
|
296
|
+
class ElectrocyclicStereoMotion:
|
|
297
|
+
"""Correlated terminal-orbital motion for a supplied electrocyclic step."""
|
|
298
|
+
|
|
299
|
+
mode: Literal["CONROTATORY", "DISROTATORY"]
|
|
300
|
+
direction: Literal["RING_CLOSURE", "RING_OPENING"]
|
|
301
|
+
termini: tuple[int, int]
|
|
302
|
+
substituents: tuple[int | str, int | str]
|
|
303
|
+
terminal_motion: tuple[int, int]
|
|
304
|
+
pi_electrons: int
|
|
305
|
+
activation: Literal["THERMAL", "PHOTOCHEMICAL"] | None
|
|
306
|
+
provenance: str = "annotated"
|
|
307
|
+
|
|
308
|
+
def __post_init__(self) -> None:
|
|
309
|
+
if self.mode not in {"CONROTATORY", "DISROTATORY"}:
|
|
310
|
+
raise MechanismModelError("Unsupported electrocyclic rotation mode.")
|
|
311
|
+
if self.direction not in {"RING_CLOSURE", "RING_OPENING"}:
|
|
312
|
+
raise MechanismModelError("Unsupported electrocyclic direction.")
|
|
313
|
+
if (
|
|
314
|
+
len(self.termini) != 2
|
|
315
|
+
or len(set(self.termini)) != 2
|
|
316
|
+
or any(type(value) is not int or value <= 0 for value in self.termini)
|
|
317
|
+
):
|
|
318
|
+
raise MechanismModelError(
|
|
319
|
+
"Electrocyclic motion requires two distinct mapped termini."
|
|
320
|
+
)
|
|
321
|
+
if len(self.substituents) != 2:
|
|
322
|
+
raise MechanismModelError(
|
|
323
|
+
"Electrocyclic motion requires one tracked substituent per terminus."
|
|
324
|
+
)
|
|
325
|
+
if len(set(self.substituents)) != 2:
|
|
326
|
+
raise MechanismModelError(
|
|
327
|
+
"Electrocyclic terminal substituents must be distinct."
|
|
328
|
+
)
|
|
329
|
+
from synkit.Graph.Stereo import parse_virtual_reference
|
|
330
|
+
|
|
331
|
+
for terminus, substituent in zip(self.termini, self.substituents):
|
|
332
|
+
if type(substituent) is int:
|
|
333
|
+
if substituent <= 0 or substituent == terminus:
|
|
334
|
+
raise MechanismModelError(
|
|
335
|
+
"Mapped terminal substituents must be positive and "
|
|
336
|
+
"distinct from their termini."
|
|
337
|
+
)
|
|
338
|
+
continue
|
|
339
|
+
virtual = parse_virtual_reference(substituent)
|
|
340
|
+
if virtual is None or virtual.center != terminus:
|
|
341
|
+
raise MechanismModelError(
|
|
342
|
+
"A virtual terminal substituent must belong to its terminus."
|
|
343
|
+
)
|
|
344
|
+
if len(self.terminal_motion) != 2 or any(
|
|
345
|
+
value not in {-1, 1} for value in self.terminal_motion
|
|
346
|
+
):
|
|
347
|
+
raise MechanismModelError(
|
|
348
|
+
"Terminal motions must be a pair of +1/-1 rotations."
|
|
349
|
+
)
|
|
350
|
+
if (
|
|
351
|
+
type(self.pi_electrons) is not int
|
|
352
|
+
or self.pi_electrons < 2
|
|
353
|
+
or self.pi_electrons % 2
|
|
354
|
+
):
|
|
355
|
+
raise MechanismModelError(
|
|
356
|
+
"Electrocyclic pi_electrons must be a positive even integer."
|
|
357
|
+
)
|
|
358
|
+
if self.activation not in {None, "THERMAL", "PHOTOCHEMICAL"}:
|
|
359
|
+
raise MechanismModelError(
|
|
360
|
+
"Activation must be THERMAL, PHOTOCHEMICAL, or absent."
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def expected_mode(self) -> str | None:
|
|
365
|
+
"""Return the Woodward-Hoffmann mode for supplied electron/context."""
|
|
366
|
+
if self.activation is None:
|
|
367
|
+
return None
|
|
368
|
+
thermal_conrotatory = self.pi_electrons % 4 == 0
|
|
369
|
+
conrotatory = (
|
|
370
|
+
thermal_conrotatory
|
|
371
|
+
if self.activation == "THERMAL"
|
|
372
|
+
else not thermal_conrotatory
|
|
373
|
+
)
|
|
374
|
+
return "CONROTATORY" if conrotatory else "DISROTATORY"
|
|
375
|
+
|
|
376
|
+
def verification_issues(
|
|
377
|
+
self,
|
|
378
|
+
*,
|
|
379
|
+
step_id: str,
|
|
380
|
+
before_graph: Any | None = None,
|
|
381
|
+
graph: Any | None = None,
|
|
382
|
+
) -> tuple[VerificationIssue, ...]:
|
|
383
|
+
"""Validate context, orbital rule, and correlation as one assertion."""
|
|
384
|
+
_changes, issues = self.verification_evidence(
|
|
385
|
+
step_id=step_id,
|
|
386
|
+
before_graph=before_graph,
|
|
387
|
+
graph=graph,
|
|
388
|
+
)
|
|
389
|
+
return issues
|
|
390
|
+
|
|
391
|
+
def verification_evidence(
|
|
392
|
+
self,
|
|
393
|
+
*,
|
|
394
|
+
step_id: str,
|
|
395
|
+
before_graph: Any | None = None,
|
|
396
|
+
graph: Any | None = None,
|
|
397
|
+
) -> tuple[tuple[Any, ...], tuple[VerificationIssue, ...]]:
|
|
398
|
+
"""Return canonical local-neighbor changes and validation issues."""
|
|
399
|
+
issues: list[VerificationIssue] = []
|
|
400
|
+
if self.activation is None:
|
|
401
|
+
issues.append(
|
|
402
|
+
VerificationIssue(
|
|
403
|
+
"ELECTROCYCLIC_CONTEXT_REQUIRED",
|
|
404
|
+
"Electrocyclic mode requires thermal or photochemical context.",
|
|
405
|
+
step_id=step_id,
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
elif self.mode != self.expected_mode:
|
|
409
|
+
issues.append(
|
|
410
|
+
VerificationIssue(
|
|
411
|
+
"ELECTROCYCLIC_MODE_MISMATCH",
|
|
412
|
+
"Declared rotation mode violates the supplied activation "
|
|
413
|
+
"and pi-electron count.",
|
|
414
|
+
step_id=step_id,
|
|
415
|
+
expected=self.expected_mode,
|
|
416
|
+
observed=self.mode,
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
changes: tuple[Any, ...] = ()
|
|
420
|
+
if before_graph is not None and graph is not None:
|
|
421
|
+
from .neighbor_stereo import derive_relative_neighbor_changes
|
|
422
|
+
|
|
423
|
+
changes, neighbor_issues = derive_relative_neighbor_changes(
|
|
424
|
+
self,
|
|
425
|
+
before_graph,
|
|
426
|
+
graph,
|
|
427
|
+
step_id=step_id,
|
|
428
|
+
)
|
|
429
|
+
issues.extend(neighbor_issues)
|
|
430
|
+
physical_rotations = (
|
|
431
|
+
tuple(change.physical_rotation for change in changes)
|
|
432
|
+
if len(changes) == 2
|
|
433
|
+
else self.terminal_motion
|
|
434
|
+
)
|
|
435
|
+
same_sense = physical_rotations[0] == physical_rotations[1]
|
|
436
|
+
if same_sense != (self.mode == "CONROTATORY"):
|
|
437
|
+
issues.append(
|
|
438
|
+
VerificationIssue(
|
|
439
|
+
"ELECTROCYCLIC_MOTION_INCONSISTENT",
|
|
440
|
+
"Terminal rotations do not realize the declared correlated "
|
|
441
|
+
"mode.",
|
|
442
|
+
step_id=step_id,
|
|
443
|
+
expected=self.mode,
|
|
444
|
+
observed=physical_rotations,
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
if graph is not None:
|
|
448
|
+
by_map = {
|
|
449
|
+
attrs.get("atom_map") or node: node
|
|
450
|
+
for node, attrs in graph.nodes(data=True)
|
|
451
|
+
}
|
|
452
|
+
if not set(self.termini) <= set(by_map):
|
|
453
|
+
issues.append(
|
|
454
|
+
VerificationIssue(
|
|
455
|
+
"ELECTROCYCLIC_SUPPORT_INVALID",
|
|
456
|
+
"A correlated terminal map is absent after the step.",
|
|
457
|
+
step_id=step_id,
|
|
458
|
+
expected=self.termini,
|
|
459
|
+
observed=tuple(sorted(by_map)),
|
|
460
|
+
)
|
|
461
|
+
)
|
|
462
|
+
else:
|
|
463
|
+
left, right = (by_map[value] for value in self.termini)
|
|
464
|
+
bonded = graph.has_edge(left, right)
|
|
465
|
+
expected_bonded = self.direction == "RING_CLOSURE"
|
|
466
|
+
if bonded != expected_bonded:
|
|
467
|
+
issues.append(
|
|
468
|
+
VerificationIssue(
|
|
469
|
+
"ELECTROCYCLIC_SUPPORT_INVALID",
|
|
470
|
+
"Terminal bond topology contradicts the declared "
|
|
471
|
+
"ring direction.",
|
|
472
|
+
step_id=step_id,
|
|
473
|
+
expected=expected_bonded,
|
|
474
|
+
observed=bonded,
|
|
475
|
+
)
|
|
476
|
+
)
|
|
477
|
+
return changes, tuple(issues)
|
|
478
|
+
|
|
479
|
+
def reversed(self) -> "ElectrocyclicStereoMotion":
|
|
480
|
+
"""Reverse closure/opening and both physical terminal rotations."""
|
|
481
|
+
return ElectrocyclicStereoMotion(
|
|
482
|
+
self.mode,
|
|
483
|
+
("RING_OPENING" if self.direction == "RING_CLOSURE" else "RING_CLOSURE"),
|
|
484
|
+
self.termini,
|
|
485
|
+
self.substituents,
|
|
486
|
+
tuple(-value for value in self.terminal_motion),
|
|
487
|
+
self.pi_electrons,
|
|
488
|
+
self.activation,
|
|
489
|
+
self.provenance,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
def to_dict(self) -> dict[str, Any]:
|
|
493
|
+
return {
|
|
494
|
+
"kind": "ELECTROCYCLIC",
|
|
495
|
+
"mode": self.mode,
|
|
496
|
+
"direction": self.direction,
|
|
497
|
+
"termini": list(self.termini),
|
|
498
|
+
"substituents": list(self.substituents),
|
|
499
|
+
"terminal_motion": list(self.terminal_motion),
|
|
500
|
+
"pi_electrons": self.pi_electrons,
|
|
501
|
+
"activation": self.activation,
|
|
502
|
+
"provenance": self.provenance,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
@classmethod
|
|
506
|
+
def from_dict(
|
|
507
|
+
cls,
|
|
508
|
+
value: Mapping[str, Any],
|
|
509
|
+
) -> "ElectrocyclicStereoMotion":
|
|
510
|
+
if value.get("kind", "ELECTROCYCLIC") != "ELECTROCYCLIC":
|
|
511
|
+
raise MechanismModelError("Unsupported correlated stereo motion.")
|
|
512
|
+
return cls(
|
|
513
|
+
value["mode"],
|
|
514
|
+
value["direction"],
|
|
515
|
+
tuple(value["termini"]),
|
|
516
|
+
tuple(value["substituents"]),
|
|
517
|
+
tuple(value["terminal_motion"]),
|
|
518
|
+
int(value["pi_electrons"]),
|
|
519
|
+
value.get("activation"),
|
|
520
|
+
value.get("provenance", "annotated"),
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@dataclass(frozen=True)
|
|
525
|
+
class MechanisticStep:
|
|
526
|
+
step_id: str
|
|
527
|
+
groups: tuple[ElectronMoveGroup, ...]
|
|
528
|
+
stereo_effects: tuple[StereoEffect, ...] = ()
|
|
529
|
+
stereo_motions: tuple[ElectrocyclicStereoMotion, ...] = ()
|
|
530
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
531
|
+
|
|
532
|
+
def __post_init__(self) -> None:
|
|
533
|
+
if not self.step_id:
|
|
534
|
+
raise MechanismModelError("MechanisticStep requires a step_id.")
|
|
535
|
+
object.__setattr__(self, "metadata", dict(self.metadata))
|
|
536
|
+
|
|
537
|
+
def to_dict(self) -> dict[str, Any]:
|
|
538
|
+
return {
|
|
539
|
+
"step_id": self.step_id,
|
|
540
|
+
"groups": [group.to_dict() for group in self.groups],
|
|
541
|
+
"stereo_effects": [effect.to_dict() for effect in self.stereo_effects],
|
|
542
|
+
"stereo_motions": [motion.to_dict() for motion in self.stereo_motions],
|
|
543
|
+
"metadata": dict(self.metadata),
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
def reversed(self) -> "MechanisticStep":
|
|
547
|
+
"""Reverse group order, electron flow, and stereo transitions."""
|
|
548
|
+
return MechanisticStep(
|
|
549
|
+
step_id=self.step_id,
|
|
550
|
+
groups=tuple(group.reversed() for group in reversed(self.groups)),
|
|
551
|
+
stereo_effects=tuple(
|
|
552
|
+
effect.reversed() for effect in reversed(self.stereo_effects)
|
|
553
|
+
),
|
|
554
|
+
stereo_motions=tuple(
|
|
555
|
+
motion.reversed() for motion in reversed(self.stereo_motions)
|
|
556
|
+
),
|
|
557
|
+
metadata=self.metadata,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
@classmethod
|
|
561
|
+
def from_dict(cls, value: Mapping[str, Any]) -> "MechanisticStep":
|
|
562
|
+
return cls(
|
|
563
|
+
step_id=value["step_id"],
|
|
564
|
+
groups=tuple(
|
|
565
|
+
ElectronMoveGroup.from_dict(group) for group in value["groups"]
|
|
566
|
+
),
|
|
567
|
+
stereo_effects=tuple(
|
|
568
|
+
StereoEffect.from_dict(effect)
|
|
569
|
+
for effect in value.get("stereo_effects", ())
|
|
570
|
+
),
|
|
571
|
+
stereo_motions=tuple(
|
|
572
|
+
ElectrocyclicStereoMotion.from_dict(motion)
|
|
573
|
+
for motion in value.get("stereo_motions", ())
|
|
574
|
+
),
|
|
575
|
+
metadata=value.get("metadata", {}),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
@dataclass(frozen=True)
|
|
580
|
+
class VerificationCertificate:
|
|
581
|
+
status: Literal["VALID", "INVALID", "INCOMPLETE", "UNSUPPORTED"]
|
|
582
|
+
schema_version: str = SCHEMA_VERSION
|
|
583
|
+
verification_level: Literal[
|
|
584
|
+
"EXACT", "DELTA_CONSISTENT", "NORMALIZED", "INVALID"
|
|
585
|
+
] = "INVALID"
|
|
586
|
+
transition_valid: bool = False
|
|
587
|
+
delta_charge_match: bool = False
|
|
588
|
+
endpoint_match: bool = False
|
|
589
|
+
absolute_lwg_valid: bool = False
|
|
590
|
+
initial_absolute_residuals: Mapping[int, int | float] = field(
|
|
591
|
+
default_factory=dict
|
|
592
|
+
)
|
|
593
|
+
product_absolute_residuals: Mapping[int, int | float] = field(
|
|
594
|
+
default_factory=dict
|
|
595
|
+
)
|
|
596
|
+
initial_global_electron_residual: int | float = 0
|
|
597
|
+
product_global_electron_residual: int | float = 0
|
|
598
|
+
absolute_residuals_invariant: bool = False
|
|
599
|
+
endpoint_resource_policy: str = "strict"
|
|
600
|
+
normalization_evidence: tuple[Mapping[str, Any], ...] = ()
|
|
601
|
+
diagnostics: tuple[str, ...] = ()
|
|
602
|
+
step_reports: tuple[Mapping[str, Any], ...] = ()
|
|
603
|
+
issues: tuple[VerificationIssue, ...] = ()
|
|
604
|
+
final_match: Mapping[str, Any] = field(default_factory=dict)
|
|
605
|
+
repaired: bool = False
|
|
606
|
+
|
|
607
|
+
def to_dict(self) -> dict[str, Any]:
|
|
608
|
+
return {
|
|
609
|
+
"status": self.status,
|
|
610
|
+
"schema_version": self.schema_version,
|
|
611
|
+
"verification_level": self.verification_level,
|
|
612
|
+
"transition_valid": self.transition_valid,
|
|
613
|
+
"delta_charge_match": self.delta_charge_match,
|
|
614
|
+
"endpoint_match": self.endpoint_match,
|
|
615
|
+
"absolute_lwg_valid": self.absolute_lwg_valid,
|
|
616
|
+
"initial_absolute_residuals": dict(self.initial_absolute_residuals),
|
|
617
|
+
"product_absolute_residuals": dict(self.product_absolute_residuals),
|
|
618
|
+
"initial_global_electron_residual": self.initial_global_electron_residual,
|
|
619
|
+
"product_global_electron_residual": self.product_global_electron_residual,
|
|
620
|
+
"absolute_residuals_invariant": self.absolute_residuals_invariant,
|
|
621
|
+
"endpoint_resource_policy": self.endpoint_resource_policy,
|
|
622
|
+
"normalization_evidence": [
|
|
623
|
+
dict(item) for item in self.normalization_evidence
|
|
624
|
+
],
|
|
625
|
+
"diagnostics": list(self.diagnostics),
|
|
626
|
+
"step_reports": [dict(report) for report in self.step_reports],
|
|
627
|
+
"issues": [issue.to_dict() for issue in self.issues],
|
|
628
|
+
"final_match": dict(self.final_match),
|
|
629
|
+
"repaired": self.repaired,
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
@classmethod
|
|
633
|
+
def from_dict(cls, value: Mapping[str, Any]) -> "VerificationCertificate":
|
|
634
|
+
return cls(
|
|
635
|
+
status=value["status"],
|
|
636
|
+
schema_version=value.get("schema_version", SCHEMA_VERSION),
|
|
637
|
+
verification_level=value.get("verification_level", "INVALID"),
|
|
638
|
+
transition_valid=bool(value.get("transition_valid", False)),
|
|
639
|
+
delta_charge_match=bool(value.get("delta_charge_match", False)),
|
|
640
|
+
endpoint_match=bool(value.get("endpoint_match", False)),
|
|
641
|
+
absolute_lwg_valid=bool(value.get("absolute_lwg_valid", False)),
|
|
642
|
+
initial_absolute_residuals={
|
|
643
|
+
int(atom_map): residual
|
|
644
|
+
for atom_map, residual in value.get(
|
|
645
|
+
"initial_absolute_residuals", {}
|
|
646
|
+
).items()
|
|
647
|
+
},
|
|
648
|
+
product_absolute_residuals={
|
|
649
|
+
int(atom_map): residual
|
|
650
|
+
for atom_map, residual in value.get(
|
|
651
|
+
"product_absolute_residuals", {}
|
|
652
|
+
).items()
|
|
653
|
+
},
|
|
654
|
+
initial_global_electron_residual=value.get(
|
|
655
|
+
"initial_global_electron_residual", 0
|
|
656
|
+
),
|
|
657
|
+
product_global_electron_residual=value.get(
|
|
658
|
+
"product_global_electron_residual", 0
|
|
659
|
+
),
|
|
660
|
+
absolute_residuals_invariant=bool(
|
|
661
|
+
value.get("absolute_residuals_invariant", False)
|
|
662
|
+
),
|
|
663
|
+
endpoint_resource_policy=value.get("endpoint_resource_policy", "strict"),
|
|
664
|
+
normalization_evidence=tuple(value.get("normalization_evidence", ())),
|
|
665
|
+
diagnostics=tuple(value.get("diagnostics", ())),
|
|
666
|
+
step_reports=tuple(value.get("step_reports", ())),
|
|
667
|
+
issues=tuple(
|
|
668
|
+
VerificationIssue.from_dict(issue) for issue in value.get("issues", ())
|
|
669
|
+
),
|
|
670
|
+
final_match=value.get("final_match", {}),
|
|
671
|
+
repaired=bool(value.get("repaired", False)),
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
@dataclass(frozen=True)
|
|
676
|
+
class MechanismRecord:
|
|
677
|
+
"""Versioned record for a supplied, mapped mechanism annotation."""
|
|
678
|
+
|
|
679
|
+
mapped_reaction: str
|
|
680
|
+
steps: tuple[MechanisticStep, ...]
|
|
681
|
+
schema_version: str = SCHEMA_VERSION
|
|
682
|
+
provenance: Mapping[str, Any] = field(default_factory=dict)
|
|
683
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
684
|
+
endpoint_stereo: Mapping[str, Mapping[str, StereoDescriptor]] = field(
|
|
685
|
+
default_factory=dict
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
def __post_init__(self) -> None:
|
|
689
|
+
if self.mapped_reaction.count(">>") != 1:
|
|
690
|
+
raise MechanismModelError("mapped_reaction must contain exactly one '>>'.")
|
|
691
|
+
if len({step.step_id for step in self.steps}) != len(self.steps):
|
|
692
|
+
raise MechanismModelError("Mechanistic step IDs must be unique.")
|
|
693
|
+
object.__setattr__(self, "provenance", dict(self.provenance))
|
|
694
|
+
object.__setattr__(self, "metadata", dict(self.metadata))
|
|
695
|
+
endpoint_stereo: dict[str, dict[str, StereoDescriptor]] = {}
|
|
696
|
+
for side, registry in self.endpoint_stereo.items():
|
|
697
|
+
if side not in {"reactant", "product"}:
|
|
698
|
+
raise MechanismModelError(
|
|
699
|
+
"Endpoint stereo side must be 'reactant' or 'product'."
|
|
700
|
+
)
|
|
701
|
+
endpoint_stereo[side] = {}
|
|
702
|
+
for key, descriptor in registry.items():
|
|
703
|
+
if not isinstance(descriptor, StereoDescriptor):
|
|
704
|
+
raise MechanismModelError(
|
|
705
|
+
"Endpoint stereo registries require StereoDescriptor values."
|
|
706
|
+
)
|
|
707
|
+
if descriptor.target_key != key:
|
|
708
|
+
raise MechanismModelError(
|
|
709
|
+
f"Endpoint descriptor belongs to {descriptor.target_key}, "
|
|
710
|
+
f"not {key}."
|
|
711
|
+
)
|
|
712
|
+
endpoint_stereo[side][key] = descriptor
|
|
713
|
+
object.__setattr__(self, "endpoint_stereo", endpoint_stereo)
|
|
714
|
+
|
|
715
|
+
@classmethod
|
|
716
|
+
def from_ef_smirks(cls, text: str, **kwargs: Any) -> "MechanismRecord":
|
|
717
|
+
from .adapters import mechanism_from_ef_smirks
|
|
718
|
+
|
|
719
|
+
return mechanism_from_ef_smirks(text, **kwargs)
|
|
720
|
+
|
|
721
|
+
def grammar_issues(self) -> tuple[VerificationIssue, ...]:
|
|
722
|
+
return tuple(
|
|
723
|
+
issue
|
|
724
|
+
for step in self.steps
|
|
725
|
+
for group in step.groups
|
|
726
|
+
for issue in group.issues()
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
def reversed(self) -> "MechanismRecord":
|
|
730
|
+
"""Return the typed mechanism operating from product to reactant."""
|
|
731
|
+
reactants, products = self.mapped_reaction.split(">>", 1)
|
|
732
|
+
return MechanismRecord(
|
|
733
|
+
mapped_reaction=f"{products}>>{reactants}",
|
|
734
|
+
steps=tuple(step.reversed() for step in reversed(self.steps)),
|
|
735
|
+
schema_version=self.schema_version,
|
|
736
|
+
provenance=self.provenance,
|
|
737
|
+
metadata=self.metadata,
|
|
738
|
+
endpoint_stereo={
|
|
739
|
+
side: self.endpoint_stereo[other]
|
|
740
|
+
for side, other in (
|
|
741
|
+
("reactant", "product"),
|
|
742
|
+
("product", "reactant"),
|
|
743
|
+
)
|
|
744
|
+
if other in self.endpoint_stereo
|
|
745
|
+
},
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
def verify(
|
|
749
|
+
self,
|
|
750
|
+
*,
|
|
751
|
+
electron: Literal["strict", "diagnostic"] = "strict",
|
|
752
|
+
stereo: Literal["off", "endpoint", "stepwise"] = "off",
|
|
753
|
+
repair: bool = False,
|
|
754
|
+
) -> VerificationCertificate:
|
|
755
|
+
"""Replay this supplied mechanism and return its certificate."""
|
|
756
|
+
from .replay import MechanismReplayer
|
|
757
|
+
|
|
758
|
+
return (
|
|
759
|
+
MechanismReplayer(
|
|
760
|
+
validation=electron,
|
|
761
|
+
verify_stereo=stereo,
|
|
762
|
+
repair=repair,
|
|
763
|
+
)
|
|
764
|
+
.replay(self)
|
|
765
|
+
.certificate
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
def to_mtg(
|
|
769
|
+
self,
|
|
770
|
+
*,
|
|
771
|
+
electron: Literal["strict", "diagnostic"] = "strict",
|
|
772
|
+
stereo: Literal["off", "endpoint", "stepwise"] = "stepwise",
|
|
773
|
+
) -> Any:
|
|
774
|
+
"""Replay and return the mechanism trajectory graph."""
|
|
775
|
+
from .replay import MechanismReplayer
|
|
776
|
+
|
|
777
|
+
return (
|
|
778
|
+
MechanismReplayer(validation=electron, verify_stereo=stereo)
|
|
779
|
+
.replay(self)
|
|
780
|
+
.mtg
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
def to_rule(
|
|
784
|
+
self,
|
|
785
|
+
*,
|
|
786
|
+
core: bool = False,
|
|
787
|
+
implicit_h: bool = False,
|
|
788
|
+
name: str | None = None,
|
|
789
|
+
) -> Any:
|
|
790
|
+
"""Build a tuple :class:`SynRule` from the same mapped endpoints.
|
|
791
|
+
|
|
792
|
+
Explicit endpoint stereo sidecars replace the corresponding SMILES
|
|
793
|
+
registry, which keeps unknown stereo and backend-independent relative
|
|
794
|
+
descriptors available at the rule boundary. ``core=False`` is the
|
|
795
|
+
conservative default because it preserves the complete reviewed
|
|
796
|
+
endpoint context used for rule/replay agreement checks.
|
|
797
|
+
"""
|
|
798
|
+
from synkit.Graph.Stereo import (
|
|
799
|
+
StereoChange,
|
|
800
|
+
classify_stereo_change,
|
|
801
|
+
stereo_from_dict,
|
|
802
|
+
)
|
|
803
|
+
from synkit.IO.chem_converter import rsmi_to_its
|
|
804
|
+
from synkit.Rule import SynRule
|
|
805
|
+
|
|
806
|
+
its = rsmi_to_its(
|
|
807
|
+
self.mapped_reaction,
|
|
808
|
+
core=core,
|
|
809
|
+
format="tuple",
|
|
810
|
+
drop_non_aam=False,
|
|
811
|
+
use_index_as_atom_map=True,
|
|
812
|
+
)
|
|
813
|
+
registries = {
|
|
814
|
+
side: dict(registry)
|
|
815
|
+
for side, registry in its.graph.get("stereo_descriptors", {}).items()
|
|
816
|
+
}
|
|
817
|
+
for side, registry in self.endpoint_stereo.items():
|
|
818
|
+
registries[side] = {
|
|
819
|
+
key: stereo_from_dict(descriptor.to_dict())
|
|
820
|
+
for key, descriptor in registry.items()
|
|
821
|
+
}
|
|
822
|
+
registries.setdefault("reactant", {})
|
|
823
|
+
registries.setdefault("product", {})
|
|
824
|
+
its.graph["stereo_descriptors"] = registries
|
|
825
|
+
its.graph["stereo_changes"] = {
|
|
826
|
+
key: StereoChange(
|
|
827
|
+
classify_stereo_change(
|
|
828
|
+
registries["reactant"].get(key),
|
|
829
|
+
registries["product"].get(key),
|
|
830
|
+
),
|
|
831
|
+
registries["reactant"].get(key),
|
|
832
|
+
registries["product"].get(key),
|
|
833
|
+
)
|
|
834
|
+
for key in set(registries["reactant"]) | set(registries["product"])
|
|
835
|
+
}
|
|
836
|
+
return SynRule(
|
|
837
|
+
its,
|
|
838
|
+
name=name,
|
|
839
|
+
format="tuple",
|
|
840
|
+
implicit_h=implicit_h,
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
def draw(
|
|
844
|
+
self,
|
|
845
|
+
*,
|
|
846
|
+
show_certificate: bool = False,
|
|
847
|
+
certificate: VerificationCertificate | None = None,
|
|
848
|
+
path: str | Path | None = None,
|
|
849
|
+
) -> Any:
|
|
850
|
+
"""Create a headless static mechanism overview figure."""
|
|
851
|
+
from .drawing import draw_mechanism_record
|
|
852
|
+
|
|
853
|
+
if show_certificate and certificate is None:
|
|
854
|
+
certificate = self.verify(stereo="stepwise")
|
|
855
|
+
return draw_mechanism_record(self, certificate=certificate, path=path)
|
|
856
|
+
|
|
857
|
+
def to_json(self, path: str | Path | None = None, *, indent: int = 2) -> str:
|
|
858
|
+
"""Serialize the versioned record and optionally write it to disk."""
|
|
859
|
+
payload = json.dumps(
|
|
860
|
+
self.to_dict(), ensure_ascii=False, indent=indent, sort_keys=True
|
|
861
|
+
)
|
|
862
|
+
if path is not None:
|
|
863
|
+
Path(path).write_text(payload + "\n", encoding="utf-8")
|
|
864
|
+
return payload
|
|
865
|
+
|
|
866
|
+
@classmethod
|
|
867
|
+
def from_json(cls, value: str | Path) -> "MechanismRecord":
|
|
868
|
+
"""Read a JSON string or an existing JSON path."""
|
|
869
|
+
if isinstance(value, Path):
|
|
870
|
+
text = value.read_text(encoding="utf-8")
|
|
871
|
+
else:
|
|
872
|
+
stripped = value.lstrip()
|
|
873
|
+
if stripped.startswith(("{", "[")):
|
|
874
|
+
text = value
|
|
875
|
+
else:
|
|
876
|
+
candidate = Path(value)
|
|
877
|
+
text = (
|
|
878
|
+
candidate.read_text(encoding="utf-8")
|
|
879
|
+
if candidate.exists()
|
|
880
|
+
else value
|
|
881
|
+
)
|
|
882
|
+
return cls.from_dict(json.loads(text))
|
|
883
|
+
|
|
884
|
+
def to_dict(self) -> dict[str, Any]:
|
|
885
|
+
return {
|
|
886
|
+
"schema_version": self.schema_version,
|
|
887
|
+
"mapped_reaction": self.mapped_reaction,
|
|
888
|
+
"steps": [step.to_dict() for step in self.steps],
|
|
889
|
+
"provenance": dict(self.provenance),
|
|
890
|
+
"metadata": dict(self.metadata),
|
|
891
|
+
"endpoint_stereo": {
|
|
892
|
+
side: {
|
|
893
|
+
key: descriptor.to_dict() for key, descriptor in registry.items()
|
|
894
|
+
}
|
|
895
|
+
for side, registry in self.endpoint_stereo.items()
|
|
896
|
+
},
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
@classmethod
|
|
900
|
+
def from_dict(cls, value: Mapping[str, Any]) -> "MechanismRecord":
|
|
901
|
+
return cls(
|
|
902
|
+
mapped_reaction=value["mapped_reaction"],
|
|
903
|
+
steps=tuple(MechanisticStep.from_dict(step) for step in value["steps"]),
|
|
904
|
+
schema_version=value.get("schema_version", SCHEMA_VERSION),
|
|
905
|
+
provenance=value.get("provenance", {}),
|
|
906
|
+
metadata=value.get("metadata", {}),
|
|
907
|
+
endpoint_stereo={
|
|
908
|
+
side: {
|
|
909
|
+
key: StereoDescriptor.from_dict(descriptor)
|
|
910
|
+
for key, descriptor in registry.items()
|
|
911
|
+
}
|
|
912
|
+
for side, registry in value.get("endpoint_stereo", {}).items()
|
|
913
|
+
},
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def issues_are_errors(issues: Iterable[VerificationIssue]) -> bool:
|
|
918
|
+
return any(issue.severity == "error" for issue in issues)
|