chython 3.3__tar.gz → 3.3.2__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/chython.egg-info → chython-3.3.2}/PKG-INFO +1 -1
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_salts.py +49 -3
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_tables.py +14 -9
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/salts.tsv +22 -7
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_salts.py +206 -5
- {chython-3.3 → chython-3.3.2}/chython/reactions/_enumerate.py +68 -13
- {chython-3.3 → chython-3.3.2}/chython/reactions/_reconstruct.py +136 -39
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_reconstruct.py +118 -0
- {chython-3.3 → chython-3.3.2/chython.egg-info}/PKG-INFO +1 -1
- {chython-3.3 → chython-3.3.2}/pyproject.toml +1 -1
- {chython-3.3 → chython-3.3.2}/LICENSE +0 -0
- {chython-3.3 → chython-3.3.2}/MANIFEST.in +0 -0
- {chython-3.3 → chython-3.3.2}/README.md +0 -0
- {chython-3.3 → chython-3.3.2}/build_inchi.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/_functions.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_abbreviations.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_canonicalize.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_counts.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_crippen.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_hydrogens.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_implicit.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_isomers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_kekule_form.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_maccs.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_organometallics.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_perceive.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_pharmacophore.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_protomers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_qed.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_residues.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_resonance.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_saturate.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_smarts.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_standardize.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/_tpsa.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/abbreviations.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/acids.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/covalent_radii.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/crippen.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/hbond.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/maccs.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/maccs_corpus.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/pharmacophore.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/qed_alerts.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/residues.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/resonance.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/rotatable.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/standardize_groups.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/standardize_metals.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/sybyl_types.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/tables/tpsa.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/_oracle.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/gen_standardize_rules.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_abbreviations.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_acids_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_canonicalize.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_counts.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_covalent_radii_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_crippen.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_crippen_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_dependency_direction.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_featurizer_injection.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_featurizer_tables_lazy.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_isomers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_kekule_form.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_maccs.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_maccs_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_maccs_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_organometallics.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_perceive.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_pharmacophore.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_protomers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_qed.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_qed_alerts_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_reaction_hydrogen_repair.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_reaction_passes.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_residues.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_resonance.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_resonance_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_saturate.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_smarts.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_standardize_differential.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_standardize_groups_port.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_standardize_overvalent_nitrogen.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_standardize_rules_examples.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_standardize_rules_merges.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_standardize_rules_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_thiele_is_single_purpose.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_tpsa.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_tpsa_tsv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_valence_report.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/chemistry/test/test_z_translation.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/RULES.md +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_canonical.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_cip.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_core.pyx +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_descriptors.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_elements.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_facade.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_features.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_fingerprints.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_hydrogens.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_inchi.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_isomorphism.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_kekule.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_log.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_ml.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_molecule_arena.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_molecule_container.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_molecule_topology.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_molecule_views.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_morgan.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_pach.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_pach3.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_query_arena.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_query_boxes.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_query_container.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_query_seal.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_reaction_passes.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_rings.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_smarts_read.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_smiles_read.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_smiles_write.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_smirks_patch.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_smirks_read.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_sssr.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_stereo.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_thiele.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/_valence.pxi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/elements.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/isotopes.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/reaction.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/arena_v4_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/bench_ml.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/chytorch_oracle.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/gen_element_tables.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/gen_modeling_view_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/gen_pach3_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/gen_reaction_pach_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/gen_v3_fixtures.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/gen_v4_fixtures.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/gen_valence_rules.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/modeling_view_corpus.json.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/modeling_view_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/oracle.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach3_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach_bond_group_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach_v0_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach_v0_native_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach_v2_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach_v3_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/pach_v4_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/reaction_pach_corpus.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/reaction_pach_v2_corpus.bin.gz +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_aggregates.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_alternative_spellings.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_apply_scratch_probe.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_arena_f60.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_arena_identity.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_arena_v3_compat.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_arena_v4_compat.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_aromatic_storage.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_canonical.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_canonical_mirror.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_cip_assign.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_cip_cases.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_cip_digraph.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_cip_ranking.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_cip_storage.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_clean_isotopes_and_coordinate_bonds.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_clean_stereo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_conformers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_container_log.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_copy_caches.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_derive.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_descriptors.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_element_tables.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_enrich.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_facade.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_features.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_featurizer_injection.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_fingerprints.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_geometry.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_h_unknown.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_hydrogens.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_inchi.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_interop_injection.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_isomorphism.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_kekule.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_log.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_magic.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_meta.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_ml_encoding.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_ml_reaction_transition.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_ml_state_view.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_ml_transition_view.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_ml_unpack_differential.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_modeling_view_frozen.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_molecule.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_morgan.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_no_chython_two_imports.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_oracle.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_pach.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_pach3.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_pack.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_query.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_r_edit.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_r_query.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_r_semantics.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_r_serialisation.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_r_smirks.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_r_storage.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_reaction_container.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_reaction_identity.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_reaction_pach.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_reaction_passes.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_reaction_patch_stereo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_reaction_smiles.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_rings.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_rings_c60.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_set_element.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_sgroups.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smarts_read.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_r.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_read.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_roundtrip.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write_aromatic.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write_cis_trans.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write_detached.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write_differential.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write_h_unknown.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write_stereo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smiles_write_sticky.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smirks_filter.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smirks_patch.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smirks_read.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smirks_report.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_smirks_stereo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_acceptance.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_anchor_identity.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_group_storage.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_owners.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_parity.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_perception.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_query.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_units.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_stereo_v2_differential.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_structure.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_thiele.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_title.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_topology.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_union_stereo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_valence.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/test_view_surface.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/v3_fixtures.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/test/v4_fixtures.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/valence_rules.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/core/wedge.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/_config.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/_hooks.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/bonds.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/colorbar.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/colormap.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/field.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/figure.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/label.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/layout/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/layout/clean2d.js +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/layout/molecule.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/layout/reaction.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/metrics/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/metrics/helvetica.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/metrics/times.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/overlay.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/render/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/render/svg.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/scene.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/style.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_bonds.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_clean2d.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_colorbar.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_colormap.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_field.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_figure.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_label.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_metrics.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_overlay.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_package.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_r_atom.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_scene.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_style.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_svg.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_wedge_draw.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/test/test_x3dom.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/wedge.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/depict/x3dom.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/exceptions.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/_text.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/CTFILE.md +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_ctab.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_errors.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_facade.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_hydrogens.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_rdf.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_rxn.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_sdf.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_sgroup.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_stream.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_tokens.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_v2000.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/_v3000.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/conftest.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_aromatic.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_container_log.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_data_labels.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_facade.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_fidelity.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_hydrogens.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_r_atom.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_rdf.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_rxn.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_sdf.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_sgroup.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_stereo_label.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_stream.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_tokens.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_v2000.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_v3000.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/ctfile/test/test_wedge.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/mol2.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/pdb/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/pdb/_builder.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/pdb/_legacy.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/pdb/_mmcif.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/pdb/_records.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/pdb/_star.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/conftest.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/oracles.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_conformance.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_done_when.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_isolation.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_log_prefix.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_mmcif.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_mol2.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_no_review_bookkeeping.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_oracles.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_pdb.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_pdb_builder.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_perceive_agreement.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_read_only_facades.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_xyz.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/test/test_xyz_builder.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/_cml.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/_dialect.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/_errors.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/_facade.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/_mrv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/_tree.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/conftest.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_cml.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_container_log.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_dialect.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_equivalence.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_facade.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_mrv.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_mrv_census.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xml/test/test_tree.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/formats/xyz.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_cdk.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_cdpkit.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_indigo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_iupac.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_java.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_openbabel.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_pandas.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_rdkit.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_records.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/_stereo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/config.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/conformers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/conftest.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_cdk.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_cdpkit.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_config.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_conformers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_coordinate_honesty.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_dispatch.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_indigo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_iupac.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_log_delivery.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_openbabel.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_pandas.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_rdkit.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_stereo.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/interop/test/test_v2_oracle.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/_numbering.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/_stickers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/_tables.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/attention/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/attention/_assign.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/attention/_encode.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/attention/_session.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/tables/functional.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/tables/protective.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/tables/reactions.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/tables/roles.tsv +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/_frozen_ids.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/gen_corpus_glossary.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/golden_subset.smi +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_attention.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_attention_assign.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_attention_encode.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_attention_isolation.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_corpus_glossary.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_dependency_direction.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_enumerate.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_functional.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_id_stability.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_numbering.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_probes.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_protective.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_roles.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_stickers.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/reactions/test/test_tables.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/__init__.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_code_hygiene.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_container_methods.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_doc_figures.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_doc_references.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_doc_samples.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_facade_names.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_hydrogen_parity.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_libinchi_staging.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_log_records.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_optional_numpy.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_packaging.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_performance.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_r_atom_integration.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_release_build.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_stereo_bluebook.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_v2_boundary.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython/test/test_writer_posture.py +0 -0
- {chython-3.3 → chython-3.3.2}/chython.egg-info/SOURCES.txt +0 -0
- {chython-3.3 → chython-3.3.2}/chython.egg-info/dependency_links.txt +0 -0
- {chython-3.3 → chython-3.3.2}/chython.egg-info/requires.txt +0 -0
- {chython-3.3 → chython-3.3.2}/chython.egg-info/top_level.txt +0 -0
- {chython-3.3 → chython-3.3.2}/setup.cfg +0 -0
- {chython-3.3 → chython-3.3.2}/setup.py +0 -0
|
@@ -397,9 +397,12 @@ def _tags(build: list[dict], roles: list[str], guarded: bool) -> frozenset[str]:
|
|
|
397
397
|
if len(formers) > 1:
|
|
398
398
|
tags.add('competing_formers')
|
|
399
399
|
if len(build) > 1:
|
|
400
|
-
|
|
400
|
+
# A SOLVENT TAG NEEDS THE COMPONENT DRAWN NEUTRAL. `klass` is read off the neutralized probe,
|
|
401
|
+
# which is right for identity -- a conjugate is not a row -- and wrong for this question: the
|
|
402
|
+
# hydroxide of `[OH-].[Na+]` keys as water and is the counterion, not solvent of crystallization.
|
|
403
|
+
if any(row['klass'] == 'water' and not row['charge'] for row in build):
|
|
401
404
|
tags.add('hydrate')
|
|
402
|
-
if any(row['klass'] in _SOLVENT_CLASSES - {'water'} for row in build):
|
|
405
|
+
if any(row['klass'] in _SOLVENT_CLASSES - {'water'} and not row['charge'] for row in build):
|
|
403
406
|
tags.add('solvate')
|
|
404
407
|
|
|
405
408
|
if any(row['residual_charge'] for row, role in zip(build, roles) if role == 'parent'):
|
|
@@ -545,6 +548,34 @@ _RULE_PROTON = 'salts:charge-transfer'
|
|
|
545
548
|
#: charges -- and 0 is the f block's unknown, which refuses for the same reason.
|
|
546
549
|
_DETERMINATE_VALENCE = 3
|
|
547
550
|
|
|
551
|
+
#: The s block, as `valence_electrons`: group 1 answers 1 and group 2 answers 2, while the d block answers
|
|
552
|
+
#: its full count (Fe 8, Ag 11, Zn 12) and aluminium 3, so these two values name no other metal.
|
|
553
|
+
_REDUCING_ELECTRONS = frozenset({1, 2})
|
|
554
|
+
|
|
555
|
+
#: Beryllium, the one s-block metal that does not reduce water or an alcohol, excluded by element.
|
|
556
|
+
_BERYLLIUM = 4
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _hydride(molecule: MoleculeContainer, n: int, log: MutableSequence) -> bool:
|
|
560
|
+
"""Does charging free metal `n` mean losing a hydrogen it was drawn with? Logs the refusal if so.
|
|
561
|
+
|
|
562
|
+
`standardize()` recomputes the implicit hydrogen count of every atom this stage writes, and the count
|
|
563
|
+
an ionic metal derives is 0, so writing a charge here is what would delete the hydride. `[NaH].CCO` is
|
|
564
|
+
sodium hydride in ethanol AS DRAWN; sodium ethoxide and H2 is a reaction, which this pass does not run.
|
|
565
|
+
The same guard `split_salts` applies before cutting a bond, for the same reason.
|
|
566
|
+
"""
|
|
567
|
+
hydrogens = molecule.implicit_h_of(n)
|
|
568
|
+
if hydrogens is None:
|
|
569
|
+
count = 'an unknown number of'
|
|
570
|
+
elif hydrogens:
|
|
571
|
+
count = str(hydrogens)
|
|
572
|
+
else:
|
|
573
|
+
return False
|
|
574
|
+
log.append(LogRecord(_RULE_METAL, (n,),
|
|
575
|
+
f'atom {n} is a free metal carrying {count} implicit hydrogen(s); charging it '
|
|
576
|
+
f'would drop them, so the whole record is left as drawn', REFUSED))
|
|
577
|
+
return True
|
|
578
|
+
|
|
548
579
|
|
|
549
580
|
def fix_salt_charges(molecule: MoleculeContainer, log: MutableSequence) -> set[int]:
|
|
550
581
|
"""Move the charges a salt was drawn without: `CC(=O)O.[Na]` is `CC(=O)[O-].[Na+]`. Written ids.
|
|
@@ -563,6 +594,11 @@ def fix_salt_charges(molecule: MoleculeContainer, log: MutableSequence) -> set[i
|
|
|
563
594
|
ALL-OR-NOTHING PER RECORD, not per site, and both steps are planned entirely in reads: step 1 charges a
|
|
564
595
|
metal on the strength of a balance step 2 may refuse. `[Na]` alone is that case, and it comes back as
|
|
565
596
|
`[Na]`.
|
|
597
|
+
|
|
598
|
+
WATER AND AN ALCOHOL ARE SITES ONLY FOR AN S-BLOCK METAL. `CCO.[Na]` is sodium ethoxide and
|
|
599
|
+
`CCO.[Zn]` is left as drawn: the table's `metal_protic` rows join the rung ladder only when every free
|
|
600
|
+
metal in the record is group 1 or group 2 and none is beryllium. They sit on rung 6, below every acid,
|
|
601
|
+
so a record holding both an acid and an alcohol spends its metal on the acid.
|
|
566
602
|
"""
|
|
567
603
|
metals = [atom.n for atom in molecule.atoms() if atom.is_metal and not atom.degree]
|
|
568
604
|
if not metals:
|
|
@@ -574,6 +610,8 @@ def fix_salt_charges(molecule: MoleculeContainer, log: MutableSequence) -> set[i
|
|
|
574
610
|
if not any(charges.values()):
|
|
575
611
|
# step 1: no metal carries a charge, so the drawing states nothing to override
|
|
576
612
|
for n in metals:
|
|
613
|
+
if _hydride(molecule, n, log):
|
|
614
|
+
return set()
|
|
577
615
|
electrons = molecule.atom(n).valence_electrons
|
|
578
616
|
if not 0 < electrons <= _DETERMINATE_VALENCE:
|
|
579
617
|
if electrons == 0:
|
|
@@ -600,9 +638,14 @@ def fix_salt_charges(molecule: MoleculeContainer, log: MutableSequence) -> set[i
|
|
|
600
638
|
|
|
601
639
|
sites: list[tuple[int, int, int, str]] = []
|
|
602
640
|
if total > anions:
|
|
641
|
+
by_klass = salts_rows_by_klass()
|
|
642
|
+
rows = by_klass['protic_acid']
|
|
643
|
+
if all(molecule.atom(n).valence_electrons in _REDUCING_ELECTRONS and
|
|
644
|
+
molecule.element_of(n) != _BERYLLIUM for n in metals):
|
|
645
|
+
rows += by_klass['metal_protic']
|
|
603
646
|
ranks = molecule.atoms_order
|
|
604
647
|
best: dict[int, tuple[int, str]] = {}
|
|
605
|
-
for row in
|
|
648
|
+
for row in rows:
|
|
606
649
|
for mapping in row.query.get_mapping(molecule):
|
|
607
650
|
n = mapping[row.anchor]
|
|
608
651
|
if molecule.charge_of(n) or (n in best and best[n][0] <= row.order):
|
|
@@ -646,6 +689,9 @@ def fix_salt_charges(molecule: MoleculeContainer, log: MutableSequence) -> set[i
|
|
|
646
689
|
group_numbers[n] = electrons
|
|
647
690
|
held = {n: planned.get(n, charges[n]) for n in metals}
|
|
648
691
|
raises = {n: group_numbers[n] for n in metals if held[n] < group_numbers[n]}
|
|
692
|
+
for n in raises:
|
|
693
|
+
if _hydride(molecule, n, log):
|
|
694
|
+
return set()
|
|
649
695
|
reached = sum(raises.get(n, held[n]) for n in metals)
|
|
650
696
|
if reached != anions:
|
|
651
697
|
log.append(LogRecord(_RULE_METAL, tuple(metals),
|
|
@@ -303,14 +303,18 @@ SALT_MATCHES = ('embed', 'whole')
|
|
|
303
303
|
|
|
304
304
|
#: The closed class vocabulary. A row naming anything else is a load-time error, because a class the
|
|
305
305
|
#: passes never ask for is a row that silently does nothing.
|
|
306
|
-
SALT_CLASSES = ('metal_cation', 'charge_acceptor', 'protic_acid',
|
|
306
|
+
SALT_CLASSES = ('metal_cation', 'charge_acceptor', 'protic_acid', 'metal_protic',
|
|
307
307
|
'mineral_acid', 'sulfonic_acid', 'short_carboxylic_acid', 'carboxylic_acid',
|
|
308
308
|
'aromatic_acid', 'fatty_acid', 'amino_acid', 'amine_base', 'quaternary_ammonium',
|
|
309
309
|
'water', 'alcohol', 'hydrocarbon', 'halo_solvent', 'aprotic_solvent')
|
|
310
310
|
|
|
311
311
|
#: Which classes are matched by embedding. Pinned here rather than trusted from the row so a typo in
|
|
312
312
|
#: `match` is a load-time error instead of a row that never fires.
|
|
313
|
-
_EMBED_CLASSES = frozenset({'metal_cation', 'charge_acceptor', 'protic_acid'})
|
|
313
|
+
_EMBED_CLASSES = frozenset({'metal_cation', 'charge_acceptor', 'protic_acid', 'metal_protic'})
|
|
314
|
+
|
|
315
|
+
#: Which classes carry an acidity rung. Both, and on one scale: `fix_salt_charges` ranks the two pools
|
|
316
|
+
#: together, so a rung that meant something different per class would decide nothing.
|
|
317
|
+
_RUNGED_CLASSES = frozenset({'protic_acid', 'metal_protic'})
|
|
314
318
|
|
|
315
319
|
|
|
316
320
|
class SaltRow(NamedTuple):
|
|
@@ -319,9 +323,10 @@ class SaltRow(NamedTuple):
|
|
|
319
323
|
Exactly one of `query` (an `embed` row) and `key` (a `whole` row) is set. `anchor` is the stable id
|
|
320
324
|
of the query atom mapped `:1` -- the subject, since a row may name a whole neighbourhood. `key` is
|
|
321
325
|
`format(species, '!s')`, the stereo-free canonical SMILES a species row is matched by. `charges` is
|
|
322
|
-
the set of charges a cation may end up with, `metal_cation` only. `order` is the acidity rung
|
|
323
|
-
`protic_acid`
|
|
324
|
-
count, `0` for an `embed` row -- a size judgement the caller would
|
|
326
|
+
the set of charges a cation may end up with, `metal_cation` only. `order` is the acidity rung on one
|
|
327
|
+
scale over `protic_acid` and `metal_protic`, low being more acidic; equal values are tied.
|
|
328
|
+
`heavy_atoms` is the species' atom count, `0` for an `embed` row -- a size judgement the caller would
|
|
329
|
+
otherwise re-measure per component.
|
|
325
330
|
"""
|
|
326
331
|
id: str
|
|
327
332
|
match: str
|
|
@@ -393,11 +398,11 @@ def _compile_salts() -> tuple[SaltRow, ...]:
|
|
|
393
398
|
|
|
394
399
|
if row['order'] == '-':
|
|
395
400
|
order = 0
|
|
396
|
-
if klass
|
|
397
|
-
raise ValueError(f'{row_id}: a
|
|
398
|
-
'
|
|
401
|
+
if klass in _RUNGED_CLASSES:
|
|
402
|
+
raise ValueError(f'{row_id}: a {klass} row must state its acidity rung; without one the '
|
|
403
|
+
'proton fix_salt_charges() moves would be chosen by file position')
|
|
399
404
|
else:
|
|
400
|
-
if klass
|
|
405
|
+
if klass not in _RUNGED_CLASSES:
|
|
401
406
|
raise ValueError(f'{row_id}: order is the acidity rung and means nothing for a {klass} '
|
|
402
407
|
'row; write `-`')
|
|
403
408
|
order = int(row['order'])
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
# that one atom -- a lone [Na+]. All 93 metals `[M]` accepts qualify.
|
|
18
18
|
# charge_acceptor embed split_salts (the atom that keeps the electron pair)
|
|
19
19
|
# protic_acid embed decompose_salts (the acid_salt tag), fix_salt_charges (the proton source)
|
|
20
|
+
# metal_protic embed fix_salt_charges ONLY, and only when every free metal is s-block -- a site a
|
|
21
|
+
# reducing metal opens and no acid table calls an acid
|
|
20
22
|
# the fourteen species classes whole decompose_salts
|
|
21
23
|
#
|
|
22
24
|
# A SPECIES ROW IS COMPARED BY `format(component, '!s')` -- the canonical SMILES with stereo disabled,
|
|
@@ -38,14 +40,19 @@
|
|
|
38
40
|
# at charge+k; the row lists every value that is allowed to be, and the whole atom is refused before
|
|
39
41
|
# anything is cut -- all-or-nothing, never a partially split molecule.
|
|
40
42
|
#
|
|
41
|
-
# `order` IS THE ACIDITY RUNG AND ONLY A `protic_acid` ROW HAS ONE. Low is more acidic;
|
|
42
|
-
# value are tied, and a tie is resolved by canonical order and logged rather than by file
|
|
43
|
+
# `order` IS THE ACIDITY RUNG AND ONLY A `protic_acid` OR `metal_protic` ROW HAS ONE. Low is more acidic;
|
|
44
|
+
# rows sharing a value are tied, and a tie is resolved by canonical order and logged rather than by file
|
|
45
|
+
# position. ONE SCALE FOR BOTH CLASSES, because `fix_salt_charges` ranks them in one pool: a carboxylic
|
|
46
|
+
# acid beside an alcohol and one sodium gives the carboxylate, rung 4 against rung 6.
|
|
43
47
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
48
|
+
# WATER AND ALCOHOL ARE NOT ACIDS, THEY ARE WHAT A FREE S-BLOCK METAL REDUCES. `CCN.CCO` is a mixture and
|
|
49
|
+
# `CCO` is no acid site, so `decompose_salts` reads neither as a salt and a hydrate stays droppable;
|
|
50
|
+
# `CCO.[Na]` is sodium ethoxide, because sodium does not stand beside a hydroxyl. That distinction is the
|
|
51
|
+
# whole of `metal_protic`. Beryllium is the one s-block metal outside it, not reducing water.
|
|
52
|
+
#
|
|
53
|
+
# EVERY AZOLE BUT TETRAZOLE IS ABSENT. Below sulfonamide's rung an N-H needs a hydride or an
|
|
54
|
+
# organolithium, and a metal drawn beside one is reagent chemistry. An acid with no row refuses, logs, and
|
|
55
|
+
# surfaces as a `charges_undrawn` tag -- the count of those refusals is what would justify a row.
|
|
49
56
|
#
|
|
50
57
|
# There is no `elements` column: `pattern` already names the elements a row applies to, and `keep=['Na']` is
|
|
51
58
|
# resolved against the MOLECULE's atoms.
|
|
@@ -58,18 +65,26 @@ salts:thiophosphate embed charge_acceptor [O,S,Se;D2;z1:1]-[P]=[S,Se] - - thio-
|
|
|
58
65
|
salts:phenolate embed charge_acceptor [O,S,Se;D2;z1:1]-[C,N;a] - - phenolate, thiophenolate, N-hydroxy azole
|
|
59
66
|
salts:nitrate embed charge_acceptor [O;D2;z1:1]-[N+](-[O-])=O - - nitrate, drawn charge-separated as standardize() leaves it
|
|
60
67
|
salts:halide-hydroxide embed charge_acceptor [O,F,Cl,Br,I;D1;z1:1] - - a terminal halogen or oxygen: halide, hydroxide, alkoxide
|
|
68
|
+
salts:alkoxide embed charge_acceptor [O,S,Se;D2;z1:1](-[C,Si;z1])-[M] - - alkoxide, thiolate, silanolate drawn with the metal bonded. The `[M]` is the row: without it the pattern is true of diethyl ether
|
|
61
69
|
# --- acidic sites: which hydrogen a metal may take, most acidic first ----------------------------- #
|
|
62
70
|
salts:sulfonic-oh embed protic_acid [O;D1;z1;h1:1]-[S;z5](=O)=O - 1 sulfonic and sulfuric acid
|
|
63
71
|
salts:oxo-acid-oh embed protic_acid [O;D1;z1;h1:1]-[N,P,Cl,Br,I]=O - 2 phosphoric, phosphonic, nitrous, perchloric
|
|
64
72
|
salts:nitric-oh embed protic_acid [O;D1;z1;h1:1]-[N+,P+,Cl+,Br+,I+]-[O-] - 2 nitric, drawn charge-separated as standardize() leaves it
|
|
65
73
|
salts:acyl-sulfonamide embed protic_acid [N;D2;z1;h1:1](-[C;z2]=O)-[S;z5](=O)=O - 2 acyl sulfonamide: saccharin
|
|
74
|
+
salts:sulfinic-oh embed protic_acid [O;D1;z1;h1:1]-[S;z2]=O - 2 sulfinic acid. The sulfonic row above is `z5`, which this S is not
|
|
75
|
+
salts:thio-acid-sh embed protic_acid [S;D1;z1;h1:1]-[C;z2]=[O,S] - 2 thioacid, dithioacid, xanthic acid, dithiocarbamic acid. Thioacetic acid is a rung optimistic here, which costs a tie broken the wrong way when one component holds two sites
|
|
66
76
|
salts:hydrogen-halide embed protic_acid [F,Cl,Br,I;D0;h1:1] - 3 HF HCl HBr HI: the unbonded halogen holding its hydrogen
|
|
67
77
|
salts:carboxylic-oh embed protic_acid [O;D1;z1;h1:1]-[C;z2]=O - 4 carboxylic acid
|
|
68
78
|
salts:tetrazole-1h embed protic_acid [nH;r5:1]:1:n:n:n:c:1 - 4 tetrazole 1H, the carboxylic-acid bioisostere
|
|
69
79
|
salts:tetrazole-2h embed protic_acid n:1:[nH;r5:1]:n:n:c:1 - 4 tetrazole 2H: standardize() converges no mobile hydrogen, so both tautomers get a row
|
|
80
|
+
salts:thiophenol-sh embed protic_acid [S;D1;z1;h1:1]-[C,N;a] - 4 thiophenol
|
|
70
81
|
salts:phenol-oh embed protic_acid [O;D1;z1;h1:1]-[C,N;a] - 5 phenol, N-hydroxy azole
|
|
71
82
|
salts:imide embed protic_acid [N;D2;z1;h1:1](-[C;z2]=O)-[C;z2]=O - 5 imide: phenytoin, succinimide
|
|
72
83
|
salts:sulfonamide embed protic_acid [N;z1;h1,h2:1]-[S;z5](=O)=O - 5 sulfonamide, tosylamide
|
|
84
|
+
salts:thiol-sh embed protic_acid [S;D1;z1;h1:1]-[C;z1] - 5 thiol
|
|
85
|
+
# --- sites only an s-block metal opens, on the same rung scale ------------------------------------ #
|
|
86
|
+
salts:water-oh embed metal_protic [O;D0;z1;h2:1] - 6 water
|
|
87
|
+
salts:alcohol-oh embed metal_protic [O;D1;z1;h1:1]-[C,Si;z1] - 6 alcohol, silanol. `D1` and `z1`, so a phenol, a carboxylic acid and an ether are each somebody else's row or nobody's
|
|
73
88
|
# --- species: the acids --------------------------------------------------------------------------- #
|
|
74
89
|
salts:hf whole mineral_acid F - - hydrofluoric
|
|
75
90
|
salts:hcl whole mineral_acid Cl - - hydrochloric
|
|
@@ -57,6 +57,24 @@ def test_the_metals_chython_two_left_alone_now_split():
|
|
|
57
57
|
assert format(m) == expect, s
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
def test_an_alkoxide_drawn_with_the_metal_bonded_splits():
|
|
61
|
+
"""The drawn O-metal bond states the alkoxide, so there is nothing left to disambiguate and
|
|
62
|
+
`salts:alkoxide` reads it. Silicon and boron are outside `[M]`, so a silyl ether is untouched."""
|
|
63
|
+
for s, expect in [('CCO[Na]', 'C(C)[O-].[Na+]'), # sodium ethoxide
|
|
64
|
+
('CC(C)(C)O[K]', 'C(C)([O-])(C)C.[K+]'), # potassium tert-butoxide
|
|
65
|
+
('CCS[K]', 'C(C)[S-].[K+]'), # potassium ethanethiolate
|
|
66
|
+
('C[Si](C)(C)O[Na]', 'C[Si]([O-])(C)C.[Na+]'), # sodium trimethylsilanolate
|
|
67
|
+
('CCO[Mg]OCC', 'C(C)[O-].C(C)[O-].[Mg+2]')]:
|
|
68
|
+
m = smiles(s)
|
|
69
|
+
assert split_salts(m) is True, s
|
|
70
|
+
assert format(m) == expect, s
|
|
71
|
+
for s in ['CCOCC', 'CSC', 'CCO[Si](C)(C)C', 'CCOB(OCC)OCC']: # no metal, no acceptor
|
|
72
|
+
m = smiles(s)
|
|
73
|
+
before = m.canonical_bytes
|
|
74
|
+
assert split_salts(m) is False, s
|
|
75
|
+
assert m.canonical_bytes == before, s
|
|
76
|
+
|
|
77
|
+
|
|
60
78
|
def test_the_atom_count_never_changes():
|
|
61
79
|
"""The atom count is a contract of this pass; nothing in this module deletes a component."""
|
|
62
80
|
for s in ['CC(=O)O[Na]', 'CC(=O)O[Zn]OC(C)=O', '[Al](OC(C)=O)(OC(C)=O)OC(C)=O', '[Na][Cl]']:
|
|
@@ -627,7 +645,8 @@ def test_classes_are_grouped_and_none_is_missing():
|
|
|
627
645
|
assert set(grouped) == set(SALT_CLASSES)
|
|
628
646
|
assert sum(len(rows) for rows in grouped.values()) == len(salts_rows())
|
|
629
647
|
assert len(grouped['metal_cation']) == 1
|
|
630
|
-
assert len(grouped['charge_acceptor']) ==
|
|
648
|
+
assert len(grouped['charge_acceptor']) == 6
|
|
649
|
+
assert len(grouped['metal_protic']) == 2
|
|
631
650
|
assert len(grouped['water']) == 1
|
|
632
651
|
|
|
633
652
|
|
|
@@ -646,9 +665,10 @@ def test_only_the_metal_cation_row_carries_charges():
|
|
|
646
665
|
assert bool(row.charges) == (row.klass == 'metal_cation'), row.id
|
|
647
666
|
|
|
648
667
|
|
|
649
|
-
def
|
|
668
|
+
def test_only_a_runged_row_carries_an_order():
|
|
669
|
+
"""One rung scale over the two classes `fix_salt_charges` ranks together, and no rung anywhere else."""
|
|
650
670
|
for row in salts_rows():
|
|
651
|
-
assert bool(row.order) == (row.klass
|
|
671
|
+
assert bool(row.order) == (row.klass in ('protic_acid', 'metal_protic')), row.id
|
|
652
672
|
|
|
653
673
|
|
|
654
674
|
def test_the_metal_row_covers_every_metal_the_core_calls_one():
|
|
@@ -700,17 +720,30 @@ ACID_LADDER = (
|
|
|
700
720
|
('salts:oxo-acid-oh', 2, 'OP(=O)(O)O'),
|
|
701
721
|
('salts:nitric-oh', 2, 'O[N+](=O)[O-]'),
|
|
702
722
|
('salts:acyl-sulfonamide', 2, 'O=C1NS(=O)(=O)c2ccccc21'),
|
|
723
|
+
('salts:sulfinic-oh', 2, 'CS(=O)O'),
|
|
724
|
+
('salts:thio-acid-sh', 2, 'CCOC(=S)S'),
|
|
703
725
|
('salts:hydrogen-halide', 3, 'Cl'),
|
|
704
726
|
('salts:carboxylic-oh', 4, 'CC(=O)O'),
|
|
705
727
|
('salts:tetrazole-1h', 4, 'c1nnn[nH]1'),
|
|
706
728
|
('salts:tetrazole-2h', 4, 'c1nn[nH]n1'),
|
|
729
|
+
('salts:thiophenol-sh', 4, 'Sc1ccccc1'),
|
|
707
730
|
('salts:phenol-oh', 5, 'Oc1ccccc1'),
|
|
708
731
|
('salts:imide', 5, 'O=C1CCC(=O)N1'),
|
|
709
732
|
('salts:sulfonamide', 5, 'Cc1ccc(cc1)S(N)(=O)=O'),
|
|
733
|
+
('salts:thiol-sh', 5, 'CCS'),
|
|
710
734
|
)
|
|
711
735
|
|
|
712
|
-
#:
|
|
713
|
-
#:
|
|
736
|
+
#: Every `metal_protic` row, on the same rung scale and below every acid. A separate ladder because a
|
|
737
|
+
#: separate class: these fire for a free s-block metal and for nothing else in the library.
|
|
738
|
+
METAL_PROTIC_LADDER = (
|
|
739
|
+
('salts:water-oh', 6, 'O'),
|
|
740
|
+
('salts:alcohol-oh', 6, 'CCO'),
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
#: Nothing here is a salt-forming acid, and no `protic_acid` row may fire on any of it. Every azole people
|
|
744
|
+
#: draw is in the list: only tetrazole sits in the salt-forming range, so every other one is a refusal by
|
|
745
|
+
#: design. Water and the alcohols are here too -- they are `metal_protic`, which is a different question
|
|
746
|
+
#: and a different class, so a row of this table must not call either an acid.
|
|
714
747
|
NOT_ACIDS = (
|
|
715
748
|
'c1cc[nH]c1', 'c1cnc[nH]1', 'c1cn[nH]c1', 'c1cn[nH]n1', 'c1c[nH]nn1', 'c1nc[nH]n1',
|
|
716
749
|
'c1ccc2[nH]nnc2c1', 'c1ccc2[nH]ccc2c1', 'Cn1c(=O)c2[nH]cnc2n(C)c1=O',
|
|
@@ -718,6 +751,14 @@ NOT_ACIDS = (
|
|
|
718
751
|
'CCO', 'O', 'CO', 'CC(C)(C)O', 'OC1CCCCC1', 'OCC1OC(O)C(O)C(O)C1O',
|
|
719
752
|
'CC(C)=O', 'CS(C)=O', 'CC(=O)OC', 'COS(C)(=O)=O', 'C1CCOC1', 'c1ccncc1',
|
|
720
753
|
'c1ccccc1[N+](=O)[O-]', 'C[N+](C)(C)[O-]', 'Clc1ccccc1', 'CCCl',
|
|
754
|
+
'CSC', 'CCSSCC', 'CSc1ccccc1', 'CC(=O)SC',
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
#: What a `metal_protic` row must not fire on. Every other O-H the table already places -- a phenol and a
|
|
758
|
+
#: carboxylic acid are acids and rank above these -- plus the ethers, which hold no hydrogen at all.
|
|
759
|
+
NOT_METAL_PROTIC = (
|
|
760
|
+
'Oc1ccccc1', 'CC(=O)O', 'CS(=O)(=O)O', 'ON=O', 'CCOCC', 'C1CCOC1', 'COC', 'C[Si](C)(C)OC',
|
|
761
|
+
'CCS', 'CC(=O)OC', 'CC(C)=O', 'NO', 'OO',
|
|
721
762
|
)
|
|
722
763
|
|
|
723
764
|
|
|
@@ -746,6 +787,33 @@ def test_no_acid_row_fires_on_a_non_acid():
|
|
|
746
787
|
assert not fired, (spelling, fired)
|
|
747
788
|
|
|
748
789
|
|
|
790
|
+
def test_the_metal_protic_ladder_is_the_table():
|
|
791
|
+
rows = salts_rows_by_klass()['metal_protic']
|
|
792
|
+
assert [(row.id, row.order) for row in rows] == [(i, o) for i, o, _ in METAL_PROTIC_LADDER]
|
|
793
|
+
acids = salts_rows_by_klass()['protic_acid']
|
|
794
|
+
assert min(row.order for row in rows) > max(row.order for row in acids), \
|
|
795
|
+
'a metal_protic rung must sit below every acid: the metal spends itself on the acid first'
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def test_every_metal_protic_row_fires_on_its_compound():
|
|
799
|
+
rows = {row.id: row for row in salts_rows_by_klass()['metal_protic']}
|
|
800
|
+
for row_id, _, spelling in METAL_PROTIC_LADDER:
|
|
801
|
+
mol = smiles(spelling)
|
|
802
|
+
mol.standardize()
|
|
803
|
+
mol.thiele()
|
|
804
|
+
assert next(rows[row_id].query.get_mapping(mol), None) is not None, row_id
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def test_no_metal_protic_row_fires_on_an_acid_or_an_ether():
|
|
808
|
+
rows = salts_rows_by_klass()['metal_protic']
|
|
809
|
+
for spelling in NOT_METAL_PROTIC:
|
|
810
|
+
mol = smiles(spelling)
|
|
811
|
+
mol.standardize()
|
|
812
|
+
mol.thiele()
|
|
813
|
+
fired = [row.id for row in rows if next(row.query.get_mapping(mol), None) is not None]
|
|
814
|
+
assert not fired, (spelling, fired)
|
|
815
|
+
|
|
816
|
+
|
|
749
817
|
def test_a_nitrophenol_is_a_phenol_and_not_a_nitric_acid():
|
|
750
818
|
rows = {row.id: row for row in salts_rows_by_klass()['protic_acid']}
|
|
751
819
|
mol = smiles('c1cc(O)ccc1[N+](=O)[O-]')
|
|
@@ -765,6 +833,12 @@ TAGGINGS = (
|
|
|
765
833
|
('CC(=O)Oc1ccccc1C(=O)O.O', {'hydrate'}),
|
|
766
834
|
('CCN.Cl', {'acid_salt'}),
|
|
767
835
|
('c1ccncc1.OC(=O)C(F)(F)F', {'acid_salt'}),
|
|
836
|
+
('CCN.CCS', {'acid_salt'}), # a thiol is an acid site, so this is a salt
|
|
837
|
+
('CCN.Sc1ccccc1', {'acid_salt'}),
|
|
838
|
+
('CCN.CCOC(=S)S', {'acid_salt'}), # xanthic acid
|
|
839
|
+
('CCN.CS(=O)O', {'acid_salt'}), # sulfinic: the sulfonic row is z5, this S is z2
|
|
840
|
+
('CCN.CCO', {'solvate'}), # and an alcohol is still no acid site
|
|
841
|
+
('CCS.[Na].CCBr', {'metal_salt', 'acid_salt'}), # the thiol is what the lone sodium owes to
|
|
768
842
|
('CC(=O)[O-].[Na+]', {'metal_salt', 'ion_pair'}),
|
|
769
843
|
('CC(=O)O[Na]', {'metal_salt', 'ion_pair'}),
|
|
770
844
|
('CC(=O)O.[Na+]', {'metal_salt', 'charge_unbalanced'}),
|
|
@@ -807,6 +881,37 @@ def test_a_tag_does_not_flip_when_classes_widen():
|
|
|
807
881
|
assert 'solvate' in narrow and narrow == wide
|
|
808
882
|
|
|
809
883
|
|
|
884
|
+
def test_a_solvent_tag_needs_the_component_drawn_neutral():
|
|
885
|
+
"""`klass` is read off the neutralized probe and the solvent tags are not.
|
|
886
|
+
|
|
887
|
+
Sodium hydroxide keys as water, because a conjugate is not a row and that is what makes the inventory
|
|
888
|
+
small; it is the counterion all the same, and `hydrate` is a claim about solvent of crystallization.
|
|
889
|
+
The roles are untouched -- both components stay parents and nothing is stripped.
|
|
890
|
+
"""
|
|
891
|
+
for spelling in ('[OH-].[Na+]', 'CC[O-].[Na+]', 'CC(C)(C)[O-].[K+]', 'C[O-].[Na+]'):
|
|
892
|
+
answer = smiles(spelling).decompose_salts()
|
|
893
|
+
assert 'hydrate' not in answer.tags and 'solvate' not in answer.tags, spelling
|
|
894
|
+
assert {'metal_salt', 'ion_pair'} <= answer.tags, spelling
|
|
895
|
+
assert len(answer.parents) == 2 and not answer.stabilizers, spelling
|
|
896
|
+
# and the drawn-neutral water is still the hydrate it was
|
|
897
|
+
for spelling in ('CC(=O)Oc1ccccc1C(=O)O.O', 'CCCS([O-])(=O)=O.[Na+].O', 'O.[Na]'):
|
|
898
|
+
assert 'hydrate' in smiles(spelling).decompose_salts().tags, spelling
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def test_an_attachment_point_is_a_parent_like_any_other():
|
|
902
|
+
"""A supported-synthesis drawing keys and desalts like anything else: the `[R]` is skeleton.
|
|
903
|
+
|
|
904
|
+
`[R]` alone is `single` and no more -- it is neither a lone metal nor a recognized solvent, so the
|
|
905
|
+
parent guard has nothing to promote back.
|
|
906
|
+
"""
|
|
907
|
+
answer = smiles('[R]CCN.Cl').decompose_salts()
|
|
908
|
+
assert [format(row.molecule, '!s') for row in answer.parents] == ['C(N)C[R]']
|
|
909
|
+
assert [row.klass for row in answer.stabilizers] == ['mineral_acid']
|
|
910
|
+
assert answer.tags == frozenset({'acid_salt'})
|
|
911
|
+
assert 'hydrate' in smiles('[R]CCN.O').decompose_salts().tags
|
|
912
|
+
assert smiles('[R]').decompose_salts().tags == frozenset({'single'})
|
|
913
|
+
|
|
914
|
+
|
|
810
915
|
def test_the_three_records_the_default_keeps_whole():
|
|
811
916
|
api = 'CC(=O)Oc1ccccc1C(=O)O' # aspirin: a carboxylic acid site
|
|
812
917
|
for record, tag in ((f'{api}.Cc1ccccc1', 'solvate'),
|
|
@@ -928,6 +1033,25 @@ def test_canonicalize_does_not_undo_the_repair():
|
|
|
928
1033
|
assert mol.canonical_bytes == smiles('CC(=O)[O-].[Na+]').canonical_bytes
|
|
929
1034
|
|
|
930
1035
|
|
|
1036
|
+
def test_a_free_s_block_metal_beside_water_or_an_alcohol_is_the_alcoholate():
|
|
1037
|
+
"""Through `standardize()` and through `canonicalize()`, which reaches this stage by running it: a
|
|
1038
|
+
hydroxide's own hydrogen comes back from `calc_implicit` like any other."""
|
|
1039
|
+
for drawn, after in (('CCO.[Na]', 'CC[O-].[Na+]'),
|
|
1040
|
+
('O.[Na]', '[OH-].[Na+]'),
|
|
1041
|
+
('CC(C)(C)O.[K]', 'CC(C)(C)[O-].[K+]'),
|
|
1042
|
+
('CCS.[Na]', 'CC[S-].[Na+]'),
|
|
1043
|
+
('C[Si](C)(C)O.[Na]', 'C[Si](C)(C)[O-].[Na+]'),
|
|
1044
|
+
('CCO.[Zn]', 'CCO.[Zn]')): # not the s block: left as drawn
|
|
1045
|
+
for pipeline in ('standardize', 'canonicalize'):
|
|
1046
|
+
mol = smiles(drawn)
|
|
1047
|
+
getattr(mol, pipeline)()
|
|
1048
|
+
assert mol.canonical_bytes == smiles(after).canonical_bytes, (drawn, pipeline)
|
|
1049
|
+
assert not mol.check_valence(), (drawn, pipeline)
|
|
1050
|
+
mol = smiles('O.[Na]')
|
|
1051
|
+
mol.standardize()
|
|
1052
|
+
assert next(atom for atom in mol.atoms() if atom.element == 8).total_h == 1
|
|
1053
|
+
|
|
1054
|
+
|
|
931
1055
|
def test_the_log_carries_one_stage_name_for_one_call():
|
|
932
1056
|
mol = smiles('CC(=O)O.[Na]')
|
|
933
1057
|
mol.standardize()
|
|
@@ -949,6 +1073,14 @@ CHARGE_FIXES = (
|
|
|
949
1073
|
('CS(=O)(=O)O.[Na]', 'CS(=O)(=O)[O-].[Na+]'), # rung 1
|
|
950
1074
|
('Cl.[Na]', '[Cl-].[Na+]'), # rung 3
|
|
951
1075
|
('Oc1ccccc1.[Na]', '[O-]c1ccccc1.[Na+]'), # rung 5
|
|
1076
|
+
('CCS.[Na]', 'CC[S-].[Na+]'), # rung 5: a thiol, for any metal at all
|
|
1077
|
+
('CCO.[Na]', 'CC[O-].[Na+]'), # rung 6: only an s-block metal opens this
|
|
1078
|
+
('O.[Na]', '[OH-].[Na+]'),
|
|
1079
|
+
('O.O.[Mg]', '[OH-].[OH-].[Mg+2]'), # two equivalents, two sites
|
|
1080
|
+
('CC(=O)O.CCO.[Na]', 'CC(=O)[O-].CCO.[Na+]'), # rung 4 beats rung 6: the acid spends it
|
|
1081
|
+
('CCO.[Sc]', None), # d block: an alcohol is no site for it
|
|
1082
|
+
('CCO.[Al]', None), # p block: the same
|
|
1083
|
+
('CCO.[Be]', None), # the s-block metal that reduces neither
|
|
952
1084
|
('CC(=O)O.[Mg]', None), # case 4: one equivalent, two wanted
|
|
953
1085
|
('CC(=O)O.[Zn]', None), # step 1 refused
|
|
954
1086
|
('CCCCCC.[Na+]', None), # case 4: nothing to sit on
|
|
@@ -1006,6 +1138,39 @@ def test_the_chosen_site_and_the_moved_charge_are_named_in_the_log():
|
|
|
1006
1138
|
assert 'salts:carboxylic-oh' in transfer.message
|
|
1007
1139
|
|
|
1008
1140
|
|
|
1141
|
+
def test_a_metal_protic_site_is_named_in_the_log_like_any_other():
|
|
1142
|
+
_, _, log = _fixed('CCO.[Na]')
|
|
1143
|
+
transfer = next(record for record in log if record.rule == 'salts:charge-transfer')
|
|
1144
|
+
assert 'salts:alcohol-oh' in transfer.message and 'rung 6' in transfer.message
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
def test_the_alcohol_is_a_site_for_the_s_block_and_for_no_other_metal():
|
|
1148
|
+
"""The ruling: a free group 1 or 2 metal does not stand beside a hydroxyl. Beryllium is the one
|
|
1149
|
+
s-block metal outside it, and the d and p blocks are outside it as blocks."""
|
|
1150
|
+
for symbol in ('Li', 'Na', 'K', 'Cs', 'Mg', 'Ca', 'Ba'):
|
|
1151
|
+
# one alcohol per equivalent: the stage is all-or-nothing, so a group 2 metal needs two
|
|
1152
|
+
charge = smiles('[%s]' % symbol).atom(1).valence_electrons
|
|
1153
|
+
mol, written, log = _fixed('.'.join(['CCO'] * charge + ['[%s]' % symbol]))
|
|
1154
|
+
assert written, symbol
|
|
1155
|
+
assert all(record.severity == REPAIRED for record in log), symbol
|
|
1156
|
+
assert sum(atom.charge for atom in mol.atoms() if atom.charge > 0) == charge, symbol
|
|
1157
|
+
for symbol in ('Be', 'Sc', 'Al', 'Zn', 'Fe', 'Ce'):
|
|
1158
|
+
mol, written, log = _fixed('.'.join(['CCO'] * 4 + ['[%s]' % symbol]))
|
|
1159
|
+
assert not written, symbol
|
|
1160
|
+
assert all(record.severity == REFUSED for record in log), symbol
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
def test_a_thiol_is_an_acid_for_every_metal_and_not_a_metal_protic_site():
|
|
1164
|
+
"""The sulfur rows are `protic_acid`, so they need no s-block gate -- what gates them is the same
|
|
1165
|
+
determinate-valence step every acid goes through."""
|
|
1166
|
+
mol, written, log = _fixed('CCS.CCS.CCS.[Al]')
|
|
1167
|
+
assert mol.canonical_bytes == smiles('CC[S-].CC[S-].CC[S-].[Al+3]').canonical_bytes
|
|
1168
|
+
assert all(record.severity == REPAIRED for record in log)
|
|
1169
|
+
# and the alcohol is not, for the same metal and the same count
|
|
1170
|
+
mol, written, log = _fixed('CCO.CCO.CCO.[Al]')
|
|
1171
|
+
assert not written and all(record.severity == REFUSED for record in log)
|
|
1172
|
+
|
|
1173
|
+
|
|
1009
1174
|
def test_the_ladder_picks_the_more_acidic_of_two_sites():
|
|
1010
1175
|
# a phenol and a carboxylic acid on one molecule: rung 4 beats rung 5
|
|
1011
1176
|
mol, written, log = _fixed('OC(=O)c1ccccc1O.[Na]')
|
|
@@ -1076,6 +1241,42 @@ def test_a_refused_case_2_leaves_no_hydrogen_behind():
|
|
|
1076
1241
|
assert mol.canonical_bytes == smiles('[Al+].[O-]S(=O)(=O)[O-]').canonical_bytes
|
|
1077
1242
|
|
|
1078
1243
|
|
|
1244
|
+
#: A free metal drawn with a hydrogen, one record per path that would have written its charge: the four
|
|
1245
|
+
#: acidity rungs an alcohol, water, a carboxylic acid and a benzylic alcohol reach, and the case-2 raise.
|
|
1246
|
+
#: `[H-].[Na+].CCO` is the control -- drawn ionically, the hydride is a component and nothing is at risk.
|
|
1247
|
+
HYDRIDE_RECORDS = ('[NaH]', '[NaH].CCO', '[NaH].O', '[NaH].CC(=O)O', '[NaH].c1ccccc1CO', '[KH].CCO',
|
|
1248
|
+
'[AlH2].CCO', '[MgH+].[O-]S(=O)(=O)[O-]')
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
@mark.parametrize('spelling', HYDRIDE_RECORDS)
|
|
1252
|
+
def test_an_implicit_hydrogen_on_the_free_metal_refuses_the_transfer(spelling):
|
|
1253
|
+
"""Charging a free metal drops nothing it was drawn with.
|
|
1254
|
+
|
|
1255
|
+
`[NaH].CCO` is sodium hydride in ethanol AS DRAWN. Sodium ethoxide and H2 is a reaction, not a
|
|
1256
|
+
repair, and the hydride has nowhere to go, so the record is left alone -- the guard `split_salts()`
|
|
1257
|
+
already applies before cutting a bond, on the path that writes a charge without cutting one.
|
|
1258
|
+
"""
|
|
1259
|
+
mol = smiles(spelling)
|
|
1260
|
+
metal = next(atom.n for atom in mol.atoms() if atom.is_metal)
|
|
1261
|
+
brutto, hydrogens = mol.brutto, mol.implicit_h_of(metal)
|
|
1262
|
+
log = mol.log
|
|
1263
|
+
mol.standardize()
|
|
1264
|
+
assert mol.brutto == brutto, spelling
|
|
1265
|
+
assert mol.implicit_h_of(metal) == hydrogens, spelling
|
|
1266
|
+
assert mol.canonical_bytes == smiles(spelling).canonical_bytes, spelling
|
|
1267
|
+
assert 'implicit hydrogen' in log.refused()[0], spelling
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
def test_the_ionically_drawn_hydride_is_not_the_guard_s_business():
|
|
1271
|
+
"""`[H-]` is a component and not a count on the metal, so the record standardizes as any other."""
|
|
1272
|
+
mol = smiles('[H-].[Na+].CCO')
|
|
1273
|
+
brutto = mol.brutto
|
|
1274
|
+
mol.standardize()
|
|
1275
|
+
assert mol.brutto == brutto
|
|
1276
|
+
assert mol.canonical_bytes == smiles('[H-].[Na+].CCO').canonical_bytes
|
|
1277
|
+
assert not mol.log.refused()
|
|
1278
|
+
|
|
1279
|
+
|
|
1079
1280
|
def test_case_2_requires_drawn_anions():
|
|
1080
1281
|
mol, written, log = _fixed('[Na-]')
|
|
1081
1282
|
assert mol.canonical_bytes == smiles('[Na-]').canonical_bytes
|
|
@@ -110,8 +110,66 @@ def _selected(rules: Mapping[str, tuple[ReactionRule, ...]],
|
|
|
110
110
|
return chain.from_iterable(rules.values())
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
#: `{rule id: (its groups as a set, its groups as a multiset)}`. The prefilter's left-hand side is a
|
|
114
|
+
#: constant of the row while the right-hand side is the pool's, so an enumerator walking many pools over
|
|
115
|
+
#: one corpus would otherwise rebuild every row's `Counter` once per pool.
|
|
116
|
+
_NEEDS_CACHE: dict[str, tuple[frozenset, Counter]] = {}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _needs(rule: ReactionRule) -> tuple[frozenset, Counter]:
|
|
120
|
+
"""`rule.groups` as a set and as a multiset, built once per row.
|
|
121
|
+
|
|
122
|
+
Two spellings of one requirement because the set settles most pools on its own: only a row whose
|
|
123
|
+
groups are all present at all goes on to the subtraction that also counts them.
|
|
124
|
+
"""
|
|
125
|
+
needs = _NEEDS_CACHE.get(rule.id)
|
|
126
|
+
if needs is None:
|
|
127
|
+
needs = _NEEDS_CACHE[rule.id] = (frozenset(rule.groups), Counter(rule.groups))
|
|
128
|
+
return needs
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _fitting(molecules: Sequence[MoleculeContainer], rules: Mapping[str, tuple[ReactionRule, ...]],
|
|
132
|
+
reaction: str | None = None, carried: Sequence[Mapping[str, int]] | None = None
|
|
133
|
+
) -> Iterator[ReactionRule]:
|
|
134
|
+
"""The rows whose groups this pool could satisfy. `_run`'s prefilter, and nothing after it.
|
|
135
|
+
|
|
136
|
+
Separate from `_run` because a caller may hold a test this file cannot: reconstruction knows the
|
|
137
|
+
recorded product, so it can bound what a row could BUILD from a pool before paying to build it.
|
|
138
|
+
Both filters and why each one is sound are documented on `_run`.
|
|
139
|
+
|
|
140
|
+
`carried` is the caller's own `[functional_groups(m) for m in molecules]`, positional, where it has
|
|
141
|
+
one already: a molecule's groups do not depend on what it is enumerated beside, so an enumerator
|
|
142
|
+
walking many pools over the same inputs scans each input once instead of once per pool.
|
|
143
|
+
"""
|
|
144
|
+
if carried is None:
|
|
145
|
+
carried = [functional_groups(molecule) for molecule in molecules]
|
|
146
|
+
available = Counter()
|
|
147
|
+
for groups in carried:
|
|
148
|
+
available.update(groups)
|
|
149
|
+
have = available.keys()
|
|
150
|
+
|
|
151
|
+
for rule in _selected(rules, reaction):
|
|
152
|
+
wanted, counts = _needs(rule)
|
|
153
|
+
if not wanted <= have or counts - available:
|
|
154
|
+
continue
|
|
155
|
+
if any(wanted.isdisjoint(groups) for groups in carried):
|
|
156
|
+
continue
|
|
157
|
+
yield rule
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _outcomes(rule: ReactionRule, molecules: Sequence[MoleculeContainer]
|
|
161
|
+
) -> Iterator[EnumeratedReaction]:
|
|
162
|
+
"""Every reaction `rule` produces on `molecules` that touched all of them."""
|
|
163
|
+
inputs = len(molecules)
|
|
164
|
+
for template in rule.templates:
|
|
165
|
+
for rxn in template(*molecules):
|
|
166
|
+
if len(rxn.reactants) == inputs:
|
|
167
|
+
yield EnumeratedReaction(rule.name, rxn, rule.id)
|
|
168
|
+
|
|
169
|
+
|
|
113
170
|
def _run(molecules: Sequence[MoleculeContainer], rules: Mapping[str, tuple[ReactionRule, ...]],
|
|
114
|
-
reaction: str | None = None
|
|
171
|
+
reaction: str | None = None, carried: Sequence[Mapping[str, int]] | None = None
|
|
172
|
+
) -> Iterator[EnumeratedReaction]:
|
|
115
173
|
"""The enumeration itself. One presence scan per input, then every row that fits.
|
|
116
174
|
|
|
117
175
|
Separate from `react()` only so a test can hand it a rule fixture the corpus has no row for.
|
|
@@ -122,19 +180,16 @@ def _run(molecules: Sequence[MoleculeContainer], rules: Mapping[str, tuple[React
|
|
|
122
180
|
(`len(rxn.reactants) == inputs`), so a three-molecule question is never answered by a row that
|
|
123
181
|
ignores one; an untouched COMPONENT of a touched input is different and survives, which is the salt
|
|
124
182
|
rule. Nothing between them knows how many molecules a row "expects".
|
|
125
|
-
"""
|
|
126
|
-
available = Counter()
|
|
127
|
-
for molecule in molecules:
|
|
128
|
-
available.update(functional_groups(molecule))
|
|
129
|
-
inputs = len(molecules)
|
|
130
183
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
184
|
+
THAT OUTGOING RULE IS ALSO A PREFILTER READ PER INPUT: an input carrying none of a row's groups can
|
|
185
|
+
never be one of its reactants, so a row is skipped where any single input is disjoint from it. The
|
|
186
|
+
union test cannot see this -- it asks only whether the groups are there, not whether they are spread
|
|
187
|
+
across every molecule that has to be touched.
|
|
188
|
+
|
|
189
|
+
`carried` is `_fitting`'s, and means the same thing here.
|
|
190
|
+
"""
|
|
191
|
+
for rule in _fitting(molecules, rules, reaction, carried):
|
|
192
|
+
yield from _outcomes(rule, molecules)
|
|
138
193
|
|
|
139
194
|
|
|
140
195
|
def react(molecule: MoleculeContainer, others=(), reaction: str | None = None
|