moleculekit.tools.sybyl module#

SYBYL atom typing from perceived chemistry and stored bond orders.

PROPKA decides which groups of a non-canonical residue can titrate entirely from SYBYL atom types, and it has no bond-order field to consult: an propka.atom.Atom carries bonded_atoms, num_pi_elec_2_3_bonds and steric_number, and nothing else about how its bonds are ordered. So propka.ligand.assign_sybyl_type reconstructs the chemistry from coordinates - ring membership, planarity and aromaticity by geometry, double bonds by a distance threshold - and most of its helpers exist only for that.

Reconstruction gets wrong what a stored bond order states outright. PROPKA’s double-bond test compares against MAX_C_DOUBLE_BOND of 1.3 A, calibrated for carbon, so a P=O at ~1.5 A never registers and every terminal phosphate oxygen is typed O.3. Each then becomes an independent titratable site, and since a phosphate has one ionizable proton fewer than it has terminal oxygens, the group is over-charged by one.

This module reads the bond orders the molecule already carries and lets RDKit perceive the rest. Ring membership, aromaticity, conjugation and hybridisation are RDKit’s answers, not re-derived here: aromaticity in particular cannot be read off bond types, since mmCIF commonly stores aromatic rings in Kekule form with no aromatic bond recorded at all.

Reproducing PROPKA’s conventions#

PROPKA reads certain types as markers rather than as neutral chemistry, so this module matches its spellings rather than any external SYBYL implementation:

  • a carboxylate is emitted as an O.co2- / O.co2 pair, one per oxygen;

  • N.pl3 doubles as the guanidinium and amidinium detector - PROPKA’s C.2 branch looks for a carbon bearing two N.pl3 nitrogens that each have a single heavy neighbour;

  • P.3 is emitted for phosphorus so that, when these types are handed to PROPKA, its phosphorus branch - which ends by resetting every bonded oxygen to O.3 - returns early instead of overwriting them.

What this does and does not settle#

Atom types settle group identity - which groups PROPKA will find - and nothing else. They cannot settle protonation, because SYBYL has no vocabulary for it: a neutral, mono-anionic and di-anionic phosphate all type identically, as O.2 plus terminal O.3. So bond orders are respected here, and the hydrogens and formal charges an input states are not, with one exception - N.4 needs the charge, and O.co2- is given to the oxygen the input charged, since both types carry charge information themselves.

A templated residue’s protonation is therefore respected one layer up rather than here: preparation_propka._apply_templated_formal_charges pins each group’s charge from mol.formalcharge and marks it non-titratable, so PROPKA never re-decides what the template already settled. Anything calling this module and handing the result to PROPKA without that step will get protonation re-invented.

Only the types PROPKA consumes are produced, in group detection (C.2, Cl, F, N.1, N.3, N.4, N.am, N.ar, N.pl3, O.2, O.3, O.co2, S.3) and in the pi-electron tables (C.1, C.ar, O.co2). Anything else falls back to the capitalised element symbol, as PROPKA itself does.

moleculekit.tools.sybyl.sybylTypes(mol, sel=None)#

Assign SYBYL atom types from perceived chemistry and stored bond orders.

Parameters:
  • mol (Molecule) – A molecule carrying bonds, bondtype and formalcharge.

  • sel (str or numpy.ndarray or None) – Atom selection, boolean mask or index array restricting which atoms are typed. Perception always runs on the whole molecule, so a residue is typed in the context of its neighbours.

Returns:

types{atom_index: sybyl_type} for the selected heavy atoms. Hydrogens are omitted: PROPKA types them H and never branches on it.

Return type:

dict

Notes

For a residue that records no bond orders only the phosphorus types are returned - the one judgement here that does not need them - and everything else is left to whatever the caller had. See _ordered_residues().

Examples

>>> from moleculekit.molecule import Molecule
>>> from moleculekit.tools.sybyl import sybylTypes
>>> mol = Molecule("3ptb")
>>> mol.templateResidueFromSmiles(
...     "resname BEN", "NC(=[NH2+])c1ccccc1", addHs=True
... )
>>> types = sybylTypes(mol, "resname BEN")
>>> sorted(set(types.values()))
['C.2', 'C.ar', 'N.pl3']