orthonym.perception.chains#

Note

Internal API. Names and behaviour may change between releases.

Chain detection and principal chain selection.

Implements IUPAC 2013 rules for selecting the principal chain. Key change in IUPAC 2013: Chain length takes priority over unsaturation!

orthonym.perception.chains.find_all_carbon_chains(mol, min_length=1, exclude_atoms=None)#

Find all carbon chains in a molecule using DFS.

Parameters:
  • mol – RDKit Mol object

  • min_length (int) – Minimum chain length to return

  • exclude_atoms (Set[int] | None) – Optional set of atom indices to skip (e.g., ring atoms)

Returns:

List of lists, each inner list contains atom indices of a chain

Return type:

List[List[int]]

orthonym.perception.chains.find_maximal_carbon_chains(mol, exclude_atoms=None)#

The paths of find_all_carbon_chains(mol, 1, exclude_atoms) that cannot be extended at either end, in the SAME relative order as in that list.

A path is extendable when an end atom has a carbon neighbour that is neither excluded nor on the path; the extended path is then itself one of the enumerated paths (the DFS below walks every simple path of non-excluded carbons from every start). find_principal_chain only ever needs these: see the proof at its call site.

Cost: the full enumeration stores every simple path, n^2 paths of mean length n/3 for an unbranched C_n chain, and find_principal_chain scored each one in O(n) – O(n^3) per molecule (C145: 21025 paths, 25 s). This walks the same DFS but records a path only when both of its ends are closed, and skips the DFS from a start atom that provably closes no path (below), so an unbranched chain costs O(n).

Traversal order is the same as find_all_carbon_chains: start atoms in atom order, neighbours in RDKit neighbour order, a path recorded when the DFS reaches its last atom. Only which paths are recorded differs.

orthonym.perception.chains.find_longest_carbon_chain(mol, exclude_atoms=None)#

Find the longest continuous carbon chain.

Parameters:
  • mol – RDKit Mol object

  • exclude_atoms (Set[int] | None) – Optional set of atom indices to skip (e.g., ring atoms)

Returns:

List of atom indices forming the longest chain

Return type:

List[int]

orthonym.perception.chains.find_all_skeletal_chains(mol, min_length=1, exclude_atoms=None, max_chains=10000)#

Find all skeletal chains following C, O, N, S atoms.

Per IUPAC and, skeletal replacement nomenclature considers O, N, S as part of the principal chain backbone.

This function is used specifically for parent selection chain-vs-ring comparison. It does NOT replace find_all_carbon_chains which is used for standard chain-based naming.

Scope: Chain FINDING only. Oxa/aza/thia prefix generation is deferred to a later phase.

Parameters:
  • mol – RDKit Mol object

  • min_length (int) – Minimum chain length to return

  • exclude_atoms (Set[int] | None) – Optional set of atom indices to skip (e.g., ring atoms)

  • max_chains (int) – Maximum chains to find (prevents combinatorial explosion)

Returns:

List of lists, each inner list contains atom indices of a skeletal chain

Return type:

List[List[int]]

orthonym.perception.chains.find_longest_skeletal_chain(mol, exclude_atoms=None)#

Find the longest continuous skeletal chain (C, O, N, S).

Convenience function parallel to find_longest_carbon_chain. Used for parent selection comparison when heteroatom chains may be longer than carbon-only chains.

Parameters:
  • mol – RDKit Mol object

  • exclude_atoms (Set[int] | None) – Optional set of atom indices to skip

Returns:

List of atom indices forming the longest skeletal chain

Return type:

List[int]

orthonym.perception.chains.find_principal_chain(mol, functional_groups, principal_group=None, exclude_atoms=None)#

Find the principal chain following IUPAC 2013 rules.

Selection criteria (in order of priority): 1. Contains principal characteristic group 2. Maximum number of principal groups 3. Maximum chain length (IUPAC 2013: length BEFORE unsaturation!) 4. Maximum multiple bonds (double + triple) 5. Maximum double bonds 6. Lowest locants for principal groups (first point of difference) 7. Lowest locants for multiple bonds 8. Maximum substituents 9. Lowest locants for substituents

Non-principal suffix-capable FG terminal carbons (e.g., the C in -C(=O)NH2 when amide is not the principal group) are excluded from chain enumeration to prevent chain length inflation (IUPAC.

Parameters:
  • mol – RDKit Mol object

  • functional_groups (Dict[str, List[tuple]]) – Dict from detect_functional_groups

  • principal_group (str | None) – Name of principal functional group (or None)

  • exclude_atoms (Set[int] | None) – Optional set of atom indices to skip (e.g., ring atoms)

Returns:

List of atom indices forming the principal chain, in order

Return type:

List[int]

orthonym.perception.chains.get_substituents(mol, main_chain)#

Find substituents attached to the main chain.

Parameters:
  • mol – RDKit Mol object

  • main_chain (List[int]) – List of atom indices in main chain (ordered)

Returns:

Dict mapping chain position (1-indexed) to list of substituent atom lists. Each substituent is represented as a list of its atom indices.

Return type:

Dict[int, List[List[int]]]

orthonym.perception.chains.get_chain_atoms_with_locants(chain)#

Create mapping from atom index to locant number.

Parameters:

chain (List[int]) – Ordered list of atom indices

Returns:

Dict mapping atom_idx -> locant (1-indexed)

Return type:

Dict[int, int]

orthonym.perception.chains.is_ring_substituent(mol, sub_atoms, parent_atoms)#

Check if substituent atoms form a complete ring.

A substituent is considered a ring substituent if all atoms of at least one ring in the molecule are contained within the substituent atoms (excluding the parent structure atoms).

Parameters:
  • mol – RDKit Mol object

  • sub_atoms (List[int]) – Atom indices of the substituent

  • parent_atoms (Set[int]) – Atoms of the parent structure (to exclude from consideration)

Returns:

True if the substituent contains a complete ring, False otherwise

Return type:

bool

Examples

>>> mol = Chem.MolFromSmiles('c1ccccc1C') # toluene
>>> # Phenyl atoms: 0-5, Methyl: 6
>>> is_ring_substituent(mol, [0, 1, 2, 3, 4, 5], {6})
True
>>> mol2 = Chem.MolFromSmiles('CCCCC') # pentane
>>> is_ring_substituent(mol2, [0, 1, 2], set)
False
orthonym.perception.chains.classify_substituent(mol, sub_atoms, parent_atoms)#

Classify a substituent as ring or alkyl chain.

This function determines whether a substituent is a ring system (and if so, what kind) or an alkyl chain. It’s used to correctly name ring substituents (phenyl, cyclohexyl, piperidinyl) instead of incorrectly counting carbons (hexyl, pentyl).

Parameters:
  • mol – RDKit Mol object

  • sub_atoms (List[int]) – Atom indices of the substituent

  • parent_atoms (Set[int]) – Atoms of the parent structure (to exclude)

Returns:

Dict with –

  • ‘type’: ‘ring’ or ‘alkyl’

  • ’name’: substituent name (e.g., ‘phenyl’, ‘cyclohexyl’, ‘methyl’)

  • ’atoms’: list of atom indices

  • ’ring_atoms’: tuple of ring atom indices (only if type=’ring’)

Return type:

Dict

Examples

>>> mol = Chem.MolFromSmiles('c1ccccc1CCC(=O)O') # phenylpropanoic acid
>>> classify_substituent(mol, [0,1,2,3,4,5], {6,7,8,9,10})
{'type': 'ring', 'name': 'phenyl', 'atoms': [0,1,2,3,4,5], 'ring_atoms': (0,1,2,3,4,5)}