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,740 @@
|
|
|
1
|
+
"""Audited adapter for SynKit's partial-AAM radical mechanism dataset."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import re
|
|
7
|
+
from collections import Counter, defaultdict
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from itertools import combinations
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Iterable, Sequence
|
|
12
|
+
|
|
13
|
+
from rdkit import Chem
|
|
14
|
+
|
|
15
|
+
from synkit.Graph.ITS.its_expand import ITSExpand
|
|
16
|
+
from synkit.Graph.Mech.conversion import (
|
|
17
|
+
build_its_from_rsmi,
|
|
18
|
+
typed_convert_arrow_code,
|
|
19
|
+
validate_arrow_maps,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .model import (
|
|
23
|
+
ElectronLocus,
|
|
24
|
+
ElectronMove,
|
|
25
|
+
ElectronMoveGroup,
|
|
26
|
+
MechanismRecord,
|
|
27
|
+
MechanisticStep,
|
|
28
|
+
VerificationIssue,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
RADICAL_CLASS_TO_MACRO = {
|
|
32
|
+
"homolyze": "HOMOLYSIS",
|
|
33
|
+
"recombine": "RECOMBINATION",
|
|
34
|
+
"addition": "RADICAL_ADDITION",
|
|
35
|
+
"retroaddition": "BETA_SCISSION",
|
|
36
|
+
"abstraction": "H_ABSTRACTION",
|
|
37
|
+
"resonance": "RADICAL_RESONANCE",
|
|
38
|
+
"ha resonance": "LONE_PAIR_RADICAL_RELOCATION",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class RadicalAAMCompletionResult:
|
|
44
|
+
"""Fail-closed evidence for arrow-independent radical AAM completion."""
|
|
45
|
+
|
|
46
|
+
source_reaction: str
|
|
47
|
+
mapped_reaction: str | None
|
|
48
|
+
status: str
|
|
49
|
+
method: str | None
|
|
50
|
+
source_was_complete: bool
|
|
51
|
+
all_atoms_mapped: bool = False
|
|
52
|
+
unique_atom_maps: bool = False
|
|
53
|
+
balanced_atom_maps: bool = False
|
|
54
|
+
source_anchors_preserved: bool = False
|
|
55
|
+
constitution_preserved: bool = False
|
|
56
|
+
expansion_side: str | None = None
|
|
57
|
+
fallback_used: bool = False
|
|
58
|
+
unmapped_explicit_hydrogens_folded: bool = False
|
|
59
|
+
folded_unmapped_explicit_hydrogen_count: int = 0
|
|
60
|
+
explicit_hydrogen_serialization: bool = False
|
|
61
|
+
stereochemistry_ignored_for_expansion: bool = False
|
|
62
|
+
radical_state_preserved: bool = False
|
|
63
|
+
failure_reason: str | None = None
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def usable(self) -> bool:
|
|
67
|
+
"""Whether the result is a validated complete atom mapping."""
|
|
68
|
+
return self.status in {"ALREADY_COMPLETE", "COMPLETED"}
|
|
69
|
+
|
|
70
|
+
def to_dict(self) -> dict[str, Any]:
|
|
71
|
+
return {
|
|
72
|
+
"source_reaction": self.source_reaction,
|
|
73
|
+
"mapped_reaction": self.mapped_reaction,
|
|
74
|
+
"status": self.status,
|
|
75
|
+
"method": self.method,
|
|
76
|
+
"source_was_complete": self.source_was_complete,
|
|
77
|
+
"usable": self.usable,
|
|
78
|
+
"all_atoms_mapped": self.all_atoms_mapped,
|
|
79
|
+
"unique_atom_maps": self.unique_atom_maps,
|
|
80
|
+
"balanced_atom_maps": self.balanced_atom_maps,
|
|
81
|
+
"source_anchors_preserved": self.source_anchors_preserved,
|
|
82
|
+
"constitution_preserved": self.constitution_preserved,
|
|
83
|
+
"expansion_side": self.expansion_side,
|
|
84
|
+
"fallback_used": self.fallback_used,
|
|
85
|
+
"unmapped_explicit_hydrogens_folded": (
|
|
86
|
+
self.unmapped_explicit_hydrogens_folded
|
|
87
|
+
),
|
|
88
|
+
"folded_unmapped_explicit_hydrogen_count": (
|
|
89
|
+
self.folded_unmapped_explicit_hydrogen_count
|
|
90
|
+
),
|
|
91
|
+
"explicit_hydrogen_serialization": (self.explicit_hydrogen_serialization),
|
|
92
|
+
"stereochemistry_ignored_for_expansion": (
|
|
93
|
+
self.stereochemistry_ignored_for_expansion
|
|
94
|
+
),
|
|
95
|
+
"radical_state_preserved": self.radical_state_preserved,
|
|
96
|
+
"failure_reason": self.failure_reason,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class RadicalNormalizationReport:
|
|
102
|
+
row_number: int
|
|
103
|
+
status: str
|
|
104
|
+
source_class: str
|
|
105
|
+
macro: str | None = None
|
|
106
|
+
aam_expanded: bool = False
|
|
107
|
+
aam_expansion_side: str | None = None
|
|
108
|
+
aam_fallback_used: bool = False
|
|
109
|
+
constitution_checked: bool = False
|
|
110
|
+
unmapped_explicit_hydrogens_folded: bool = False
|
|
111
|
+
folded_unmapped_explicit_hydrogen_count: int = 0
|
|
112
|
+
stereochemistry_ignored_for_expansion: bool = False
|
|
113
|
+
radical_state_preserved: bool = False
|
|
114
|
+
equivalent_map_swap: tuple[int, int] | None = None
|
|
115
|
+
arrow_repair_assessment: str | None = None
|
|
116
|
+
spin_assessment: str = "UNASSESSED_NO_SOURCE_EVIDENCE"
|
|
117
|
+
aliases_normalized: tuple[str, ...] = ()
|
|
118
|
+
issues: tuple[VerificationIssue, ...] = ()
|
|
119
|
+
|
|
120
|
+
def to_dict(self) -> dict[str, Any]:
|
|
121
|
+
return {
|
|
122
|
+
"row_number": self.row_number,
|
|
123
|
+
"status": self.status,
|
|
124
|
+
"source_class": self.source_class,
|
|
125
|
+
"macro": self.macro,
|
|
126
|
+
"aam_expanded": self.aam_expanded,
|
|
127
|
+
"aam_expansion_side": self.aam_expansion_side,
|
|
128
|
+
"aam_fallback_used": self.aam_fallback_used,
|
|
129
|
+
"constitution_checked": self.constitution_checked,
|
|
130
|
+
"unmapped_explicit_hydrogens_folded": (
|
|
131
|
+
self.unmapped_explicit_hydrogens_folded
|
|
132
|
+
),
|
|
133
|
+
"folded_unmapped_explicit_hydrogen_count": (
|
|
134
|
+
self.folded_unmapped_explicit_hydrogen_count
|
|
135
|
+
),
|
|
136
|
+
"stereochemistry_ignored_for_expansion": (
|
|
137
|
+
self.stereochemistry_ignored_for_expansion
|
|
138
|
+
),
|
|
139
|
+
"radical_state_preserved": self.radical_state_preserved,
|
|
140
|
+
"equivalent_map_swap": (
|
|
141
|
+
list(self.equivalent_map_swap) if self.equivalent_map_swap else None
|
|
142
|
+
),
|
|
143
|
+
"arrow_repair_assessment": self.arrow_repair_assessment,
|
|
144
|
+
"spin_assessment": self.spin_assessment,
|
|
145
|
+
"aliases_normalized": list(self.aliases_normalized),
|
|
146
|
+
"issues": [issue.to_dict() for issue in self.issues],
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class RadicalDatasetRecord:
|
|
152
|
+
source_row: tuple[str, ...]
|
|
153
|
+
report: RadicalNormalizationReport
|
|
154
|
+
mechanism: MechanismRecord | None = None
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def accepted(self) -> bool:
|
|
158
|
+
return self.mechanism is not None and self.report.status == "NORMALIZED"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _split_dataset_text(text: str) -> tuple[str, str]:
|
|
162
|
+
try:
|
|
163
|
+
reaction, flow = text.strip().split(None, 1)
|
|
164
|
+
except ValueError as exc:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"Radical record must contain reaction SMILES followed by electron flow."
|
|
167
|
+
) from exc
|
|
168
|
+
if reaction.count(">>") != 1:
|
|
169
|
+
raise ValueError("Radical record must contain one reaction '>>'.")
|
|
170
|
+
return reaction, flow
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _normalize_dataset_flow(flow: str) -> tuple[str, tuple[str, ...]]:
|
|
174
|
+
"""Normalize unambiguous legacy flow separators to internal equals form."""
|
|
175
|
+
steps: list[str] = []
|
|
176
|
+
aliases: list[str] = []
|
|
177
|
+
atom_list = r"\d+(?:\s*,\s*\d+)*"
|
|
178
|
+
if any(raw_step != raw_step.strip() for raw_step in flow.split(";")):
|
|
179
|
+
aliases.append("FlowWhitespace→canonical")
|
|
180
|
+
|
|
181
|
+
for raw_step in flow.split(";"):
|
|
182
|
+
step = raw_step.strip()
|
|
183
|
+
if not step:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
if step.count("=") == 1 and "-" not in step:
|
|
187
|
+
lhs, rhs = step.split("=", 1)
|
|
188
|
+
aliases.append("FlowSeparator=→-")
|
|
189
|
+
elif step.count("-") == 1 and "=" not in step:
|
|
190
|
+
lhs, rhs = step.split("-", 1)
|
|
191
|
+
elif step.count("-") == 2 and "=" not in step:
|
|
192
|
+
match = re.fullmatch(rf"\s*({atom_list})\s*-\s*(\d+)\s*-\s*(\d+)\s*", step)
|
|
193
|
+
if match is None:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"MALFORMED_FLOW_SEPARATOR: cannot normalize {step!r}."
|
|
196
|
+
)
|
|
197
|
+
lhs, rhs_a, rhs_b = match.groups()
|
|
198
|
+
rhs = f"{rhs_a},{rhs_b}"
|
|
199
|
+
aliases.append("FlowPairSeparator-→,")
|
|
200
|
+
else:
|
|
201
|
+
raise ValueError(f"MALFORMED_FLOW_SEPARATOR: cannot normalize {step!r}.")
|
|
202
|
+
|
|
203
|
+
lhs = lhs.strip()
|
|
204
|
+
rhs = rhs.strip()
|
|
205
|
+
if not re.fullmatch(atom_list, lhs) or not re.fullmatch(atom_list, rhs):
|
|
206
|
+
raise ValueError(f"MALFORMED_FLOW_LOCUS: cannot parse {step!r}.")
|
|
207
|
+
steps.append(f"{lhs}={rhs}")
|
|
208
|
+
|
|
209
|
+
if not steps:
|
|
210
|
+
raise ValueError("MALFORMED_FLOW_SEPARATOR: electron flow is empty.")
|
|
211
|
+
return ";".join(steps), tuple(dict.fromkeys(aliases))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _mapped_atoms_complete(reaction: str) -> bool:
|
|
215
|
+
for side in reaction.split(">>"):
|
|
216
|
+
mol = Chem.MolFromSmiles(side, sanitize=False)
|
|
217
|
+
if mol is None:
|
|
218
|
+
return False
|
|
219
|
+
if any(
|
|
220
|
+
atom.GetAtomicNum() > 0 and atom.GetAtomMapNum() == 0
|
|
221
|
+
for atom in mol.GetAtoms()
|
|
222
|
+
):
|
|
223
|
+
return False
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _side_atom_map_identities(
|
|
228
|
+
smiles: str, *, require_complete: bool
|
|
229
|
+
) -> dict[int, tuple[int, int]]:
|
|
230
|
+
"""Return positive map identities while rejecting unsafe map topology."""
|
|
231
|
+
parser = Chem.SmilesParserParams()
|
|
232
|
+
parser.removeHs = False
|
|
233
|
+
mol = Chem.MolFromSmiles(smiles, parser)
|
|
234
|
+
if mol is None:
|
|
235
|
+
raise ValueError(f"Cannot parse reaction side {smiles!r}.")
|
|
236
|
+
|
|
237
|
+
atoms = [atom for atom in mol.GetAtoms() if atom.GetAtomicNum() > 0]
|
|
238
|
+
atom_maps = [atom.GetAtomMapNum() for atom in atoms]
|
|
239
|
+
if require_complete and any(atom_map <= 0 for atom_map in atom_maps):
|
|
240
|
+
raise ValueError("Completed AAM contains an unmapped atom.")
|
|
241
|
+
positive_maps = [atom_map for atom_map in atom_maps if atom_map > 0]
|
|
242
|
+
if len(positive_maps) != len(set(positive_maps)):
|
|
243
|
+
raise ValueError("A reaction side contains duplicate positive atom maps.")
|
|
244
|
+
return {
|
|
245
|
+
atom.GetAtomMapNum(): (atom.GetAtomicNum(), atom.GetIsotope())
|
|
246
|
+
for atom in atoms
|
|
247
|
+
if atom.GetAtomMapNum() > 0
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _validate_completed_radical_aam(source: str, candidate: str) -> None:
|
|
252
|
+
"""Validate completeness, map balance, identity, anchors, and constitution."""
|
|
253
|
+
if source.count(">>") != 1 or candidate.count(">>") != 1:
|
|
254
|
+
raise ValueError("AAM validation requires one reaction '>>'.")
|
|
255
|
+
if not ITSExpand.endpoint_constitutions_match(source, candidate):
|
|
256
|
+
raise ValueError("Completed AAM changed an endpoint constitution.")
|
|
257
|
+
|
|
258
|
+
source_sides = [
|
|
259
|
+
_side_atom_map_identities(side, require_complete=False)
|
|
260
|
+
for side in source.split(">>")
|
|
261
|
+
]
|
|
262
|
+
candidate_sides = [
|
|
263
|
+
_side_atom_map_identities(side, require_complete=True)
|
|
264
|
+
for side in candidate.split(">>")
|
|
265
|
+
]
|
|
266
|
+
if set(candidate_sides[0]) != set(candidate_sides[1]):
|
|
267
|
+
raise ValueError("Completed AAM has different reactant and product map sets.")
|
|
268
|
+
for atom_map, identity in candidate_sides[0].items():
|
|
269
|
+
if candidate_sides[1][atom_map] != identity:
|
|
270
|
+
raise ValueError(
|
|
271
|
+
f"Completed AAM map {atom_map} changes atom identity across endpoints."
|
|
272
|
+
)
|
|
273
|
+
for atom_map in set(source_sides[0]) | set(source_sides[1]):
|
|
274
|
+
for source_side, candidate_side in zip(source_sides, candidate_sides):
|
|
275
|
+
if (
|
|
276
|
+
atom_map in source_side
|
|
277
|
+
and candidate_side.get(atom_map) != source_side[atom_map]
|
|
278
|
+
):
|
|
279
|
+
raise ValueError(
|
|
280
|
+
f"Completed AAM moved or removed source anchor map {atom_map}."
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def complete_radical_aam(reaction: str) -> RadicalAAMCompletionResult:
|
|
285
|
+
"""Complete and validate a radical AAM without consulting electron arrows.
|
|
286
|
+
|
|
287
|
+
Existing positive map numbers are hard anchors. Partial mappings are expanded
|
|
288
|
+
from the ITS with endpoint-constitution, radical-state, and explicit-H guards.
|
|
289
|
+
The returned result is usable only after all validation gates pass.
|
|
290
|
+
"""
|
|
291
|
+
source_was_complete = _mapped_atoms_complete(reaction)
|
|
292
|
+
try:
|
|
293
|
+
if source_was_complete:
|
|
294
|
+
candidate = reaction
|
|
295
|
+
expansion = None
|
|
296
|
+
status = "ALREADY_COMPLETE"
|
|
297
|
+
method = "source"
|
|
298
|
+
else:
|
|
299
|
+
expansion = ITSExpand.expand_aam_with_its_report(
|
|
300
|
+
reaction,
|
|
301
|
+
preserve_older_map=True,
|
|
302
|
+
fallback_to_other_side=True,
|
|
303
|
+
require_constitution_preservation=True,
|
|
304
|
+
fold_unmapped_explicit_hydrogens=True,
|
|
305
|
+
ignore_stereochemistry=True,
|
|
306
|
+
explicit_hydrogen=True,
|
|
307
|
+
preserve_radical_state=True,
|
|
308
|
+
)
|
|
309
|
+
candidate = expansion.rsmi
|
|
310
|
+
status = "COMPLETED"
|
|
311
|
+
method = "its"
|
|
312
|
+
|
|
313
|
+
_validate_completed_radical_aam(reaction, candidate)
|
|
314
|
+
return RadicalAAMCompletionResult(
|
|
315
|
+
reaction,
|
|
316
|
+
candidate,
|
|
317
|
+
status,
|
|
318
|
+
method,
|
|
319
|
+
source_was_complete,
|
|
320
|
+
all_atoms_mapped=True,
|
|
321
|
+
unique_atom_maps=True,
|
|
322
|
+
balanced_atom_maps=True,
|
|
323
|
+
source_anchors_preserved=True,
|
|
324
|
+
constitution_preserved=True,
|
|
325
|
+
expansion_side=expansion.selected_side if expansion else None,
|
|
326
|
+
fallback_used=expansion.fallback_used if expansion else False,
|
|
327
|
+
unmapped_explicit_hydrogens_folded=(
|
|
328
|
+
expansion.unmapped_explicit_hydrogens_folded if expansion else False
|
|
329
|
+
),
|
|
330
|
+
folded_unmapped_explicit_hydrogen_count=(
|
|
331
|
+
expansion.folded_unmapped_explicit_hydrogen_count if expansion else 0
|
|
332
|
+
),
|
|
333
|
+
explicit_hydrogen_serialization=(
|
|
334
|
+
expansion.explicit_hydrogen_serialization if expansion else False
|
|
335
|
+
),
|
|
336
|
+
stereochemistry_ignored_for_expansion=(
|
|
337
|
+
expansion.stereochemistry_ignored_for_expansion if expansion else False
|
|
338
|
+
),
|
|
339
|
+
radical_state_preserved=(
|
|
340
|
+
expansion.radical_state_preserved if expansion else True
|
|
341
|
+
),
|
|
342
|
+
)
|
|
343
|
+
except Exception as exc:
|
|
344
|
+
return RadicalAAMCompletionResult(
|
|
345
|
+
reaction,
|
|
346
|
+
None,
|
|
347
|
+
"FAILED",
|
|
348
|
+
None,
|
|
349
|
+
source_was_complete,
|
|
350
|
+
failure_reason=str(exc),
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _positive_source_atom_maps(reaction: str) -> set[int]:
|
|
355
|
+
"""Collect supplied positive maps without treating completed maps as evidence."""
|
|
356
|
+
atom_maps: set[int] = set()
|
|
357
|
+
parser = Chem.SmilesParserParams()
|
|
358
|
+
parser.removeHs = False
|
|
359
|
+
for side in reaction.split(">>"):
|
|
360
|
+
mol = Chem.MolFromSmiles(side, parser)
|
|
361
|
+
if mol is None:
|
|
362
|
+
continue
|
|
363
|
+
atom_maps.update(
|
|
364
|
+
atom.GetAtomMapNum() for atom in mol.GetAtoms() if atom.GetAtomMapNum() > 0
|
|
365
|
+
)
|
|
366
|
+
return atom_maps
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _reactant_symmetry_map_pairs(
|
|
370
|
+
mapped_reaction: str, *, allowed_maps: set[int]
|
|
371
|
+
) -> tuple[tuple[int, int], ...]:
|
|
372
|
+
"""Find supplied map pairs in the same RDKit reactant symmetry class."""
|
|
373
|
+
reactants, _products = mapped_reaction.split(">>")
|
|
374
|
+
parser = Chem.SmilesParserParams()
|
|
375
|
+
parser.removeHs = False
|
|
376
|
+
mol = Chem.MolFromSmiles(reactants, parser)
|
|
377
|
+
if mol is None:
|
|
378
|
+
return ()
|
|
379
|
+
|
|
380
|
+
source_maps = [atom.GetAtomMapNum() for atom in mol.GetAtoms()]
|
|
381
|
+
map_free = Chem.Mol(mol)
|
|
382
|
+
for atom in map_free.GetAtoms():
|
|
383
|
+
atom.SetAtomMapNum(0)
|
|
384
|
+
ranks = Chem.CanonicalRankAtoms(
|
|
385
|
+
map_free,
|
|
386
|
+
breakTies=False,
|
|
387
|
+
includeChirality=True,
|
|
388
|
+
includeIsotopes=True,
|
|
389
|
+
)
|
|
390
|
+
classes: dict[tuple[int, int, int], list[int]] = defaultdict(list)
|
|
391
|
+
for atom, atom_map, rank in zip(mol.GetAtoms(), source_maps, ranks):
|
|
392
|
+
if atom_map in allowed_maps:
|
|
393
|
+
classes[(rank, atom.GetAtomicNum(), atom.GetIsotope())].append(atom_map)
|
|
394
|
+
return tuple(
|
|
395
|
+
pair
|
|
396
|
+
for symmetry_class in classes.values()
|
|
397
|
+
for pair in combinations(symmetry_class, 2)
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _swap_reaction_atom_maps(mapped_reaction: str, atom_maps: tuple[int, int]) -> str:
|
|
402
|
+
"""Swap two map labels globally without changing endpoint constitution."""
|
|
403
|
+
left_map, right_map = atom_maps
|
|
404
|
+
parser = Chem.SmilesParserParams()
|
|
405
|
+
parser.removeHs = False
|
|
406
|
+
swapped_sides: list[str] = []
|
|
407
|
+
for side in mapped_reaction.split(">>"):
|
|
408
|
+
mol = Chem.MolFromSmiles(side, parser)
|
|
409
|
+
if mol is None:
|
|
410
|
+
raise ValueError(f"Cannot parse reaction side {side!r} for map swap.")
|
|
411
|
+
for atom in mol.GetAtoms():
|
|
412
|
+
if atom.GetAtomMapNum() == left_map:
|
|
413
|
+
atom.SetAtomMapNum(right_map)
|
|
414
|
+
elif atom.GetAtomMapNum() == right_map:
|
|
415
|
+
atom.SetAtomMapNum(left_map)
|
|
416
|
+
swapped_sides.append(Chem.MolToSmiles(mol, canonical=True, isomericSmiles=True))
|
|
417
|
+
return ">>".join(swapped_sides)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _normalization_failure_code(message: str) -> str:
|
|
421
|
+
"""Classify fail-closed adapter errors without claiming chemical repair."""
|
|
422
|
+
if "MALFORMED_FLOW_" in message:
|
|
423
|
+
return "MALFORMED_ELECTRON_FLOW"
|
|
424
|
+
if "missing_arrow_maps" in message:
|
|
425
|
+
return "FLOW_ATOM_MAP_MISSING"
|
|
426
|
+
if "ITS graph has no edge" in message:
|
|
427
|
+
return "FLOW_BOND_ABSENT_FROM_ITS"
|
|
428
|
+
if "Cannot align a reconstructed endpoint" in message:
|
|
429
|
+
return "AAM_RADICAL_STATE_UNALIGNABLE"
|
|
430
|
+
if "changed an endpoint constitution" in message:
|
|
431
|
+
return "AAM_ENDPOINT_CONSTITUTION_CHANGED"
|
|
432
|
+
if "Unsupported electron locus" in message:
|
|
433
|
+
return "FLOW_LOCUS_UNTYPABLE"
|
|
434
|
+
if "ATOM_TRANSFER_STATE_MISMATCH" in message:
|
|
435
|
+
return "FLOW_ATOM_TRANSFER_STATE_MISMATCH"
|
|
436
|
+
return "RADICAL_DATA_NORMALIZATION_FAILED"
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _fishhook_move(
|
|
440
|
+
typed_row: Sequence[Any],
|
|
441
|
+
*,
|
|
442
|
+
group_id: str,
|
|
443
|
+
coupling_id: str | None,
|
|
444
|
+
event_id: str,
|
|
445
|
+
) -> ElectronMove:
|
|
446
|
+
action, source_maps, target_maps = typed_row[:3]
|
|
447
|
+
source_name, target_name = str(action).split("-/", 1)
|
|
448
|
+
target_name = target_name.removesuffix("+")
|
|
449
|
+
|
|
450
|
+
# In typed polar EPD a one-atom endpoint is called LP. In this reviewed
|
|
451
|
+
# radical dataset the same shape denotes the SOMO/radical endpoint; bond
|
|
452
|
+
# endpoints retain the σ/π type inferred from the ITS bond-order change.
|
|
453
|
+
if len(source_maps) == 1 and len(target_maps) == 1:
|
|
454
|
+
# State-aware atom-to-atom typing is authoritative when both compact
|
|
455
|
+
# endpoints resolve to explicit nonbonding resource transitions.
|
|
456
|
+
source_kind = source_name
|
|
457
|
+
target_kind = target_name
|
|
458
|
+
else:
|
|
459
|
+
source_kind = "∙" if len(source_maps) == 1 else source_name
|
|
460
|
+
target_kind = "∙" if len(target_maps) == 1 else target_name
|
|
461
|
+
return ElectronMove(
|
|
462
|
+
source=ElectronLocus(source_kind, tuple(source_maps)),
|
|
463
|
+
target=ElectronLocus(target_kind, tuple(target_maps)),
|
|
464
|
+
electron_count=1,
|
|
465
|
+
arrow_type="fishhook",
|
|
466
|
+
group_id=group_id,
|
|
467
|
+
coupling_id=coupling_id,
|
|
468
|
+
event_id=event_id,
|
|
469
|
+
metadata={"inferred_from_typed_epd": str(action)},
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def normalize_radical_row( # noqa: C901
|
|
474
|
+
row: Sequence[str],
|
|
475
|
+
*,
|
|
476
|
+
row_number: int = 0,
|
|
477
|
+
enforce_source_macro: bool = True,
|
|
478
|
+
) -> RadicalDatasetRecord:
|
|
479
|
+
"""Normalize one four-column radical CSV row or return a quarantine report.
|
|
480
|
+
|
|
481
|
+
``enforce_source_macro=False`` validates and reconstructs the typed
|
|
482
|
+
fishhook group independently of the dataset's reaction-class label. The
|
|
483
|
+
state-resolved single-fishhook transition retains its operational marker
|
|
484
|
+
because replay needs its paired lone-pair/radical commit semantics.
|
|
485
|
+
"""
|
|
486
|
+
source_row = tuple(str(value) for value in row)
|
|
487
|
+
source_class = source_row[3].strip().lower() if len(source_row) >= 4 else ""
|
|
488
|
+
macro = RADICAL_CLASS_TO_MACRO.get(source_class)
|
|
489
|
+
issues: list[VerificationIssue] = []
|
|
490
|
+
|
|
491
|
+
if len(source_row) != 4:
|
|
492
|
+
issues.append(
|
|
493
|
+
VerificationIssue("INVALID_DATASET_ROW", "Expected four CSV columns.")
|
|
494
|
+
)
|
|
495
|
+
if enforce_source_macro and macro is None:
|
|
496
|
+
issues.append(
|
|
497
|
+
VerificationIssue(
|
|
498
|
+
"UNREVIEWED_RADICAL_CLASS",
|
|
499
|
+
f"Radical class {source_class!r} has no approved macro mapping.",
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
if issues:
|
|
503
|
+
return RadicalDatasetRecord(
|
|
504
|
+
source_row,
|
|
505
|
+
RadicalNormalizationReport(
|
|
506
|
+
row_number, "QUARANTINED", source_class, macro, issues=tuple(issues)
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
flow_aliases: tuple[str, ...] = ()
|
|
511
|
+
try:
|
|
512
|
+
reaction, flow = _split_dataset_text(source_row[0])
|
|
513
|
+
arrow_code, flow_aliases = _normalize_dataset_flow(flow)
|
|
514
|
+
was_complete = _mapped_atoms_complete(reaction)
|
|
515
|
+
# Validate flow references against supplied maps before completion.
|
|
516
|
+
# Otherwise a newly assigned map could accidentally legitimize a
|
|
517
|
+
# source arrow that originally named a nonexistent atom.
|
|
518
|
+
validate_arrow_maps(
|
|
519
|
+
reaction,
|
|
520
|
+
arrow_code,
|
|
521
|
+
raise_on_arrow_duplicates=True,
|
|
522
|
+
raise_on_missing_arrow_maps=True,
|
|
523
|
+
)
|
|
524
|
+
aam_completion = complete_radical_aam(reaction)
|
|
525
|
+
if not aam_completion.usable or aam_completion.mapped_reaction is None:
|
|
526
|
+
raise ValueError(
|
|
527
|
+
"Arrow-independent AAM completion failed before flow typing: "
|
|
528
|
+
f"{aam_completion.failure_reason}"
|
|
529
|
+
)
|
|
530
|
+
equivalent_map_swap: tuple[int, int] | None = None
|
|
531
|
+
arrow_repair_assessment: str | None = None
|
|
532
|
+
try:
|
|
533
|
+
its, expanded, _cleaned, _diagnostics = build_its_from_rsmi(
|
|
534
|
+
aam_completion.mapped_reaction,
|
|
535
|
+
arrow_code,
|
|
536
|
+
expand_aam=False,
|
|
537
|
+
remove_non_arrow_maps=False,
|
|
538
|
+
)
|
|
539
|
+
typed = typed_convert_arrow_code(arrow_code, its, electron_count=1)
|
|
540
|
+
except Exception as arrow_exc:
|
|
541
|
+
if "ITS graph has no edge" not in str(arrow_exc):
|
|
542
|
+
raise
|
|
543
|
+
repair_candidates = []
|
|
544
|
+
allowed_maps = _positive_source_atom_maps(reaction)
|
|
545
|
+
for atom_map_pair in _reactant_symmetry_map_pairs(
|
|
546
|
+
aam_completion.mapped_reaction,
|
|
547
|
+
allowed_maps=allowed_maps,
|
|
548
|
+
):
|
|
549
|
+
swapped = _swap_reaction_atom_maps(
|
|
550
|
+
aam_completion.mapped_reaction, atom_map_pair
|
|
551
|
+
)
|
|
552
|
+
if not ITSExpand.endpoint_constitutions_match(
|
|
553
|
+
aam_completion.mapped_reaction, swapped
|
|
554
|
+
):
|
|
555
|
+
continue
|
|
556
|
+
try:
|
|
557
|
+
candidate_its, candidate_rsmi, candidate_cleaned, candidate_diag = (
|
|
558
|
+
build_its_from_rsmi(
|
|
559
|
+
swapped,
|
|
560
|
+
arrow_code,
|
|
561
|
+
expand_aam=False,
|
|
562
|
+
remove_non_arrow_maps=False,
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
candidate_typed = typed_convert_arrow_code(
|
|
566
|
+
arrow_code, candidate_its, electron_count=1
|
|
567
|
+
)
|
|
568
|
+
except Exception:
|
|
569
|
+
continue
|
|
570
|
+
repair_candidates.append(
|
|
571
|
+
(
|
|
572
|
+
atom_map_pair,
|
|
573
|
+
candidate_its,
|
|
574
|
+
candidate_rsmi,
|
|
575
|
+
candidate_cleaned,
|
|
576
|
+
candidate_diag,
|
|
577
|
+
candidate_typed,
|
|
578
|
+
)
|
|
579
|
+
)
|
|
580
|
+
if len(repair_candidates) != 1:
|
|
581
|
+
raise arrow_exc
|
|
582
|
+
(
|
|
583
|
+
equivalent_map_swap,
|
|
584
|
+
its,
|
|
585
|
+
expanded,
|
|
586
|
+
_cleaned,
|
|
587
|
+
_diagnostics,
|
|
588
|
+
typed,
|
|
589
|
+
) = repair_candidates[0]
|
|
590
|
+
arrow_repair_assessment = "REACTANT_SYMMETRY_UNIQUE"
|
|
591
|
+
expansion_side = aam_completion.expansion_side
|
|
592
|
+
fallback_used = aam_completion.fallback_used
|
|
593
|
+
constitution_checked = aam_completion.constitution_preserved
|
|
594
|
+
hydrogens_folded = aam_completion.unmapped_explicit_hydrogens_folded
|
|
595
|
+
folded_hydrogen_count = aam_completion.folded_unmapped_explicit_hydrogen_count
|
|
596
|
+
stereochemistry_ignored = aam_completion.stereochemistry_ignored_for_expansion
|
|
597
|
+
radical_state_preserved = aam_completion.radical_state_preserved
|
|
598
|
+
is_state_relocation = (
|
|
599
|
+
len(typed) == 1
|
|
600
|
+
and len(typed[0][1]) == 1
|
|
601
|
+
and len(typed[0][2]) == 1
|
|
602
|
+
and str(typed[0][0]).lower().startswith("lp-/lp")
|
|
603
|
+
)
|
|
604
|
+
operational_macro = (
|
|
605
|
+
"LONE_PAIR_RADICAL_RELOCATION"
|
|
606
|
+
if is_state_relocation
|
|
607
|
+
else macro if enforce_source_macro else None
|
|
608
|
+
)
|
|
609
|
+
aliases = (
|
|
610
|
+
"LP→∙",
|
|
611
|
+
"Sigma→σ",
|
|
612
|
+
"Pi→π",
|
|
613
|
+
*flow_aliases,
|
|
614
|
+
*(
|
|
615
|
+
("CompactAtomArrow→state-resolved:lp→lp",)
|
|
616
|
+
if is_state_relocation
|
|
617
|
+
else ()
|
|
618
|
+
),
|
|
619
|
+
*(("Stereo→constitution-only",) if stereochemistry_ignored else ()),
|
|
620
|
+
*(
|
|
621
|
+
(
|
|
622
|
+
"EquivalentReactantMapSwap:"
|
|
623
|
+
f"{equivalent_map_swap[0]}↔{equivalent_map_swap[1]}",
|
|
624
|
+
)
|
|
625
|
+
if equivalent_map_swap
|
|
626
|
+
else ()
|
|
627
|
+
),
|
|
628
|
+
)
|
|
629
|
+
group_id = f"g{row_number or 1}"
|
|
630
|
+
coupling_id = (
|
|
631
|
+
None
|
|
632
|
+
if operational_macro == "LONE_PAIR_RADICAL_RELOCATION"
|
|
633
|
+
else f"{source_class}-{row_number or 1}"
|
|
634
|
+
)
|
|
635
|
+
moves = tuple(
|
|
636
|
+
_fishhook_move(
|
|
637
|
+
typed_row,
|
|
638
|
+
group_id=group_id,
|
|
639
|
+
coupling_id=coupling_id,
|
|
640
|
+
event_id=f"e{index}",
|
|
641
|
+
)
|
|
642
|
+
for index, typed_row in enumerate(typed, start=1)
|
|
643
|
+
)
|
|
644
|
+
group = ElectronMoveGroup(
|
|
645
|
+
group_id,
|
|
646
|
+
moves,
|
|
647
|
+
macro=operational_macro,
|
|
648
|
+
metadata={"source_class": source_class},
|
|
649
|
+
)
|
|
650
|
+
grammar_issues = group.issues()
|
|
651
|
+
if grammar_issues:
|
|
652
|
+
return RadicalDatasetRecord(
|
|
653
|
+
source_row,
|
|
654
|
+
RadicalNormalizationReport(
|
|
655
|
+
row_number,
|
|
656
|
+
"QUARANTINED",
|
|
657
|
+
source_class,
|
|
658
|
+
macro,
|
|
659
|
+
aam_expanded=not was_complete,
|
|
660
|
+
aam_expansion_side=expansion_side,
|
|
661
|
+
aam_fallback_used=fallback_used,
|
|
662
|
+
constitution_checked=constitution_checked,
|
|
663
|
+
unmapped_explicit_hydrogens_folded=hydrogens_folded,
|
|
664
|
+
folded_unmapped_explicit_hydrogen_count=folded_hydrogen_count,
|
|
665
|
+
stereochemistry_ignored_for_expansion=stereochemistry_ignored,
|
|
666
|
+
radical_state_preserved=radical_state_preserved,
|
|
667
|
+
equivalent_map_swap=equivalent_map_swap,
|
|
668
|
+
arrow_repair_assessment=arrow_repair_assessment,
|
|
669
|
+
aliases_normalized=aliases,
|
|
670
|
+
issues=grammar_issues,
|
|
671
|
+
),
|
|
672
|
+
)
|
|
673
|
+
report = RadicalNormalizationReport(
|
|
674
|
+
row_number,
|
|
675
|
+
"NORMALIZED",
|
|
676
|
+
source_class,
|
|
677
|
+
macro,
|
|
678
|
+
aam_expanded=not was_complete,
|
|
679
|
+
aam_expansion_side=expansion_side,
|
|
680
|
+
aam_fallback_used=fallback_used,
|
|
681
|
+
constitution_checked=constitution_checked,
|
|
682
|
+
unmapped_explicit_hydrogens_folded=hydrogens_folded,
|
|
683
|
+
folded_unmapped_explicit_hydrogen_count=folded_hydrogen_count,
|
|
684
|
+
stereochemistry_ignored_for_expansion=stereochemistry_ignored,
|
|
685
|
+
radical_state_preserved=radical_state_preserved,
|
|
686
|
+
equivalent_map_swap=equivalent_map_swap,
|
|
687
|
+
arrow_repair_assessment=arrow_repair_assessment,
|
|
688
|
+
aliases_normalized=aliases,
|
|
689
|
+
)
|
|
690
|
+
mechanism = MechanismRecord(
|
|
691
|
+
expanded,
|
|
692
|
+
(MechanisticStep("s1", (group,)),),
|
|
693
|
+
provenance={
|
|
694
|
+
"format": "synkit_radical_csv",
|
|
695
|
+
"source_row": row_number,
|
|
696
|
+
"temperature": source_row[1],
|
|
697
|
+
"stage": source_row[2],
|
|
698
|
+
"normalization": report.to_dict(),
|
|
699
|
+
},
|
|
700
|
+
)
|
|
701
|
+
return RadicalDatasetRecord(source_row, report, mechanism)
|
|
702
|
+
except Exception as exc:
|
|
703
|
+
message = str(exc)
|
|
704
|
+
issue = VerificationIssue(
|
|
705
|
+
_normalization_failure_code(message),
|
|
706
|
+
message,
|
|
707
|
+
observed=source_row[0] if source_row else None,
|
|
708
|
+
)
|
|
709
|
+
return RadicalDatasetRecord(
|
|
710
|
+
source_row,
|
|
711
|
+
RadicalNormalizationReport(
|
|
712
|
+
row_number,
|
|
713
|
+
"QUARANTINED",
|
|
714
|
+
source_class,
|
|
715
|
+
macro,
|
|
716
|
+
aliases_normalized=flow_aliases,
|
|
717
|
+
issues=(issue,),
|
|
718
|
+
),
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def iter_radical_csv(path: str | Path) -> Iterable[RadicalDatasetRecord]:
|
|
723
|
+
with Path(path).open(newline="", encoding="utf-8-sig") as handle:
|
|
724
|
+
for row_number, row in enumerate(csv.reader(handle), start=1):
|
|
725
|
+
if row:
|
|
726
|
+
yield normalize_radical_row(row, row_number=row_number)
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def radical_dataset_summary(records: Iterable[RadicalDatasetRecord]) -> dict[str, Any]:
|
|
730
|
+
records = list(records)
|
|
731
|
+
return {
|
|
732
|
+
"total": len(records),
|
|
733
|
+
"accepted": sum(record.accepted for record in records),
|
|
734
|
+
"quarantined": sum(not record.accepted for record in records),
|
|
735
|
+
"classes": dict(Counter(record.report.source_class for record in records)),
|
|
736
|
+
"expanded_aam": sum(record.report.aam_expanded for record in records),
|
|
737
|
+
"issue_codes": dict(
|
|
738
|
+
Counter(issue.code for record in records for issue in record.report.issues)
|
|
739
|
+
),
|
|
740
|
+
}
|