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,223 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import networkx as nx
|
|
3
|
+
from typing import Tuple, List, Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GMLToGraph:
|
|
7
|
+
"""Parses a GML-like reaction rule into three NetworkX graphs: reactant (left),
|
|
8
|
+
conserved context (context), and product (right). Preserves atom-map indices
|
|
9
|
+
and original SMARTS labels, and attaches placeholder constraints (both
|
|
10
|
+
table-style and multi-block Rest-style) to node and edge attributes.
|
|
11
|
+
|
|
12
|
+
:param gml_text: The GML-like reaction rule text, containing 'left', 'context', 'right',
|
|
13
|
+
and 'constrainLabelAny' sections.
|
|
14
|
+
:type gml_text: str
|
|
15
|
+
|
|
16
|
+
:ivar graphs: A mapping of section names ('left', 'context', 'right') to the
|
|
17
|
+
corresponding parsed graphs.
|
|
18
|
+
:vartype graphs: Dict[str, nx.Graph]
|
|
19
|
+
:ivar placeholder_constraints: A mapping from placeholder labels (e.g. '_X') to their allowed values,
|
|
20
|
+
extracted from the 'constrainLabelAny' block.
|
|
21
|
+
:vartype placeholder_constraints: Dict[str, List[str]]
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, gml_text: str):
|
|
25
|
+
"""Initialize the parser with the full GML text.
|
|
26
|
+
|
|
27
|
+
:param gml_text: GML-like rule text to be parsed.
|
|
28
|
+
:type gml_text: str
|
|
29
|
+
|
|
30
|
+
:raises ValueError: If the provided gml_text is empty.
|
|
31
|
+
"""
|
|
32
|
+
if not gml_text:
|
|
33
|
+
raise ValueError("gml_text must be a non-empty string")
|
|
34
|
+
self.gml_text = gml_text
|
|
35
|
+
self.graphs: Dict[str, nx.Graph] = {
|
|
36
|
+
sec: nx.Graph() for sec in ("left", "context", "right")
|
|
37
|
+
}
|
|
38
|
+
self.placeholder_constraints: Dict[str, List[str]] = {}
|
|
39
|
+
|
|
40
|
+
def _parse_element(self, line: str, sec: str) -> None:
|
|
41
|
+
"""Parse a single GML line describing a node or an edge and insert it
|
|
42
|
+
into the specified graph section.
|
|
43
|
+
|
|
44
|
+
:param line: A line starting with 'node' or 'edge', e.g., 'node [ id 1 label "C" ]'.
|
|
45
|
+
:type line: str
|
|
46
|
+
:param sec: The target section in 'left', 'context', or 'right'.
|
|
47
|
+
:type sec: str
|
|
48
|
+
|
|
49
|
+
:raises ValueError: If `sec` is not one of 'left', 'context', or 'right'.
|
|
50
|
+
"""
|
|
51
|
+
if sec not in self.graphs:
|
|
52
|
+
raise ValueError(f"Unknown section: {sec}")
|
|
53
|
+
tokens = line.split()
|
|
54
|
+
order_map = {"-": 1, ":": 1.5, "=": 2, "#": 3}
|
|
55
|
+
if tokens[0] == "node":
|
|
56
|
+
# Extract node attributes
|
|
57
|
+
nid = int(tokens[tokens.index("id") + 1])
|
|
58
|
+
raw_label = tokens[tokens.index("label") + 1].strip('"')
|
|
59
|
+
m = re.fullmatch(r"([A-Za-z*_]+?)(\d+)?([+-])?", raw_label)
|
|
60
|
+
if m:
|
|
61
|
+
element = m.group(1)
|
|
62
|
+
charge = int(m.group(2)) if m.group(2) else 1 if m.group(3) else 0
|
|
63
|
+
if m.group(3) == "-":
|
|
64
|
+
charge = -charge
|
|
65
|
+
else:
|
|
66
|
+
element = raw_label
|
|
67
|
+
charge = 0
|
|
68
|
+
attrs = {
|
|
69
|
+
"element": element,
|
|
70
|
+
"charge": charge,
|
|
71
|
+
"atom_map": nid,
|
|
72
|
+
"hcount": 0,
|
|
73
|
+
"label": raw_label,
|
|
74
|
+
}
|
|
75
|
+
self.graphs[sec].add_node(nid, **attrs)
|
|
76
|
+
elif tokens[0] == "edge":
|
|
77
|
+
# Extract edge attributes
|
|
78
|
+
src = int(tokens[tokens.index("source") + 1])
|
|
79
|
+
tgt = int(tokens[tokens.index("target") + 1])
|
|
80
|
+
lbl = tokens[tokens.index("label") + 1].strip('"')
|
|
81
|
+
order = order_map.get(lbl, 0)
|
|
82
|
+
self.graphs[sec].add_edge(src, tgt, order=order, label=lbl)
|
|
83
|
+
|
|
84
|
+
def _synchronize(self) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Ensure that every node and edge in the 'context' graph also appears in
|
|
87
|
+
both 'left' and 'right' graphs, carrying over node and edge attributes.
|
|
88
|
+
"""
|
|
89
|
+
ctx = self.graphs["context"]
|
|
90
|
+
# Nodes
|
|
91
|
+
for n, data in ctx.nodes(data=True):
|
|
92
|
+
for sec in ("left", "right"):
|
|
93
|
+
if n not in self.graphs[sec]:
|
|
94
|
+
self.graphs[sec].add_node(n, **data)
|
|
95
|
+
else:
|
|
96
|
+
self.graphs[sec].nodes[n].update(data)
|
|
97
|
+
# Edges
|
|
98
|
+
for u, v, data in ctx.edges(data=True):
|
|
99
|
+
for sec in ("left", "right"):
|
|
100
|
+
if not self.graphs[sec].has_edge(u, v):
|
|
101
|
+
self.graphs[sec].add_edge(u, v, **data)
|
|
102
|
+
|
|
103
|
+
def _parse_constraints(self, lines: List[str], idx: int) -> int:
|
|
104
|
+
"""Parse placeholder constraints from the 'constrainLabelAny' block,
|
|
105
|
+
supporting both table and multi-block Rest styles.
|
|
106
|
+
|
|
107
|
+
:param lines: All lines of the GML text.
|
|
108
|
+
:type lines: List[str]
|
|
109
|
+
:param idx: The index of the first line inside the '[' of the block.
|
|
110
|
+
:type idx: int
|
|
111
|
+
|
|
112
|
+
:return: The index of the closing ']' line of the block.
|
|
113
|
+
:rtype: int
|
|
114
|
+
"""
|
|
115
|
+
while idx < len(lines):
|
|
116
|
+
line = lines[idx].strip()
|
|
117
|
+
if line.startswith("]"):
|
|
118
|
+
return idx
|
|
119
|
+
m = re.match(r'label\s+"[^\(]+\(([^)]+)\)"', line)
|
|
120
|
+
if m:
|
|
121
|
+
placeholders = [p.strip() for p in m.group(1).split(",")]
|
|
122
|
+
# Table style if multiple placeholders
|
|
123
|
+
if len(placeholders) > 1:
|
|
124
|
+
table: List[Tuple[str, ...]] = []
|
|
125
|
+
idx += 1
|
|
126
|
+
# Find rows
|
|
127
|
+
while idx < len(lines) and "labels [" not in lines[idx]:
|
|
128
|
+
idx += 1
|
|
129
|
+
while idx < len(lines):
|
|
130
|
+
row_line = lines[idx].strip()
|
|
131
|
+
matches = re.findall(r'label\s+"[^\(]+\(([^)]+)\)"', row_line)
|
|
132
|
+
for row in matches:
|
|
133
|
+
table.append(tuple(val.strip() for val in row.split(",")))
|
|
134
|
+
if "]" in row_line:
|
|
135
|
+
break
|
|
136
|
+
idx += 1
|
|
137
|
+
for j, ph in enumerate(placeholders):
|
|
138
|
+
self.placeholder_constraints[ph] = [row[j] for row in table]
|
|
139
|
+
idx += 1
|
|
140
|
+
continue
|
|
141
|
+
# Multi-block Rest style
|
|
142
|
+
ph = placeholders[0]
|
|
143
|
+
self.placeholder_constraints[ph] = []
|
|
144
|
+
idx += 1
|
|
145
|
+
while idx < len(lines) and "labels [" not in lines[idx]:
|
|
146
|
+
idx += 1
|
|
147
|
+
if idx < len(lines):
|
|
148
|
+
while idx < len(lines):
|
|
149
|
+
lbl_line = lines[idx].strip()
|
|
150
|
+
matches = re.findall(r'label\s+"[^\(]+\(([^)]+)\)"', lbl_line)
|
|
151
|
+
self.placeholder_constraints[ph].extend(matches)
|
|
152
|
+
if "]" in lbl_line:
|
|
153
|
+
break
|
|
154
|
+
idx += 1
|
|
155
|
+
idx += 1
|
|
156
|
+
continue
|
|
157
|
+
idx += 1
|
|
158
|
+
return idx
|
|
159
|
+
|
|
160
|
+
def _attach_constraints(self) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Attach the parsed placeholder constraints to nodes (as 'constraint')
|
|
163
|
+
and edges (as 'bond_constraint') across all graphs, and store the
|
|
164
|
+
raw mapping on the context graph's .graph metadata.
|
|
165
|
+
"""
|
|
166
|
+
pc = self.placeholder_constraints
|
|
167
|
+
for graph in self.graphs.values():
|
|
168
|
+
for n, data in graph.nodes(data=True):
|
|
169
|
+
lbl = data.get("label")
|
|
170
|
+
if lbl in pc:
|
|
171
|
+
data["constraint"] = pc[lbl]
|
|
172
|
+
for u, v, data in graph.edges(data=True):
|
|
173
|
+
lbl = data.get("label")
|
|
174
|
+
if lbl in pc:
|
|
175
|
+
data["bond_constraint"] = pc[lbl]
|
|
176
|
+
self.graphs["context"].graph["placeholder_constraints"] = pc
|
|
177
|
+
|
|
178
|
+
def transform(self) -> Tuple[nx.Graph, nx.Graph, nx.Graph]:
|
|
179
|
+
"""Parse the GML text, build the left, right, and context graphs, and return them.
|
|
180
|
+
|
|
181
|
+
:return: A tuple (left_graph, right_graph, context_graph), each with attached constraints.
|
|
182
|
+
:rtype: Tuple[nx.Graph, nx.Graph, nx.Graph]
|
|
183
|
+
"""
|
|
184
|
+
lines = self.gml_text.splitlines()
|
|
185
|
+
section: str = ""
|
|
186
|
+
i = 0
|
|
187
|
+
while i < len(lines):
|
|
188
|
+
line = lines[i].strip()
|
|
189
|
+
if line.startswith("constrainLabelAny"):
|
|
190
|
+
while i < len(lines) and not lines[i].strip().endswith("["):
|
|
191
|
+
i += 1
|
|
192
|
+
i = self._parse_constraints(lines, i + 1)
|
|
193
|
+
elif any(line.startswith(x) for x in ("left", "right", "context")):
|
|
194
|
+
section = line.split("[")[0].strip()
|
|
195
|
+
elif line.startswith(("node", "edge")) and section:
|
|
196
|
+
self._parse_element(line, section)
|
|
197
|
+
i += 1
|
|
198
|
+
self._synchronize()
|
|
199
|
+
self._attach_constraints()
|
|
200
|
+
return (self.graphs["left"], self.graphs["right"], self.graphs["context"])
|
|
201
|
+
|
|
202
|
+
def __repr__(self) -> str:
|
|
203
|
+
"""Return a summary indicating the number of nodes in each graph.
|
|
204
|
+
|
|
205
|
+
:return: A brief representation with node counts.
|
|
206
|
+
:rtype: str
|
|
207
|
+
"""
|
|
208
|
+
return (
|
|
209
|
+
f"<GMLToGraph left={self.graphs['left'].number_of_nodes()} nodes, "
|
|
210
|
+
f"right={self.graphs['right'].number_of_nodes()} nodes, "
|
|
211
|
+
f"context={self.graphs['context'].number_of_nodes()} nodes>"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def help(self) -> str:
|
|
215
|
+
"""Return a usage summary for the GMLToGraph parser.
|
|
216
|
+
|
|
217
|
+
:return: Multi-line help text explaining the API.
|
|
218
|
+
:rtype: str
|
|
219
|
+
"""
|
|
220
|
+
return (
|
|
221
|
+
"GMLToGraph(gml_text) -> (left, right, context) graphs\n"
|
|
222
|
+
"Supports Nuc-style and Rest-style constraint parsing."
|
|
223
|
+
)
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
from typing import Dict, List, Tuple, Any, Set
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GraphToGML:
|
|
6
|
+
"""Convert two NetworkX graphs into a minimal GML reaction rule string,
|
|
7
|
+
using canonical context detection and SynKit-style constraint annotations.
|
|
8
|
+
|
|
9
|
+
This class identifies the conserved context (nodes/edges unchanged between
|
|
10
|
+
reactant and product graphs), extracts minimal changing portions,
|
|
11
|
+
and generates a GML block including left, context, right, and placeholder constraints.
|
|
12
|
+
|
|
13
|
+
:param left: Graph representing the reactant state.
|
|
14
|
+
:type left: nx.Graph
|
|
15
|
+
:param right: Graph representing the product state.
|
|
16
|
+
:type right: nx.Graph
|
|
17
|
+
:param rule_id: Identifier for the reaction rule (default: "1").
|
|
18
|
+
:type rule_id: str
|
|
19
|
+
:raises ValueError: If input graphs have mismatched mapping nodes.
|
|
20
|
+
|
|
21
|
+
.. rubric:: Examples
|
|
22
|
+
|
|
23
|
+
>>> from networkx import Graph
|
|
24
|
+
>>> G1, G2 = Graph(), Graph()
|
|
25
|
+
>>> # populate G1, G2 with atom_map nodes, constraints, etc.
|
|
26
|
+
>>> g2g = GraphToGML(G1, G2, rule_id="rxn1")
|
|
27
|
+
>>> gml = g2g.to_gml()
|
|
28
|
+
>>> print(gml)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, left: nx.Graph, right: nx.Graph, rule_id: str = "1") -> None:
|
|
32
|
+
"""Initialize the GraphToGML converter.
|
|
33
|
+
|
|
34
|
+
:param left: Reactant graph with node and edge attributes.
|
|
35
|
+
:type left: nx.Graph
|
|
36
|
+
:param right: Product graph with node and edge attributes.
|
|
37
|
+
:type right: nx.Graph
|
|
38
|
+
:param rule_id: Unique identifier for the rule output.
|
|
39
|
+
:type rule_id: str
|
|
40
|
+
:return: None
|
|
41
|
+
:rtype: None
|
|
42
|
+
:raises ValueError: If graphs have nodes without atom_map attribute.
|
|
43
|
+
"""
|
|
44
|
+
self.left: nx.Graph = left
|
|
45
|
+
self.right: nx.Graph = right
|
|
46
|
+
self.rule_id: str = rule_id
|
|
47
|
+
|
|
48
|
+
self.context_nodes: Set[int] = set()
|
|
49
|
+
self.context_edges: Set[Tuple[int, int]] = set()
|
|
50
|
+
self.left_nodes: Set[int] = set()
|
|
51
|
+
self.right_nodes: Set[int] = set()
|
|
52
|
+
self.left_edges: List[Tuple[int, int, Dict[str, Any]]] = []
|
|
53
|
+
self.right_edges: List[Tuple[int, int, Dict[str, Any]]] = []
|
|
54
|
+
self.context: nx.Graph = nx.Graph()
|
|
55
|
+
self.constraint_dict: Dict[str, List[str]] = {}
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def same_node(nl: Dict[str, Any], nr: Dict[str, Any]) -> bool:
|
|
59
|
+
"""Compare two node attribute dictionaries, ignoring 'atom_map'.
|
|
60
|
+
|
|
61
|
+
:param nl: Node attribute dict from left graph.
|
|
62
|
+
:type nl: Dict[str, Any]
|
|
63
|
+
:param nr: Node attribute dict from right graph.
|
|
64
|
+
:type nr: Dict[str, Any]
|
|
65
|
+
:return: True if all attributes (except 'atom_map') match.
|
|
66
|
+
:rtype: bool
|
|
67
|
+
"""
|
|
68
|
+
keys = set(nl) | set(nr)
|
|
69
|
+
keys.discard("atom_map")
|
|
70
|
+
return all(nl.get(k) == nr.get(k) for k in keys)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def same_edge(el: Dict[str, Any], er: Dict[str, Any]) -> bool:
|
|
74
|
+
"""Compare two edge attribute dictionaries for equality.
|
|
75
|
+
|
|
76
|
+
:param el: Edge attribute dict from left graph.
|
|
77
|
+
:type el: Dict[str, Any]
|
|
78
|
+
:param er: Edge attribute dict from right graph.
|
|
79
|
+
:type er: Dict[str, Any]
|
|
80
|
+
:return: True if all edge attributes match.
|
|
81
|
+
:rtype: bool
|
|
82
|
+
"""
|
|
83
|
+
keys = set(el) | set(er)
|
|
84
|
+
return all(el.get(k) == er.get(k) for k in keys)
|
|
85
|
+
|
|
86
|
+
def compute(self) -> None:
|
|
87
|
+
"""Compute conserved context and minimal changing nodes/edges,
|
|
88
|
+
then collect placeholder constraints from context graph.
|
|
89
|
+
|
|
90
|
+
:return: None
|
|
91
|
+
:rtype: None
|
|
92
|
+
"""
|
|
93
|
+
# Identify conserved context nodes
|
|
94
|
+
self.context_nodes = {
|
|
95
|
+
n
|
|
96
|
+
for n in set(self.left.nodes) & set(self.right.nodes)
|
|
97
|
+
if self.same_node(self.left.nodes[n], self.right.nodes[n])
|
|
98
|
+
}
|
|
99
|
+
# Identify conserved context edges
|
|
100
|
+
self.context_edges = {
|
|
101
|
+
tuple(sorted((u, v)))
|
|
102
|
+
for u, v in set(self.left.edges) & set(self.right.edges)
|
|
103
|
+
if u in self.context_nodes
|
|
104
|
+
and v in self.context_nodes
|
|
105
|
+
and self.same_edge(self.left.edges[u, v], self.right.edges[u, v])
|
|
106
|
+
}
|
|
107
|
+
# Compute minimal changing edges and nodes
|
|
108
|
+
self.left_edges, left_extra = self.get_changing_edges_and_nodes(
|
|
109
|
+
self.left, self.context_edges, self.context_nodes
|
|
110
|
+
)
|
|
111
|
+
self.right_edges, right_extra = self.get_changing_edges_and_nodes(
|
|
112
|
+
self.right, self.context_edges, self.context_nodes
|
|
113
|
+
)
|
|
114
|
+
self.left_nodes = {
|
|
115
|
+
n for n in self.left.nodes if n not in self.context_nodes
|
|
116
|
+
} | left_extra
|
|
117
|
+
self.right_nodes = {
|
|
118
|
+
n for n in self.right.nodes if n not in self.context_nodes
|
|
119
|
+
} | right_extra
|
|
120
|
+
# Build context subgraph
|
|
121
|
+
self.context = nx.Graph()
|
|
122
|
+
for n in sorted(self.context_nodes):
|
|
123
|
+
self.context.add_node(n, **self.left.nodes[n])
|
|
124
|
+
for u, v in self.context_edges:
|
|
125
|
+
self.context.add_edge(u, v, **self.left.edges[u, v])
|
|
126
|
+
# Gather constraints from context
|
|
127
|
+
self.constraint_dict.clear()
|
|
128
|
+
for _, d in self.context.nodes(data=True):
|
|
129
|
+
if "constraint" in d:
|
|
130
|
+
self.constraint_dict[d["label"]] = d["constraint"]
|
|
131
|
+
for _, _, d in self.context.edges(data=True):
|
|
132
|
+
if "bond_constraint" in d:
|
|
133
|
+
self.constraint_dict[d["label"]] = d["bond_constraint"]
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def get_changing_edges_and_nodes(
|
|
137
|
+
G: nx.Graph, context_edges: Set[Tuple[int, int]], context_nodes: Set[int]
|
|
138
|
+
) -> Tuple[List[Tuple[int, int, Dict[str, Any]]], Set[int]]:
|
|
139
|
+
"""Identify edges and nodes in G that are not in the conserved context.
|
|
140
|
+
|
|
141
|
+
:param G: Input graph.
|
|
142
|
+
:type G: nx.Graph
|
|
143
|
+
:param context_edges: Edges part of context.
|
|
144
|
+
:type context_edges: Set[Tuple[int, int]]
|
|
145
|
+
:param context_nodes: Nodes part of context.
|
|
146
|
+
:type context_nodes: Set[int]
|
|
147
|
+
:return: A tuple of (changed_edges, changed_nodes).
|
|
148
|
+
:rtype: Tuple[List[Tuple[int, int, Dict[str, Any]]], Set[int]]
|
|
149
|
+
"""
|
|
150
|
+
changed_edges: List[Tuple[int, int, Dict[str, Any]]] = []
|
|
151
|
+
changed_nodes: Set[int] = set()
|
|
152
|
+
for u, v in G.edges():
|
|
153
|
+
key = tuple(sorted((u, v)))
|
|
154
|
+
if key not in context_edges:
|
|
155
|
+
changed_edges.append((u, v, G.edges[u, v]))
|
|
156
|
+
if u not in context_nodes:
|
|
157
|
+
changed_nodes.add(u)
|
|
158
|
+
if v not in context_nodes:
|
|
159
|
+
changed_nodes.add(v)
|
|
160
|
+
return changed_edges, changed_nodes
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def graph_section(
|
|
164
|
+
name: str,
|
|
165
|
+
G: nx.Graph,
|
|
166
|
+
nodes: Set[int],
|
|
167
|
+
edges: List[Tuple[int, int, Dict[str, Any]]],
|
|
168
|
+
) -> List[str]:
|
|
169
|
+
"""Render a GML block for a subgraph section.
|
|
170
|
+
|
|
171
|
+
:param name: Section name ('left', 'context', or 'right').
|
|
172
|
+
:type name: str
|
|
173
|
+
:param G: Graph containing nodes/edges.
|
|
174
|
+
:type G: nx.Graph
|
|
175
|
+
:param nodes: Node IDs to include.
|
|
176
|
+
:type nodes: Set[int]
|
|
177
|
+
:param edges: Edges to include (u,v,attr).
|
|
178
|
+
:type edges: List[Tuple[int,int,Dict[str,Any]]]
|
|
179
|
+
:return: Lines of GML representing the section.
|
|
180
|
+
:rtype: List[str]
|
|
181
|
+
"""
|
|
182
|
+
lines: List[str] = [f" {name} ["]
|
|
183
|
+
for n in sorted(nodes):
|
|
184
|
+
d = G.nodes[n]
|
|
185
|
+
lbl = d.get("label", d.get("element", str(n)))
|
|
186
|
+
lines.append(f' node [ id {n} label "{lbl}" ]')
|
|
187
|
+
order_map = {1: "-", 1.5: ":", 2: "=", 3: "#"}
|
|
188
|
+
for u, v, d in sorted(edges):
|
|
189
|
+
lbl = d.get("label", order_map.get(d.get("order", 1), "-"))
|
|
190
|
+
lines.append(f' edge [ source {u} target {v} label "{lbl}" ]')
|
|
191
|
+
lines.append(" ]")
|
|
192
|
+
return lines
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def context_section(G: nx.Graph) -> List[str]:
|
|
196
|
+
"""Render the conserved context GML block.
|
|
197
|
+
|
|
198
|
+
:param G: Context graph.
|
|
199
|
+
:type G: nx.Graph
|
|
200
|
+
:return: Lines of GML for context.
|
|
201
|
+
:rtype: List[str]
|
|
202
|
+
"""
|
|
203
|
+
lines: List[str] = [" context []"] # placeholder, updated in full block
|
|
204
|
+
lines = [" context ["]
|
|
205
|
+
for n, d in sorted(G.nodes(data=True)):
|
|
206
|
+
lbl = d.get("label", d.get("element", str(n)))
|
|
207
|
+
lines.append(f' node [ id {n} label "{lbl}" ]')
|
|
208
|
+
order_map = {1: "-", 1.5: ":", 2: "=", 3: "#"}
|
|
209
|
+
for u, v, d in sorted(G.edges(data=True)):
|
|
210
|
+
lbl = d.get("label", order_map.get(d.get("order", 1), "-"))
|
|
211
|
+
lines.append(f' edge [ source {u} target {v} label "{lbl}" ]')
|
|
212
|
+
lines.append(" ]")
|
|
213
|
+
return lines
|
|
214
|
+
|
|
215
|
+
def constraints_section(self) -> List[str]:
|
|
216
|
+
"""Render placeholder constraints as one constrainLabelAny block.
|
|
217
|
+
|
|
218
|
+
:return: Lines of GML for constraints.
|
|
219
|
+
:rtype: List[str]
|
|
220
|
+
"""
|
|
221
|
+
lines: List[str] = []
|
|
222
|
+
if not self.constraint_dict:
|
|
223
|
+
return lines
|
|
224
|
+
lines.append(" constrainLabelAny [")
|
|
225
|
+
for ph, children in self.constraint_dict.items():
|
|
226
|
+
lines.append(f' label "Rest({ph})"')
|
|
227
|
+
if children:
|
|
228
|
+
labels_inner = " ".join(f'label "Rest({c})"' for c in children)
|
|
229
|
+
lines.append(f" labels [{labels_inner}]")
|
|
230
|
+
else:
|
|
231
|
+
lines.append(" labels []")
|
|
232
|
+
lines.append(" ]")
|
|
233
|
+
return lines
|
|
234
|
+
|
|
235
|
+
def to_gml(self) -> str:
|
|
236
|
+
"""Generate the full GML reaction rule string.
|
|
237
|
+
|
|
238
|
+
:return: Complete GML string for the reaction rule.
|
|
239
|
+
:rtype: str
|
|
240
|
+
"""
|
|
241
|
+
self.compute()
|
|
242
|
+
out: List[str] = ["rule [", f' ruleID "{self.rule_id}"']
|
|
243
|
+
out += self.graph_section("left", self.left, self.left_nodes, self.left_edges)
|
|
244
|
+
out += self.context_section(self.context)
|
|
245
|
+
out += self.graph_section(
|
|
246
|
+
"right", self.right, self.right_nodes, self.right_edges
|
|
247
|
+
)
|
|
248
|
+
out += self.constraints_section()
|
|
249
|
+
out.append("]")
|
|
250
|
+
return "\n".join(out)
|
|
251
|
+
|
|
252
|
+
def __repr__(self) -> str:
|
|
253
|
+
"""Return a summary of the rule converter.
|
|
254
|
+
|
|
255
|
+
:return: Brief description with node counts.
|
|
256
|
+
:rtype: str
|
|
257
|
+
"""
|
|
258
|
+
return (
|
|
259
|
+
f"<GraphToGML rule_id='{self.rule_id}', "
|
|
260
|
+
f"left_nodes={len(self.left_nodes)}, "
|
|
261
|
+
f"right_nodes={len(self.right_nodes)}, "
|
|
262
|
+
f"context_nodes={len(self.context_nodes)}>"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def help(self) -> str:
|
|
266
|
+
"""Show usage instructions for GraphToGML.
|
|
267
|
+
|
|
268
|
+
:return: Multi-line help text.
|
|
269
|
+
:rtype: str
|
|
270
|
+
"""
|
|
271
|
+
return (
|
|
272
|
+
"GraphToGML(left, right, rule_id='374')\n"
|
|
273
|
+
" - left: nx.Graph for reactant state\n"
|
|
274
|
+
" - right: nx.Graph for product state\n"
|
|
275
|
+
" - rule_id: identifier for the GML rule\n"
|
|
276
|
+
"\n"
|
|
277
|
+
"Usage:\n"
|
|
278
|
+
" g2g = GraphToGML(G_left, G_right, rule_id='374')\n"
|
|
279
|
+
" print(g2g.to_gml())\n"
|
|
280
|
+
)
|