chython 3.3.2__tar.gz → 3.4__tar.gz
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.
- {chython-3.3.2/chython.egg-info → chython-3.4}/PKG-INFO +1 -1
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_canonicalize.py +52 -8
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_isomers.py +33 -11
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_protomers.py +8 -2
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/acids.tsv +2 -2
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/standardize_groups.tsv +1 -1
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_canonicalize.py +39 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_isomers.py +37 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_protomers.py +2 -2
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_thiele_is_single_purpose.py +4 -11
- {chython-3.3.2 → chython-3.4}/chython/core/_features.pxi +9 -2
- {chython-3.3.2 → chython-3.4}/chython/core/_reaction_passes.py +34 -13
- {chython-3.3.2 → chython-3.4}/chython/core/_smirks_patch.pxi +53 -9
- {chython-3.3.2 → chython-3.4}/chython/core/_smirks_read.pxi +9 -2
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_passes.py +27 -1
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smarts_read.py +15 -6
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_patch.py +52 -1
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_query.py +5 -8
- {chython-3.3.2 → chython-3.4}/chython/reactions/_enumerate.py +174 -17
- {chython-3.3.2 → chython-3.4}/chython/reactions/_reconstruct.py +98 -34
- {chython-3.3.2 → chython-3.4}/chython/reactions/_stickers.py +46 -14
- {chython-3.3.2 → chython-3.4}/chython/reactions/_tables.py +2 -2
- {chython-3.3.2 → chython-3.4}/chython/reactions/tables/functional.tsv +26 -12
- {chython-3.3.2 → chython-3.4}/chython/reactions/tables/protective.tsv +3 -0
- chython-3.4/chython/reactions/tables/reactions.tsv +857 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/tables/roles.tsv +2 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/_frozen_ids.py +35 -3
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_enumerate.py +18 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_protective.py +67 -2
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_reconstruct.py +86 -3
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_roles.py +1 -1
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_stickers.py +87 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_tables.py +8 -2
- {chython-3.3.2 → chython-3.4}/chython/test/test_facade_names.py +3 -3
- {chython-3.3.2 → chython-3.4/chython.egg-info}/PKG-INFO +1 -1
- {chython-3.3.2 → chython-3.4}/pyproject.toml +1 -1
- chython-3.3.2/chython/reactions/tables/reactions.tsv +0 -427
- {chython-3.3.2 → chython-3.4}/LICENSE +0 -0
- {chython-3.3.2 → chython-3.4}/MANIFEST.in +0 -0
- {chython-3.3.2 → chython-3.4}/README.md +0 -0
- {chython-3.3.2 → chython-3.4}/build_inchi.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/_functions.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_abbreviations.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_counts.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_crippen.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_hydrogens.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_implicit.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_kekule_form.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_maccs.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_organometallics.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_perceive.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_pharmacophore.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_qed.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_residues.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_resonance.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_salts.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_saturate.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_smarts.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_standardize.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_tables.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/_tpsa.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/abbreviations.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/covalent_radii.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/crippen.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/hbond.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/maccs.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/maccs_corpus.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/pharmacophore.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/qed_alerts.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/residues.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/resonance.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/rotatable.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/salts.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/standardize_metals.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/sybyl_types.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/tables/tpsa.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/_oracle.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/gen_standardize_rules.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_abbreviations.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_acids_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_counts.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_covalent_radii_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_crippen.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_crippen_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_dependency_direction.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_featurizer_injection.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_featurizer_tables_lazy.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_kekule_form.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_maccs.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_maccs_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_maccs_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_organometallics.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_perceive.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_pharmacophore.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_qed.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_qed_alerts_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_reaction_hydrogen_repair.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_reaction_passes.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_residues.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_resonance.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_resonance_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_salts.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_saturate.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_smarts.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_differential.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_groups_port.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_overvalent_nitrogen.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_rules_examples.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_rules_merges.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_standardize_rules_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_tpsa.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_tpsa_tsv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_valence_report.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/chemistry/test/test_z_translation.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/RULES.md +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_canonical.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_cip.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_core.pyx +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_descriptors.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_elements.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_facade.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_fingerprints.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_hydrogens.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_inchi.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_isomorphism.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_kekule.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_log.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_ml.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_molecule_arena.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_molecule_container.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_molecule_topology.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_molecule_views.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_morgan.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_pach.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_pach3.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_query_arena.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_query_boxes.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_query_container.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_query_seal.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_rings.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_smarts_read.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_smiles_read.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_smiles_write.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_sssr.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_stereo.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_thiele.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/_valence.pxi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/elements.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/isotopes.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/reaction.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/arena_v4_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/bench_ml.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/chytorch_oracle.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/gen_element_tables.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/gen_modeling_view_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/gen_pach3_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/gen_reaction_pach_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/gen_v3_fixtures.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/gen_v4_fixtures.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/gen_valence_rules.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/modeling_view_corpus.json.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/modeling_view_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/oracle.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach3_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach_bond_group_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v0_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v0_native_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v2_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v3_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/pach_v4_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/reaction_pach_corpus.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/reaction_pach_v2_corpus.bin.gz +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_aggregates.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_alternative_spellings.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_apply_scratch_probe.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_f60.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_identity.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_v3_compat.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_arena_v4_compat.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_aromatic_storage.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_canonical.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_canonical_mirror.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_assign.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_cases.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_digraph.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_ranking.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_cip_storage.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_clean_isotopes_and_coordinate_bonds.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_clean_stereo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_conformers.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_container_log.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_copy_caches.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_derive.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_descriptors.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_element_tables.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_enrich.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_facade.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_features.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_featurizer_injection.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_fingerprints.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_geometry.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_h_unknown.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_hydrogens.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_inchi.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_interop_injection.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_isomorphism.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_kekule.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_log.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_magic.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_meta.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_encoding.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_reaction_transition.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_state_view.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_transition_view.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_ml_unpack_differential.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_modeling_view_frozen.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_molecule.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_morgan.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_no_chython_two_imports.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_oracle.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_pach.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_pach3.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_pack.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_query.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_edit.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_query.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_semantics.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_serialisation.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_smirks.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_r_storage.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_container.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_identity.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_pach.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_patch_stereo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_reaction_smiles.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_rings.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_rings_c60.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_set_element.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_sgroups.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_r.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_read.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_roundtrip.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_aromatic.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_cis_trans.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_detached.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_differential.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_h_unknown.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_stereo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smiles_write_sticky.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_filter.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_read.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_report.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_smirks_stereo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_acceptance.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_anchor_identity.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_group_storage.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_owners.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_parity.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_perception.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_units.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_stereo_v2_differential.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_structure.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_thiele.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_title.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_topology.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_union_stereo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_valence.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/test_view_surface.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/v3_fixtures.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/test/v4_fixtures.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/valence_rules.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/core/wedge.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/_config.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/_hooks.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/bonds.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/colorbar.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/colormap.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/field.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/figure.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/label.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/layout/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/layout/clean2d.js +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/layout/molecule.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/layout/reaction.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/metrics/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/metrics/helvetica.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/metrics/times.tsv +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/overlay.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/render/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/render/svg.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/scene.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/style.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_bonds.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_clean2d.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_colorbar.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_colormap.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_field.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_figure.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_label.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_metrics.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_overlay.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_package.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_r_atom.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_scene.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_style.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_svg.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_wedge_draw.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/test/test_x3dom.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/wedge.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/depict/x3dom.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/exceptions.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/_text.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/CTFILE.md +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_ctab.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_errors.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_facade.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_hydrogens.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_rdf.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_rxn.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_sdf.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_sgroup.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_stream.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_tokens.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_v2000.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/_v3000.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/conftest.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_aromatic.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_container_log.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_data_labels.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_facade.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_fidelity.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_hydrogens.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_r_atom.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_rdf.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_rxn.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_sdf.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_sgroup.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_stereo_label.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_stream.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_tokens.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_v2000.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_v3000.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/ctfile/test/test_wedge.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/mol2.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/pdb/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_builder.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_legacy.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_mmcif.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_records.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/pdb/_star.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/conftest.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/oracles.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_conformance.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_done_when.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_isolation.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_log_prefix.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_mmcif.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_mol2.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_no_review_bookkeeping.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_oracles.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_pdb.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_pdb_builder.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_perceive_agreement.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_read_only_facades.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_xyz.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/test/test_xyz_builder.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/_cml.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/_dialect.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/_errors.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/_facade.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/_mrv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/_tree.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/conftest.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_cml.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_container_log.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_dialect.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_equivalence.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_facade.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_mrv.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_mrv_census.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xml/test/test_tree.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/formats/xyz.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_cdk.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_cdpkit.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_indigo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_iupac.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_java.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_openbabel.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_pandas.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_rdkit.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_records.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/_stereo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/config.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/conformers.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/conftest.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_cdk.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_cdpkit.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_config.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_conformers.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_coordinate_honesty.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_dispatch.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_indigo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_iupac.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_log_delivery.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_openbabel.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_pandas.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_rdkit.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_stereo.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/interop/test/test_v2_oracle.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/_numbering.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/attention/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/attention/_assign.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/attention/_encode.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/attention/_session.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/gen_corpus_glossary.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/golden_subset.smi +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention_assign.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention_encode.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_attention_isolation.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_corpus_glossary.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_dependency_direction.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_functional.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_id_stability.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_numbering.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/reactions/test/test_probes.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/__init__.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_code_hygiene.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_container_methods.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_doc_figures.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_doc_references.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_doc_samples.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_hydrogen_parity.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_libinchi_staging.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_log_records.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_optional_numpy.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_packaging.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_performance.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_r_atom_integration.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_release_build.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_stereo_bluebook.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_v2_boundary.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython/test/test_writer_posture.py +0 -0
- {chython-3.3.2 → chython-3.4}/chython.egg-info/SOURCES.txt +0 -0
- {chython-3.3.2 → chython-3.4}/chython.egg-info/dependency_links.txt +0 -0
- {chython-3.3.2 → chython-3.4}/chython.egg-info/requires.txt +0 -0
- {chython-3.3.2 → chython-3.4}/chython.egg-info/top_level.txt +0 -0
- {chython-3.3.2 → chython-3.4}/setup.cfg +0 -0
- {chython-3.3.2 → chython-3.4}/setup.py +0 -0
|
@@ -48,6 +48,43 @@ _RULE_ROUNDS = 'canonicalize:rounds'
|
|
|
48
48
|
_ROUNDS_MAX = 5
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _pin_lactim(molecule: MoleculeContainer) -> bool:
|
|
52
|
+
"""Kekulise `molecule` with the C=N of one aromatic lactim pinned, when that lets a row repair it.
|
|
53
|
+
|
|
54
|
+
A site is `[O,S;D1]-c:n` with a bare two-connected ring nitrogen. Sites are tried in `atoms_order`,
|
|
55
|
+
which the placement stage has already made spelling-independent, and a site is taken only when a
|
|
56
|
+
trial `standardize()` on the pinned form turns its C-OH into C=O -- the rows stay the only authority
|
|
57
|
+
on which lactims are lactams. Returns True with the molecule kekulised and the repair still to run.
|
|
58
|
+
"""
|
|
59
|
+
ranks = molecule.atoms_order
|
|
60
|
+
sites = []
|
|
61
|
+
for o in molecule.atom_numbers:
|
|
62
|
+
if molecule.element_of(o) not in (8, 16) or molecule.charge_of(o) or molecule.implicit_h_of(o) != 1:
|
|
63
|
+
continue
|
|
64
|
+
neighbors = tuple(molecule.neighbors_of(o))
|
|
65
|
+
if len(neighbors) != 1 or molecule.order_of(o, neighbors[0]) != 1:
|
|
66
|
+
continue
|
|
67
|
+
c = neighbors[0]
|
|
68
|
+
for n in molecule.neighbors_of(c):
|
|
69
|
+
if (molecule.element_of(n) == 7 and molecule.order_of(c, n) == 4 and not molecule.charge_of(n)
|
|
70
|
+
and not molecule.implicit_h_of(n) and len(tuple(molecule.neighbors_of(n))) == 2):
|
|
71
|
+
sites.append((ranks[o], ranks[n], o, c, n))
|
|
72
|
+
for *_, o, c, n in sorted(sites):
|
|
73
|
+
trial = molecule.copy()
|
|
74
|
+
with trial.edit():
|
|
75
|
+
trial.set_order(c, n, 2)
|
|
76
|
+
if trial.kekule().unresolved or trial.order_of(c, n) != 2:
|
|
77
|
+
continue
|
|
78
|
+
standardize(trial)
|
|
79
|
+
if trial.order_of(o, c) != 2:
|
|
80
|
+
continue
|
|
81
|
+
with molecule.edit():
|
|
82
|
+
molecule.set_order(c, n, 2)
|
|
83
|
+
molecule.kekule()
|
|
84
|
+
return True
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
|
|
51
88
|
def canonicalize(molecule: MoleculeContainer, *, fix_tautomers: bool = True,
|
|
52
89
|
keep_kekule: bool = False) -> bool:
|
|
53
90
|
"""Bring `molecule` to the representation two drawings of one compound share. Did it change?
|
|
@@ -149,25 +186,32 @@ def canonicalize(molecule: MoleculeContainer, *, fix_tautomers: bool = True,
|
|
|
149
186
|
#
|
|
150
187
|
# `kekule()` leads, and not for the reason step 1 does: the `tautomer` rows are written against
|
|
151
188
|
# definite bond orders, so on the aromatic form step 7 left behind they match nothing at all and
|
|
152
|
-
# re-running step 3 would be a guaranteed no-op. Step 5 is re-entered
|
|
153
|
-
#
|
|
189
|
+
# re-running step 3 would be a guaranteed no-op. Step 5 is re-entered every round: a repair and
|
|
190
|
+
# the placement both hand it charged sites it has not seen -- the placement moves an N-oxide's
|
|
191
|
+
# charge onto an azole NH, `c1ccc2[nH]cnc2[n+]1[O-]` to `c1ccc2nc[nH+]c2n1[O-]`, a zwitterion
|
|
192
|
+
# step 5 pairs off -- and what it pairs off can unblock the placement again. It cannot cycle:
|
|
193
|
+
# step 5 only ever reduces the number of charged atoms and step 8 never adds one. Step 6 rides
|
|
154
194
|
# with the aromatisation and not with the repair: the kekulisation above is free to come back with
|
|
155
195
|
# a different Kekule form than the one it was handed, which is the form step 7 would then read.
|
|
156
196
|
# Step 2 is not re-entered: the parities it can justify are a property of the constitution, which
|
|
157
197
|
# no stage from here on changes.
|
|
198
|
+
#
|
|
199
|
+
# A round the placement did not ask for is opened by a lactim `kekule()` left unrepairable: the
|
|
200
|
+
# hydroxy-azine rows need the C=N beside the C-OH, and which Kekule form comes back depends on the
|
|
201
|
+
# atom order. `_pin_lactim` pins that C=N and kekulises around it, so 3-hydroxyisoquinoline
|
|
202
|
+
# reaches isoquinolin-3(2H)-one from every order.
|
|
158
203
|
for _ in range(_ROUNDS_MAX):
|
|
159
|
-
if
|
|
204
|
+
if moved:
|
|
205
|
+
molecule.kekule()
|
|
206
|
+
elif not (fix_tautomers and _pin_lactim(molecule)):
|
|
160
207
|
break
|
|
161
|
-
molecule.kekule()
|
|
162
208
|
changed = standardize(molecule, fix_tautomers=fix_tautomers)
|
|
163
209
|
if changed:
|
|
164
210
|
implicify_hydrogens(molecule)
|
|
165
|
-
|
|
211
|
+
changed |= neutralize(molecule)
|
|
166
212
|
standardize_kekule(molecule)
|
|
167
213
|
molecule.thiele() # unconditional: step 6's form is what a caller compares, and
|
|
168
|
-
|
|
169
|
-
break
|
|
170
|
-
moved = standardize_isomers(molecule)
|
|
214
|
+
moved = changed and standardize_isomers(molecule) # the kekulisation has to be undone either way
|
|
171
215
|
else:
|
|
172
216
|
with recording(molecule, stage='canonicalize') as log:
|
|
173
217
|
log.append(LogRecord(_RULE_ROUNDS, (), f'repair and placement were still changing the '
|
|
@@ -217,8 +217,9 @@ def _deal(sites: tuple[int, ...], states: tuple[tuple, ...]):
|
|
|
217
217
|
|
|
218
218
|
|
|
219
219
|
def _admissible(molecule: MoleculeContainer, group: list[int],
|
|
220
|
-
placement: dict[int, tuple[int, int]]) ->
|
|
221
|
-
"""
|
|
220
|
+
placement: dict[int, tuple[int, int]]) -> int | None:
|
|
221
|
+
"""How many bonds `thiele()` writes aromatic under this placement, or `None` when it gives no Kekule
|
|
222
|
+
form for this group. The kekuliser is the oracle.
|
|
222
223
|
|
|
223
224
|
This group's systems and not the whole molecule's: one ring nobody can kekulise (`c1cccc1`) would
|
|
224
225
|
otherwise make every placement of every other ring inadmissible.
|
|
@@ -238,17 +239,32 @@ def _admissible(molecule: MoleculeContainer, group: list[int],
|
|
|
238
239
|
work.set_hydrogens(n, hydrogens)
|
|
239
240
|
members = frozenset(group)
|
|
240
241
|
if any(members.intersection(system) for system in work.kekule().unresolved):
|
|
241
|
-
return
|
|
242
|
-
|
|
242
|
+
return None
|
|
243
|
+
if any((work.implicit_h_of(n), work.charge_of(n)) != state for n, state in placement.items()):
|
|
244
|
+
return None
|
|
245
|
+
work.thiele()
|
|
246
|
+
return work.aromatic_bond_count
|
|
243
247
|
|
|
244
248
|
|
|
245
249
|
def _choose(molecule: MoleculeContainer, group: list[int], ranks: dict[int, int]):
|
|
246
250
|
"""The canonical placement for one group, `None` when nothing must move, `'budget'` when too big.
|
|
247
251
|
|
|
248
252
|
The hydrogens and the charges are read off the group rather than assumed, and dealt back over it. The
|
|
249
|
-
key is the
|
|
250
|
-
|
|
251
|
-
|
|
253
|
+
key is the aromatic bond count `thiele()` gives the placement, most first, then the hydrogens beside a
|
|
254
|
+
C=O or C=S carbon, then the hydrogens on five-membered rings, each most first, then the sorted ranks
|
|
255
|
+
of the sites holding the hydrogens, then of those holding each charge in turn -- a strict total order,
|
|
256
|
+
because `atoms_order` is a permutation and those two sets fix the placement, so no two placements
|
|
257
|
+
share a key and no tie is left for an arbitrary rule to break.
|
|
258
|
+
|
|
259
|
+
A lactam N-H sits next to its carbonyl: 6-methylpyrimidin-4(3H)-one, not its 1H form. An azole N-H
|
|
260
|
+
outranks an azine N-H whenever both forms are equally aromatic, so every 7-azaindole reads 1H
|
|
261
|
+
(`c1cnc2[nH]ccc2c1`), its carboxylic acid too. Ring size and bond order are graph facts, so both
|
|
262
|
+
terms are as spelling-independent as the ranks after them.
|
|
263
|
+
|
|
264
|
+
Aromaticity leads because a fused system spelled aromatic joins rings a hydrogen cannot cross for
|
|
265
|
+
free: pyrido[4,3-d]pyrimidine-2,4-dione `O=C1NC(=O)c2cnccc2N1` with its N1-H moved onto the pyridine
|
|
266
|
+
N kekulises, as a quinoid imine with no pyridine sextet. Only nitrogen sites deal here and `thiele()`
|
|
267
|
+
leaves a lactam non-aromatic, so the term never weighs an enol against its ketone.
|
|
252
268
|
|
|
253
269
|
A hydrogen deals over the sites with two heavy neighbours and a charge over all of them, which is the
|
|
254
270
|
whole difference a substituted nitrogen makes. That much is not left to the oracle: `kekule()` decides
|
|
@@ -268,16 +284,22 @@ def _choose(molecule: MoleculeContainer, group: list[int], ranks: dict[int, int]
|
|
|
268
284
|
if trials == 1:
|
|
269
285
|
return None # one way to deal them: there is no distribution to choose
|
|
270
286
|
signs = [q for q in sorted(charges) if q]
|
|
287
|
+
azole = {n for n in carriers if 5 in molecule.ring_sizes_of(n)}
|
|
288
|
+
lactam = {n for n in carriers
|
|
289
|
+
if any(any(molecule.order_of(c, x) == 2 and molecule.element_of(x) in _DONOR
|
|
290
|
+
for x in molecule.neighbors_of(c)) for c in molecule.neighbors_of(n))}
|
|
271
291
|
current = {n: (molecule.implicit_h_of(n) or 0, molecule.charge_of(n)) for n in group}
|
|
272
292
|
best = None
|
|
273
293
|
for protonated in combinations(carriers, hydrogens):
|
|
274
294
|
for dealt in _deal(tuple(group), order):
|
|
275
295
|
placement = {n: (1 if n in protonated else 0, dealt[n]) for n in group}
|
|
276
|
-
|
|
296
|
+
aromatic = _admissible(molecule, group, placement)
|
|
297
|
+
if aromatic is None:
|
|
298
|
+
continue
|
|
299
|
+
key = [[-aromatic, -sum(n in lactam for n in protonated), -sum(n in azole for n in protonated)],
|
|
300
|
+
sorted(ranks[n] for n in protonated)]
|
|
277
301
|
key.extend(sorted(ranks[n] for n in group if dealt[n] == q) for q in signs)
|
|
278
|
-
if best is
|
|
279
|
-
continue # cheaper than the oracle, so it goes first
|
|
280
|
-
if _admissible(molecule, group, placement):
|
|
302
|
+
if best is None or key < best[0]:
|
|
281
303
|
best = (key, placement)
|
|
282
304
|
if best is None or best[1] == current:
|
|
283
305
|
return None
|
|
@@ -66,6 +66,11 @@ def _admitted(molecule: MoleculeContainer, candidates: dict[int, AcidRow], delta
|
|
|
66
66
|
f'atom {n} has no derivable implicit hydrogen count, so a proton '
|
|
67
67
|
f'cannot be counted off or onto it', REFUSED))
|
|
68
68
|
continue
|
|
69
|
+
if hydrogens + delta < 0:
|
|
70
|
+
lines.append(LogRecord(row.id, (n,),
|
|
71
|
+
f'atom {n} holds its proton as a drawn hydrogen atom, and this pass '
|
|
72
|
+
f'writes implicit counts only', REFUSED))
|
|
73
|
+
continue
|
|
69
74
|
order_sum, environment, aromatic = environment_of(molecule, n)
|
|
70
75
|
# an aromatic bond has no valence row, so the question cannot be put -- `check_valence` calls
|
|
71
76
|
# that `unknown` rather than a violation, and a pyridinium must stay deprotonatable.
|
|
@@ -101,8 +106,9 @@ def neutralize(molecule: MoleculeContainer, *, keep_charge: bool = True) -> bool
|
|
|
101
106
|
nitrate past zero. `keep_charge=False` lets a site act alone, as far as its own component's charge
|
|
102
107
|
allows: `C[NH3+]` alone becomes `CN`.
|
|
103
108
|
|
|
104
|
-
Sites are found by `tables/acids.tsv`, whose `h` primitive
|
|
105
|
-
|
|
109
|
+
Sites are found by `tables/acids.tsv`, whose `h` primitive counts drawn hydrogen atoms too, but a
|
|
110
|
+
proton is moved only off an implicit count, so a molecule carrying explicit hydrogen atoms wants
|
|
111
|
+
`implicify_hydrogens()` first. A site whose hydrogen count
|
|
106
112
|
is not derivable, and one whose neutral form no valence row accepts, is refused and logged.
|
|
107
113
|
"""
|
|
108
114
|
lines: list[LogRecord] = []
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
# pattern matching nothing is invisible: the pass just stops recognizing that site.
|
|
15
15
|
# comment what the row claims. Documentation only.
|
|
16
16
|
#
|
|
17
|
-
# `h` COUNTS
|
|
18
|
-
# hydrogen ATOMS. `implicify_hydrogens()` first; the pass says so in its docstring.
|
|
17
|
+
# `h` COUNTS DRAWN HYDROGENS TOO, but a proton moves only off an implicit count, so `[N+]([H])([H])[H]`
|
|
18
|
+
# drawn with three hydrogen ATOMS is refused. `implicify_hydrogens()` first; the pass says so in its docstring.
|
|
19
19
|
#
|
|
20
20
|
# AN UNSTATED CHARGE IS NEUTRAL, not "any charge", which is why nitrate has a row of its own: the anchor
|
|
21
21
|
# oxygen's neighbour is `[N+]`, and `acids:alkoxide`'s `[C,N]` admits the neutral nitrogen alone.
|
|
@@ -87,7 +87,7 @@ groups:05 [B;z1]-[O,S;D3;z1] - 1:2:8 0 - BS(C)C>>[BH3]~[S](C)C A three-coordinat
|
|
|
87
87
|
groups:06 [B;z2;-]=[N;D1,D2,D3;z2;+] 1:1:-;2:-1:- 1:2:1 0 - [B-]=[N+]>>BN A borane adduct drawn as the ylide [B-]=[N+]; the two charges cancel and the bond is an ordinary single one
|
|
88
88
|
groups:07 [B;D4;z1;+3]([A;-])([A;-])([A;-])[A;-] 1:-4:-;2:1:-;3:1:-;4:1:-;5:1:- - 0 - [O-][B+3]([O-])([O-])[O-]>>[B-](O)(O)(O)O A borate drawn with the whole count of charges on the boron and an anion on every ligand as well; the boron is [B-] and the four ligands are neutral
|
|
89
89
|
groups:08 [B;D4;z1]-[A;-] 1:-1:-;2:1:- - 0 - [O-]B(O)(O)O>>[B-](O)(O)(O)O A four-coordinate boron carrying an anionic ligand holds the charge itself; the negative charge moves from the ligand to the boron
|
|
90
|
-
groups:09 [B;D4;z1] 1:-1:- - 0 groups:
|
|
90
|
+
groups:09 [B;D4;z1] 1:-1:- - 0 groups:08 OB(O)(O)O>>[B-](O)(O)(O)O Four single bonds on a boron is a borate; the drawing left it neutral, so the charge is added
|
|
91
91
|
groups:10 [P&D4&x0&z1,N&D4&z1] 1:1:- - 0 - CP(C)(C)C>>C[P+](C)(C)C;CN(C)(C)C>>C[N+](C)(C)C Four single bonds on a phosphorus with no heteroatom neighbour, or on a nitrogen, is a phosphonium or a quaternary ammonium; the drawing left it neutral, so the charge is added and the phantom hydrogen goes with it
|
|
92
92
|
groups:11 [N;D3;z5;x2](=[O;D1])([O;D1])=C 1:1:-;3:-1:- 1:4:1 1 - C=N(=O)O>>C[N+]([O-])=O A nitro group drawn pentavalent in its aci form, C=N(=O)OH; the nitro becomes charge-separated and the acidic hydrogen moves from oxygen to carbon
|
|
93
93
|
groups:12 [N;D3;z5](=[O;D1])(=[C,N,O])-[A] 1:1:-;2:-1:- 1:2:1 0 groups:11 C=N(C)=O>>C=[N+](C)[O-] A pentavalent nitrogen carrying an N=O and a second double bond; the N=O is separated into [N+]-[O-] and the other double bond is left as drawn
|
|
@@ -228,6 +228,21 @@ def test_the_pipeline_is_a_fixed_point_and_not_merely_ordered():
|
|
|
228
228
|
assert m.canonical_bytes == first, f'{label}: {string} is not a fixed point'
|
|
229
229
|
|
|
230
230
|
|
|
231
|
+
def test_a_placement_that_makes_a_zwitterion_is_neutralized():
|
|
232
|
+
"""Step 8 can hand step 5 a charged site: it moves an N-oxide's charge onto the azole NH.
|
|
233
|
+
|
|
234
|
+
Imidazo[4,5-b]pyridine 4-oxide and its N-hydroxy tautomer are one compound, and each drawing is a
|
|
235
|
+
fixed point -- the zwitterion step 8 leaves behind is not.
|
|
236
|
+
"""
|
|
237
|
+
for oxide, hydroxy in (('c1ccc2[nH]cnc2[n+]1[O-]', 'c1ccc2ncnc2n1O'),
|
|
238
|
+
('c1[n+]([O-])c2[nH]cnc2c([N+](=O)[O-])c1', 'c1n(O)c2ncnc2c([N+](=O)[O-])c1')):
|
|
239
|
+
a, b = smiles(oxide), smiles(hydroxy)
|
|
240
|
+
a.canonicalize()
|
|
241
|
+
b.canonicalize()
|
|
242
|
+
assert a == b, f'{oxide} and {hydroxy} keep two keys: {a} vs {b}'
|
|
243
|
+
assert a.canonicalize() is False, f'{oxide} still moved on a second pass'
|
|
244
|
+
|
|
245
|
+
|
|
231
246
|
def test_the_extra_round_costs_no_duplicate_repair_record():
|
|
232
247
|
"""A repair is reported once however many rounds saw the molecule.
|
|
233
248
|
|
|
@@ -604,3 +619,27 @@ def test_nothing_to_do_is_zero_and_touches_nothing():
|
|
|
604
619
|
assert explicify_hydrogens(m) == 0, s
|
|
605
620
|
assert len(m) == n
|
|
606
621
|
assert not [r for r in log if r.rule == 'hydrogens:explicify']
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
LACTIMS = ['Oc1cc2ccccc2cn1', # 3-hydroxyisoquinoline
|
|
625
|
+
'Cc1ncc2cnc(O)c(-c3ccccc3)c2n1', # a fused hydroxypyridine beside a pyrimidine
|
|
626
|
+
'Oc1ncnc2ccccc12', # quinazolin-4-ol
|
|
627
|
+
'Cc1cc(O)nc(C)n1'] # 2,6-dimethylpyrimidin-4-ol
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def test_a_lactim_reaches_its_lactam_from_every_atom_order():
|
|
631
|
+
"""One key per compound, whichever Kekule form `kekule()` happened to return, with the NH beside
|
|
632
|
+
the C=O."""
|
|
633
|
+
import random
|
|
634
|
+
random.seed(0)
|
|
635
|
+
for lactim in LACTIMS:
|
|
636
|
+
keys = set()
|
|
637
|
+
for _ in range(40):
|
|
638
|
+
molecule = smiles(format(smiles(lactim), 'r'))
|
|
639
|
+
molecule.canonicalize()
|
|
640
|
+
keys.add(molecule.canonical_bytes)
|
|
641
|
+
assert len(keys) == 1, lactim
|
|
642
|
+
assert not any(molecule.element_of(n) == 8 and molecule.implicit_h_of(n) for n in molecule.atom_numbers)
|
|
643
|
+
nh = [n for n in molecule.atom_numbers if molecule.element_of(n) == 7 and molecule.implicit_h_of(n)]
|
|
644
|
+
assert [any(molecule.element_of(x) == 8 for c in molecule.neighbors_of(n) for x in molecule.neighbors_of(c))
|
|
645
|
+
for n in nh] == [True], f'{lactim}: {molecule}'
|
|
@@ -192,6 +192,43 @@ def test_two_kekule_tautomers_of_one_lactam_store_the_same_molecule():
|
|
|
192
192
|
f'{label}: {a} and {b} still differ, {ma} vs {mb}'
|
|
193
193
|
|
|
194
194
|
|
|
195
|
+
AROMATIC_PAIRS = [
|
|
196
|
+
('O=C1NC(=O)c2cnccc2N1', 'O=C1NC(=O)C2=CNC=CC2=N1', 'pyrido[4,3-d]pyrimidine-2,4-dione'),
|
|
197
|
+
('O=C1Nc2cc3ncccc3cc2N1', 'O=C1N=C2C=C3C(=CC=CN3)C=C2N1', 'imidazo[4,5-g]quinolin-2-one'),
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_a_fused_lactam_keeps_its_pyridine_aromatic():
|
|
202
|
+
"""A hydrogen on a lactam nitrogen does not move onto a fused pyridine nitrogen, whichever form arrives.
|
|
203
|
+
|
|
204
|
+
Both placements kekulise, so the canonical order alone would take whichever ranks lower; the imine
|
|
205
|
+
leaves the pyridine without a sextet and is the form that loses.
|
|
206
|
+
"""
|
|
207
|
+
for aromatic, quinoid, label in AROMATIC_PAIRS:
|
|
208
|
+
ma, mb = read_smiles(aromatic), read_smiles(quinoid)
|
|
209
|
+
canonicalize(ma)
|
|
210
|
+
canonicalize(mb)
|
|
211
|
+
assert ma.aromatic_rings_count == read_smiles(aromatic).aromatic_rings_count, f'{label}: {ma}'
|
|
212
|
+
assert ma.canonical_bytes == mb.canonical_bytes, f'{label}: {ma} vs {mb}'
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
AZOLE_FIRST = [('c1cnc2[nH]ccc2c1', 'C1=CNC2=NC=CC2=C1', '7-azaindole'),
|
|
216
|
+
('OC(=O)c1ccc2cc[nH]c2n1', 'OC(=O)C1=CC=C2C=CN=C2N1', '7-azaindole-6-carboxylic acid'),
|
|
217
|
+
('NC(=O)c1ccc2cc[nH]c2n1', 'NC(=O)C1=CC=C2C=CN=C2N1', '7-azaindole-6-carboxamide')]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_an_azole_nh_outranks_an_azine_nh():
|
|
221
|
+
"""Both forms are aromatic, so the ring size decides: the hydrogen sits on the five-membered ring."""
|
|
222
|
+
for azole, azine, label in AZOLE_FIRST:
|
|
223
|
+
ma, mb = read_smiles(azole), read_smiles(azine)
|
|
224
|
+
canonicalize(ma)
|
|
225
|
+
canonicalize(mb)
|
|
226
|
+
assert ma.canonical_bytes == mb.canonical_bytes, f'{label}: {ma} vs {mb}'
|
|
227
|
+
nh = [n for n in ma.atom_numbers if ma.element_of(n) == 7 and ma.total_h_of(n)
|
|
228
|
+
and len(tuple(ma.neighbors_of(n))) == 2]
|
|
229
|
+
assert [5 in ma.ring_sizes_of(n) for n in nh] == [True], f'{label}: {ma}'
|
|
230
|
+
|
|
231
|
+
|
|
195
232
|
def test_a_kekule_placement_never_changes_the_formula():
|
|
196
233
|
"""The working copy is spelled aromatic and kekulised back, and `kekule()` repairs when it must.
|
|
197
234
|
|
|
@@ -175,8 +175,8 @@ def test_a_site_taken_past_zero_alone_is_refused():
|
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
def test_explicit_hydrogens_hide_the_site():
|
|
178
|
-
"""
|
|
179
|
-
they are folded in. The docstring says `implicify_hydrogens()` first, and this is why."""
|
|
178
|
+
"""A proton is moved only off an implicit count, so a cation drawn with hydrogen atoms is refused
|
|
179
|
+
until they are folded in. The docstring says `implicify_hydrogens()` first, and this is why."""
|
|
180
180
|
m = smiles('[H][N+]([H])([H])C.[Cl-]')
|
|
181
181
|
assert neutralize(m) is False
|
|
182
182
|
implicify_hydrogens(m)
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"""Asking for the aromatic spelling must not silently choose a tautomer.
|
|
20
20
|
|
|
21
21
|
`kekule()` repairs, because it is the boundary where input arrives; `thiele()` must not, because by
|
|
22
|
-
then the data is the library's own arena. The hydrogen move chython 2 did inside `thiele`
|
|
22
|
+
then the data is the library's own arena. The hydrogen move chython 2 did inside `thiele` belongs to
|
|
23
23
|
`standardize_isomers` instead. Both halves are pinned here.
|
|
24
24
|
"""
|
|
25
25
|
import pytest
|
|
@@ -96,16 +96,9 @@ for line in sys.stdin:
|
|
|
96
96
|
assert on_smiles != off_smiles
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
'the isomer pass lands -- a strict xfail is what makes that '
|
|
103
|
-
'a required edit rather than an optional one')
|
|
104
|
-
def test_the_hydrogen_move_is_owed_to_standardize_isomers():
|
|
105
|
-
"""What the split costs until the isomer pass exists, written down as a failing test.
|
|
106
|
-
|
|
107
|
-
The end state is the one chython 2 reached by combining the two jobs, and is what the ported rule
|
|
108
|
-
must reproduce: the hydrogen on atom 5, not atom 1.
|
|
99
|
+
def test_the_hydrogen_move_belongs_to_standardize_isomers():
|
|
100
|
+
"""The hydrogen move chython 2 made inside `thiele` is `standardize_isomers`' own: an azole N-H
|
|
101
|
+
outranks an azine N-H, so the hydrogen goes to atom 5, not atom 1.
|
|
109
102
|
"""
|
|
110
103
|
molecule = read_smiles(SHIFTED)
|
|
111
104
|
molecule.thiele()
|
|
@@ -245,6 +245,9 @@ cdef int fill_features(Structure structure) except -1:
|
|
|
245
245
|
# `rebuild_derived` derives degree as the CSR row length, `_pach.pxi` writes it as that
|
|
246
246
|
# row length, and `_stereo.pxi` reads it as connectivity. Two different facts, so two
|
|
247
247
|
# counts -- neither is a stale copy of the other, and each is derived in one place only.
|
|
248
|
+
#
|
|
249
|
+
# A hydrogen ATOM of any isotope is not a `D` neighbour: `[2H]C([2H])(C)O` is a primary
|
|
250
|
+
# alcohol, `[C;D2]`, exactly as `C(C)O` is. It counts towards `h` below instead.
|
|
248
251
|
w1 = w0_element_bits(element)
|
|
249
252
|
degree = 0
|
|
250
253
|
heteroatoms = 0
|
|
@@ -252,8 +255,10 @@ cdef int fill_features(Structure structure) except -1:
|
|
|
252
255
|
w1 |= w0_bond_bits(&edges[k])
|
|
253
256
|
if edges[k].order == 8:
|
|
254
257
|
continue
|
|
255
|
-
degree += 1
|
|
256
258
|
nb_element = atoms[edges[k].to].element
|
|
259
|
+
if nb_element == 1:
|
|
260
|
+
continue
|
|
261
|
+
degree += 1
|
|
257
262
|
if element_is_heteroatom(nb_element):
|
|
258
263
|
heteroatoms += 1
|
|
259
264
|
|
|
@@ -299,8 +304,10 @@ cdef int fill_features(Structure structure) except -1:
|
|
|
299
304
|
# the one function-body forward reference in the core.
|
|
300
305
|
w3 |= SPAN_MASK[SPAN_IMPLICIT_H] | SPAN_MASK[SPAN_TOTAL_H]
|
|
301
306
|
else:
|
|
307
|
+
# `h` is every attached hydrogen, implicit or drawn, so a deuterium answers `h` as the
|
|
308
|
+
# protium `canonicalize()` would have folded into the count. `H` is the same sum.
|
|
302
309
|
th = ih + eh
|
|
303
|
-
w3 |= <uint64_t> 1 << (17 + _bit_of(<int32_t>
|
|
310
|
+
w3 |= <uint64_t> 1 << (17 + _bit_of(<int32_t> th, 0, 4))
|
|
304
311
|
w3 |= <uint64_t> 1 << (27 + _bit_of(<int32_t> th, 0, 5))
|
|
305
312
|
w3 |= <uint64_t> 1 << (33 + _bit_of(a.charge, -4, 8))
|
|
306
313
|
if a.isotope:
|
|
@@ -354,10 +354,12 @@ def reaction_reset_mapping(rxn) -> bool:
|
|
|
354
354
|
def _side_states(molecules):
|
|
355
355
|
"""`({map number: state}, {colliding map numbers})` for one side.
|
|
356
356
|
|
|
357
|
-
A state is `(element, charge, radical, implicit h, {neighbour map number: order})`
|
|
358
|
-
CGR's dynamic atom and dynamic bond carried between them, which is what the reaction
|
|
359
|
-
defined against.
|
|
360
|
-
|
|
357
|
+
A state is `(element, charge, radical, implicit h, {neighbour map number: order}, unmapped branches)`
|
|
358
|
+
-- everything a CGR's dynamic atom and dynamic bond carried between them, which is what the reaction
|
|
359
|
+
centre is defined against. An unmapped neighbour has no identity across the arrow, so it enters as a
|
|
360
|
+
description: the canonical SMILES of the unmapped region it belongs to, rooted at the mapped atom by
|
|
361
|
+
its map number. `[C:1](=[O:2])OC >> [C:1](=[O:2])O` changes atom 1's branch from `[C:1]OC` to
|
|
362
|
+
`[C:1]O`; an unmapped `OH` drawn on both sides is the same branch twice and changes nothing.
|
|
361
363
|
"""
|
|
362
364
|
states = {}
|
|
363
365
|
collisions = set()
|
|
@@ -371,23 +373,42 @@ def _side_states(molecules):
|
|
|
371
373
|
if mn in states:
|
|
372
374
|
collisions.add(mn)
|
|
373
375
|
local[atom.n] = mn
|
|
374
|
-
states[mn] = (atom.element, atom.charge, atom.is_radical, atom.implicit_h, {})
|
|
376
|
+
states[mn] = (atom.element, atom.charge, atom.is_radical, atom.implicit_h, {}, [])
|
|
375
377
|
for bond in molecule.bonds():
|
|
376
378
|
mn, mm = local.get(bond.n), local.get(bond.m)
|
|
377
|
-
if mn is None
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
379
|
+
if mn is not None and mm is not None:
|
|
380
|
+
states[mn][4][mm] = bond.order
|
|
381
|
+
states[mm][4][mn] = bond.order
|
|
382
|
+
for n, mn in local.items():
|
|
383
|
+
states[mn][5].extend(_unmapped_branches(molecule, n, local))
|
|
384
|
+
return {mn: (*state[:5], tuple(sorted(state[5]))) for mn, state in states.items()}, collisions
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _unmapped_branches(molecule, n, mapped):
|
|
388
|
+
"""The canonical SMILES of each unmapped region bonded to atom `n`, with `n` itself as the root."""
|
|
389
|
+
seen = set()
|
|
390
|
+
for start in molecule.neighbors_of(n):
|
|
391
|
+
if start in mapped or start in seen:
|
|
392
|
+
continue
|
|
393
|
+
region = {start}
|
|
394
|
+
stack = [start]
|
|
395
|
+
while stack:
|
|
396
|
+
for m in molecule.neighbors_of(stack.pop()):
|
|
397
|
+
if m not in mapped and m not in region:
|
|
398
|
+
region.add(m)
|
|
399
|
+
stack.append(m)
|
|
400
|
+
seen |= region
|
|
401
|
+
region.add(n)
|
|
402
|
+
yield format(molecule.substructure(region), 'm')
|
|
382
403
|
|
|
383
404
|
|
|
384
405
|
def reaction_center(rxn) -> set[int]:
|
|
385
406
|
"""The map numbers of the atoms this reaction changes. Empty for a reaction with no mapping.
|
|
386
407
|
|
|
387
408
|
An atom is in the centre when it appears on BOTH sides and something about it differs: its element,
|
|
388
|
-
charge, radical state, implicit hydrogen count,
|
|
389
|
-
computed without building a CGR: there is
|
|
390
|
-
not need one.
|
|
409
|
+
charge, radical state, implicit hydrogen count, the map numbers and orders of its bonds, or the
|
|
410
|
+
unmapped branches bonded to it (`_side_states`). It is computed without building a CGR: there is
|
|
411
|
+
no CGR container on this release, and the question does not need one.
|
|
391
412
|
|
|
392
413
|
AN ATOM PRESENT ON ONLY ONE SIDE IS NOT IN THE CENTRE, and that is a decision. Calling it dynamic
|
|
393
414
|
instead -- a bond that exists on one side and not the other -- keeps sodium hydroxide a REACTANT in
|
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
#
|
|
54
54
|
# * an atom the patch WROTE (its element, charge, radical, or one of its bonds) and every surviving
|
|
55
55
|
# neighbour of a deleted atom gets its count recomputed from the valence collection;
|
|
56
|
-
# *
|
|
56
|
+
# * a pnictogen sharing an aromatic ring with a pnictogen the patch bonded is recomputed -- the
|
|
57
|
+
# azole NH of the tautomer the patch replaced;
|
|
58
|
+
# * any other atom that merely sat inside the match keeps the count its input stated, exactly;
|
|
57
59
|
# * where the collection has no answer -- no row for the element in that charge and radical state,
|
|
58
60
|
# or an aromatic bond reaching the atom -- the count is stored as `H_UNKNOWN`.
|
|
59
61
|
#
|
|
@@ -189,6 +191,39 @@ cdef int smk_hydrogens(MoleculeContainer m, uint32_t n) except -2:
|
|
|
189
191
|
return H_UNKNOWN
|
|
190
192
|
|
|
191
193
|
|
|
194
|
+
cdef bint smk_aromatic_pnictogen(MoleculeContainer m, object n) except -1:
|
|
195
|
+
cdef object other
|
|
196
|
+
if m.element_of(<uint32_t> n) not in (7, 15, 33):
|
|
197
|
+
return False
|
|
198
|
+
for other in m.neighbors_of(<uint32_t> n):
|
|
199
|
+
if m.order_of(<uint32_t> n, <uint32_t> other) == 4:
|
|
200
|
+
return True
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
cdef int smk_ring_pnictogens(MoleculeContainer new, set gained, set alive, set changed) except -1:
|
|
205
|
+
"""Add to `changed` every pnictogen with hydrogens sharing a ring with a bonded aromatic pnictogen.
|
|
206
|
+
|
|
207
|
+
The pyrrole-type hydrogen of an azole belongs to one tautomer. N-substituting any other ring
|
|
208
|
+
nitrogen makes it the other tautomer's, so the NH is recomputed although the patch never wrote
|
|
209
|
+
it, and comes back `H_UNKNOWN` for `kekule()`. Example: 5-methyltetrazole arylated at N2.
|
|
210
|
+
"""
|
|
211
|
+
cdef set sites = set()
|
|
212
|
+
cdef object ring, n
|
|
213
|
+
for n in gained:
|
|
214
|
+
if n in alive and smk_aromatic_pnictogen(new, n):
|
|
215
|
+
sites.add(n)
|
|
216
|
+
if not sites:
|
|
217
|
+
return 0
|
|
218
|
+
for ring in new.rings:
|
|
219
|
+
if sites.isdisjoint(ring):
|
|
220
|
+
continue
|
|
221
|
+
for n in ring:
|
|
222
|
+
if n not in changed and new.implicit_h_of(<uint32_t> n) and smk_aromatic_pnictogen(new, n):
|
|
223
|
+
changed.add(n)
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
|
|
192
227
|
# "the element is the R marker", in `smk_atom_fields`'s element slot, where 0 already means INHERIT.
|
|
193
228
|
# A distinct value because those two are distinct claims, and 118 elements sit between them.
|
|
194
229
|
DEF SMK_ELEMENT_R = -1
|
|
@@ -928,6 +963,8 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
|
|
|
928
963
|
if key not in doomed:
|
|
929
964
|
changed.add(key)
|
|
930
965
|
|
|
966
|
+
# atoms given a NEW bond: the ring-pnictogen rule below starts from them
|
|
967
|
+
cdef set gained = set()
|
|
931
968
|
cdef set prod_bond_set = set(t.product_bonds)
|
|
932
969
|
cdef tuple fields
|
|
933
970
|
cdef uint32_t nid
|
|
@@ -957,7 +994,8 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
|
|
|
957
994
|
if <int> fields[1] != work.charge_of(nid):
|
|
958
995
|
new.set_charge(nid, <int> fields[1])
|
|
959
996
|
changed.add(<object> nid)
|
|
960
|
-
|
|
997
|
+
# an unstated product isotope (0) keeps the reactant's: `[C:1]` does not strip a 14C label
|
|
998
|
+
if <int> fields[2] and <int> fields[2] != work.isotope_of(nid):
|
|
961
999
|
new.set_isotope(nid, <int> fields[2])
|
|
962
1000
|
if <bint> fields[3] != work.radical_of(nid):
|
|
963
1001
|
new.set_radical(nid, <bint> fields[3])
|
|
@@ -997,8 +1035,11 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
|
|
|
997
1035
|
new.add_bond(<uint32_t> u, <uint32_t> v, order)
|
|
998
1036
|
changed.add(u)
|
|
999
1037
|
changed.add(v)
|
|
1038
|
+
gained.add(u)
|
|
1039
|
+
gained.add(v)
|
|
1000
1040
|
|
|
1001
1041
|
cdef set alive = set(new.atom_numbers)
|
|
1042
|
+
smk_ring_pnictogens(new, gained, alive, changed)
|
|
1002
1043
|
|
|
1003
1044
|
# Hydrogens BEFORE the stereo directives, and the order is load-bearing: whether an anchor's
|
|
1004
1045
|
# fourth direction is an atom or its implicit hydrogen is a fact about the count, so a directive
|
|
@@ -1173,7 +1214,8 @@ cdef tuple smk_one(ReactionTemplate t, MoleculeContainer work, set work_bonds, d
|
|
|
1173
1214
|
(tuple(sorted(touched)), tuple(identities)), where)
|
|
1174
1215
|
|
|
1175
1216
|
|
|
1176
|
-
def smk_apply(ReactionTemplate t, tuple molecules, bint automorphism_filter, object log, bint report
|
|
1217
|
+
def smk_apply(ReactionTemplate t, tuple molecules, bint automorphism_filter, object log, bint report,
|
|
1218
|
+
bint dedupe):
|
|
1177
1219
|
"""Validate eagerly, then hand back the generator.
|
|
1178
1220
|
|
|
1179
1221
|
The split is the point: a bad argument raises from the CALL, not from the first `next()`, so a
|
|
@@ -1189,10 +1231,11 @@ def smk_apply(ReactionTemplate t, tuple molecules, bint automorphism_filter, obj
|
|
|
1189
1231
|
raise TypeError('a template applies to molecules; this one was handed a %s' % type(m).__name__)
|
|
1190
1232
|
if not molecules:
|
|
1191
1233
|
raise ValueError('a template needs at least one molecule to apply to')
|
|
1192
|
-
return smk_enumerate(t, list(molecules), automorphism_filter, log, report)
|
|
1234
|
+
return smk_enumerate(t, list(molecules), automorphism_filter, log, report, dedupe)
|
|
1193
1235
|
|
|
1194
1236
|
|
|
1195
|
-
def smk_enumerate(ReactionTemplate t, list inputs, bint automorphism_filter, object log, bint report
|
|
1237
|
+
def smk_enumerate(ReactionTemplate t, list inputs, bint automorphism_filter, object log, bint report,
|
|
1238
|
+
bint dedupe):
|
|
1196
1239
|
"""Every distinct outcome of one template over one set of inputs.
|
|
1197
1240
|
|
|
1198
1241
|
The inputs are unioned into one working container, which is why an intramolecular template needs
|
|
@@ -1259,10 +1302,11 @@ def smk_enumerate(ReactionTemplate t, list inputs, bint automorphism_filter, obj
|
|
|
1259
1302
|
continue
|
|
1260
1303
|
if made is None:
|
|
1261
1304
|
continue
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1305
|
+
if dedupe:
|
|
1306
|
+
key = (<tuple> made)[1]
|
|
1307
|
+
if key in seen_keys:
|
|
1308
|
+
continue
|
|
1309
|
+
seen_keys.add(key)
|
|
1266
1310
|
if len(log) > start: # `_smiles_read.pxi:smi_one` states why the touch is guarded
|
|
1267
1311
|
(<object> (<tuple> made)[0]).log.absorb('react', log[start:])
|
|
1268
1312
|
if report:
|
|
@@ -1121,7 +1121,8 @@ cdef class ReactionTemplate:
|
|
|
1121
1121
|
def __repr__(self):
|
|
1122
1122
|
return 'read_smirks(%r)' % self.smirks
|
|
1123
1123
|
|
|
1124
|
-
def __call__(self, *molecules, bint automorphism_filter=True, log=None, bint report=False
|
|
1124
|
+
def __call__(self, *molecules, bint automorphism_filter=True, log=None, bint report=False,
|
|
1125
|
+
bint dedupe=True):
|
|
1125
1126
|
"""Apply this template to one molecule or to several, yielding one `ReactionContainer` per
|
|
1126
1127
|
distinct outcome.
|
|
1127
1128
|
|
|
@@ -1158,9 +1159,15 @@ cdef class ReactionTemplate:
|
|
|
1158
1159
|
reaction alone. The ids are the yielded products' own, and they survive `copy()` and `split()`,
|
|
1159
1160
|
so a caller that must edit "the atom the product side called :1" can reach it -- by map number,
|
|
1160
1161
|
the template's `:N` space, which is still not the reaction's imposed mapping.
|
|
1162
|
+
|
|
1163
|
+
`dedupe=False` yields one outcome per embedding, including outcomes that build the same
|
|
1164
|
+
structure from different atoms: `C1CNCCN1` cut at either nitrogen. A caller that filters
|
|
1165
|
+
outcomes by atom id needs every one, since the default keeps only the first of each structure.
|
|
1166
|
+
`automorphism_filter` is a different collapse -- over the template's own symmetry, not the
|
|
1167
|
+
outcome's.
|
|
1161
1168
|
"""
|
|
1162
1169
|
return smk_apply(self, molecules, automorphism_filter,
|
|
1163
|
-
log if log is not None else [], report)
|
|
1170
|
+
log if log is not None else [], report, dedupe)
|
|
1164
1171
|
|
|
1165
1172
|
|
|
1166
1173
|
def read_smirks(text, log=None, *, rule_id=None):
|