orthonym.perception.rings#

Note

Internal API. Names and behaviour may change between releases.

Ring system detection and analysis.

Handles detection of: - Simple rings - Fused ring systems - Spiro systems - Aromatic rings - Heterocyclic rings

orthonym.perception.rings.get_ring_info(mol)#

Get basic ring information from RDKit.

Parameters:

mol – RDKit Mol object

Returns:

Dictionary with ring information –

  • atom_rings: tuple of tuples of atom indices

  • bond_rings: tuple of tuples of bond indices

  • num_rings: number of rings

Return type:

Dict

orthonym.perception.rings.get_ring_systems(mol, include_spiro=False)#

Find connected ring systems.

Groups rings that share atoms into ring systems: - Fused rings share >1 atom - Spiro rings share exactly 1 atom

Parameters:
  • mol – RDKit Mol object

  • include_spiro (bool) – If True, spiro-connected rings are in same system

Returns:

List of sets, each set contains atom indices in one ring system

Return type:

List[Set[int]]

orthonym.perception.rings.get_complete_ring_atom_set(mol)#

Return ALL atoms in ALL ring systems (fused, bridged, spiro merged).

IUPAC: Ring system = all atoms in connected ring components. Includes bridgehead atoms, bridge atoms, spiro atoms. Does NOT include exocyclic atoms (=O, -OH, etc.) per. RDKit’s AtomRings correctly reports only ring-member atoms.

Parameters:

mol – RDKit Mol object

Returns:

frozenset of all ring atom indices across all ring systems

Return type:

frozenset

orthonym.perception.rings.get_containing_ring_system(mol, ring_atoms)#

Return all atoms in the ring system(s) containing the given ring atoms.

For a single SSSR ring that is part of a fused/bridged/spiro system, this returns the complete merged system. Used by Type A callers (ring-parent substituent detection) to prevent BFS from walking into fused partner rings.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms – Iterable of atom indices (e.g., one SSSR ring)

Returns:

frozenset of all atom indices in the containing ring system(s)

Return type:

frozenset

orthonym.perception.rings.is_aromatic_ring(mol, ring_atoms)#

Check if all atoms in a ring are aromatic.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms – Iterable of atom indices

Returns:

True if all atoms in the ring are aromatic

Return type:

bool

orthonym.perception.rings.get_aromatic_rings(mol)#

Get all aromatic rings in the molecule.

Parameters:

mol – RDKit Mol object

Returns:

List of tuples, each tuple contains atom indices of an aromatic ring

Return type:

List[Tuple[int, …]]

orthonym.perception.rings.get_ring_size(mol, atom_idx)#

Get the smallest ring size containing an atom.

Parameters:
  • mol – RDKit Mol object

  • atom_idx (int) – Index of atom

Returns:

Smallest ring size, or 0 if atom is not in any ring

Return type:

int

orthonym.perception.rings.is_in_ring(mol, atom_idx)#

Check if an atom is in any ring.

Parameters:
  • mol – RDKit Mol object

  • atom_idx (int) – Index of atom

Returns:

True if atom is in a ring

Return type:

bool

orthonym.perception.rings.atoms_in_same_ring(mol, idx1, idx2)#

Check if two atoms are in the same ring.

Parameters:
  • mol – RDKit Mol object

  • idx1 (int) – Atom indices

  • idx2 (int) – Atom indices

Returns:

True if atoms share at least one ring

Return type:

bool

orthonym.perception.rings.get_ring_heteroatoms(mol, ring_atoms)#

Get heteroatoms (non-carbon) in a ring.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms – Iterable of atom indices

Returns:

List of (position_in_ring, element_symbol) tuples Position is 0-indexed within the ring

Return type:

List[Tuple[int, str]]

orthonym.perception.rings.count_ring_double_bonds(mol, ring_atoms)#

Count double bonds within a ring.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms – Iterable of atom indices

Returns:

Number of double bonds in the ring

Return type:

int

orthonym.perception.rings.get_ring_double_bond_atoms(mol, ring_atoms)#

Get all double bonds within a ring as atom pairs.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms – Iterable of atom indices

Returns:

List of (atom_idx1, atom_idx2) tuples for each double bond

Return type:

List[Tuple[int, int]]

orthonym.perception.rings.is_saturated_ring(mol, ring_atoms)#

Check if a ring is fully saturated (no double bonds).

Parameters:
  • mol – RDKit Mol object

  • ring_atoms – Iterable of atom indices

Returns:

True if ring has no double bonds

Return type:

bool

orthonym.perception.rings.is_heterocyclic(mol, ring_atoms)#

Check if a ring contains heteroatoms.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms – Iterable of atom indices

Returns:

True if ring contains non-carbon atoms

Return type:

bool

orthonym.perception.rings.get_spiro_atoms(mol)#

Memoising front of:func:_get_spiro_atoms_impl (perf lever A7, 2026-09-13).

Ring-classification helpers (is_spiro_system, classify_ring_system_type, is_bicyclo_system, _spiro_fusion_count,…) call this about 11 times per pipeline pass on the same Mol (26,161 calls per 300 molecules, 2.2 s). Ring membership depends on bonds only, which cannot change on a Chem.Mol without an RWMol (never cached), so the key is the Mol identity. A fresh set is returned on every call because some callers mutate the result.

orthonym.perception.rings.get_bridgehead_atoms(mol)#

Find bridgehead atoms in bridged ring systems.

Parameters:

mol – RDKit Mol object

Returns:

Set of atom indices that are bridgeheads

Return type:

Set[int]

orthonym.perception.rings.find_ring_bridgeheads(mol, ring_atoms=None)#

Find von-Baeyer bridgehead atoms: ring-skeletal atoms bonded to >=3 other ring-skeletal atoms.

/: the SINGLE consolidated bridgehead predicate (seeded from VonBaeyerAnalyzer._find_all_bridgeheads, polycyclic.py:425-433). Counts only ring-member neighbours, so an exocyclic substituent (camphor’s gem-dimethyl bridgehead) does NOT disqualify a bridgehead — exactly the

fix. This is distinct from get_bridgehead_atoms (count>=2 ring

membership, too loose) which is left unchanged because it is widely imported.

IUPAC: a bridgehead is a skeletal atom bonded to three or more other skeletal atoms (excluding H); bridgeheads MAY be quaternary/ substituted.

Parameters:

mol – RDKit Mol object

Returns:

Set of atom indices that are bridgeheads (>=3 ring-member neighbours).

Return type:

Set[int]

orthonym.perception.rings.classify_ring(mol, ring_atoms)#

Classify a ring by its chemical type.

Classification order (check in this order): 1. Heterocyclic aromatic - contains non-carbon atoms AND all atoms aromatic 2. Heterocyclic saturated - contains non-carbon atoms AND not all aromatic 3. Aromatic - all atoms are aromatic (RDKit detection), carbocyclic 4. Cycloalkane - saturated, all carbon, no double bonds 5. Cycloalkene - unsaturated, all carbon, has double bonds but not aromatic

All heterocyclic return values start with ‘heterocyclic’ so callers can use ring_type.startswith('heterocyclic') for backward-compatible matching.

Parameters:
  • mol – RDKit Mol object

  • ring_atoms (Tuple[int, ...]) – Tuple of atom indices defining the ring

Returns:

Classification string – ‘heterocyclic_aromatic’, ‘heterocyclic_saturated’, ‘aromatic’, ‘cycloalkane’, or ‘cycloalkene’

Return type:

str

Examples

>>> mol = Chem.MolFromSmiles('C1CCCCC1') # cyclohexane
>>> classify_ring(mol, mol.GetRingInfo.AtomRings[0])
'cycloalkane'
>>> mol = Chem.MolFromSmiles('c1ccccc1') # benzene
>>> classify_ring(mol, mol.GetRingInfo.AtomRings[0])
'aromatic'
>>> mol = Chem.MolFromSmiles('c1ccncc1') # pyridine
>>> classify_ring(mol, mol.GetRingInfo.AtomRings[0])
'heterocyclic_aromatic'
>>> mol = Chem.MolFromSmiles('C1CCNCC1') # piperidine
>>> classify_ring(mol, mol.GetRingInfo.AtomRings[0])
'heterocyclic_saturated'