Source code for synkit.Chem.Molecule.valence
from __future__ import annotations
from typing import Protocol, runtime_checkable
from rdkit import Chem
from rdkit.Chem import rdchem as rdchem
@runtime_checkable
class _AtomLike(Protocol):
"""Minimal protocol RDKit atoms satisfy (duck-typed for typing tools)."""
def GetValence(self, which: rdchem.ValenceType | int = ...) -> int: ...
def GetExplicitValence(self) -> int: ...
def GetImplicitValence(self) -> int: ...
def GetNumImplicitHs(self) -> int: ...
[docs]
class ValenceResolver:
"""
Warning-free valence utilities for RDKit atoms.
These helpers retrieve **explicit**, **implicit**, and **total** valences
while silencing common deprecation or signature warnings across RDKit
versions. They first try the modern keyword-argument API and gracefully
fall back to older call signatures or legacy methods.
Preferred (modern) RDKit API:
- ``atom.GetValence(which=rdchem.ValenceType.EXPLICIT)``
- ``atom.GetValence(which=rdchem.ValenceType.IMPLICIT)``
Fallbacks maintain compatibility with older wrappers:
- ``atom.GetValence(rdchem.ValenceType.EXPLICIT)`` (positional)
- ``atom.GetExplicitValence()``
- ``atom.GetImplicitValence()``
- ``atom.GetNumImplicitHs()`` (as a last resort for implicit)
Notes
-----
* Returned values are coerced to Python ``int`` and guaranteed non-negative,
with ``0`` returned if all strategies fail.
* Values reflect the *current* state of the atom. If you modify hydrogen
counts, aromaticity, or bond orders, query again.
* ``Chem.Atom`` is an alias of ``rdchem.Atom``, but a structural duck-type
``_AtomLike`` protocol is provided for static typing tools.
Examples
--------
>>> from rdkit import Chem
>>> m = Chem.MolFromSmiles("CCO")
>>> a = m.GetAtomWithIdx(1) # central carbon
>>> ValenceResolver.explicit(a) >= 0
True
>>> ValenceResolver.total(a) == ValenceResolver.explicit(a) + ValenceResolver.implicit(a)
True
"""
[docs]
@staticmethod
def explicit(atom: Chem.Atom | _AtomLike) -> int:
"""
Return the **explicit valence** of an atom.
Tries modern ``GetValence(which=EXPLICIT)`` first, then older positional
form, then ``GetExplicitValence()``. Returns ``0`` on failure.
:param atom: RDKit atom instance.
:type atom: rdchem.Atom
:returns: Explicit valence (non-negative integer).
:rtype: int
"""
# Modern keyword form (preferred; avoids RDKit warnings)
try:
return int(atom.GetValence(which=rdchem.ValenceType.EXPLICIT)) # type: ignore[call-arg]
except TypeError:
# Some RDKit builds don't accept the kwarg form
try:
return int(atom.GetValence(rdchem.ValenceType.EXPLICIT)) # type: ignore[arg-type]
except Exception:
# Legacy explicit valence API
try:
return int(atom.GetExplicitValence())
except Exception:
return 0
[docs]
@staticmethod
def implicit(atom: Chem.Atom | _AtomLike) -> int:
"""
Return the **implicit valence** of an atom.
Tries modern ``GetValence(which=IMPLICIT)`` first, then older positional
form, then ``GetImplicitValence()``, finally falls back to the number of
implicit hydrogens if needed. Returns ``0`` on failure.
:param atom: RDKit atom instance.
:type atom: rdchem.Atom
:returns: Implicit valence (non-negative integer).
:rtype: int
"""
# Modern keyword form (preferred)
try:
return int(atom.GetValence(which=rdchem.ValenceType.IMPLICIT)) # type: ignore[call-arg]
except TypeError:
# Older builds without kwarg support
try:
return int(atom.GetValence(rdchem.ValenceType.IMPLICIT)) # type: ignore[arg-type]
except Exception:
# Legacy implicit valence API
try:
return int(atom.GetImplicitValence())
except Exception:
# As a last resort, approximate with implicit Hs
try:
return int(atom.GetNumImplicitHs())
except Exception:
return 0
[docs]
@staticmethod
def total(atom: Chem.Atom | _AtomLike) -> int:
"""
Return the **total valence** (explicit + implicit).
:param atom: RDKit atom instance.
:type atom: rdchem.Atom
:returns: Total valence as ``explicit(atom) + implicit(atom)``.
:rtype: int
"""
return ValenceResolver.explicit(atom) + ValenceResolver.implicit(atom)