orthonym.rules.polycyclics#

Note

Internal API. Names and behaviour may change between releases.

Polycyclic aromatic hydrocarbon (PAH) naming rules.

Handles identification and naming of common polycyclic aromatics: - Bicyclic: naphthalene - Tricyclic: anthracene, phenanthrene, fluorene, acenaphthene, acenaphthylene - Tetracyclic: pyrene, chrysene, tetracene, triphenylene, benz[a]anthracene, benzo[c]phenanthrene - Pentacyclic: pentacene, perylene, benzo[a]pyrene - Hexacyclic+: coronene

Also coordinates with fused_rings module for fused heterocyclic systems.

IUPAC 2013 Rules (Blue Book Section: - PAH numbering is FIXED by IUPAC standard - Use retained names as parent - Substituents are named with their IUPAC locant position - Numbering is NOT reoriented based on substituents (unlike benzene)

Key difference from benzene: - Benzene substituent numbering uses lowest locants - PAH numbering is fixed to the standard IUPAC orientation

PAH Classification: - Ortho-fused: linear PAHs like naphthalene, anthracene, tetracene, pentacene - Peri-condensed: PAHs with interior atoms like pyrene, perylene, coronene

Integration with fused_rings.py: - This module handles carbocyclic PAHs (naphthalene, anthracene, etc.) - fused_rings.py handles fused heterocycles (indole, quinoline, etc.) - This module can delegate to fused_rings for heterocyclic detection

orthonym.rules.polycyclics.identify_polycyclic(mol)#
orthonym.rules.polycyclics.get_polycyclic_core_atoms(mol, pah_name)#

Get the atom indices that form the PAH core.

Parameters:
  • mol – RDKit Mol object

  • pah_name (str) – Name of the PAH (e.g., ‘naphthalene’)

Returns:

Set of atom indices forming the PAH core, or None if no match

Return type:

Set[int] | None

orthonym.rules.polycyclics.get_polycyclic_substituents(mol, pah_name, principal_group=None)#

Find substituents attached to a polycyclic aromatic core.

Parameters:
  • mol – RDKit Mol object

  • pah_name (str) – Name of the PAH (e.g., ‘naphthalene’)

  • principal_group (str | None) – the molecule-level principal group (features.principal_group); when it is a primary amine the amine’s ring carbon is treated as the PCG anchor for lowest-locant numbering (Fix 2).

Returns:

Dict mapping IUPAC locant (1-indexed) to list of substituent info dicts. Each dict has keys: ‘name’ (str), ‘atoms’ (list of atom indices)

Return type:

Dict[int, List[Dict]]

Note

Position mapping for naphthalene is based on IUPAC numbering: - Atoms are numbered 1-8 around the periphery - Fusion carbons (4a, 8a) are not substituent positions

orthonym.rules.polycyclics.get_polycyclic_iupac_locants(mol, pah_name, substituent_atoms=None, pcg_atoms=None)#

a phase: return authoritative IUPAC locants for a cataloged PAH.

Reads POLYCYCLIC_DATA[pah_name]['iupac_numbering'] (canonical-SMILES keyed) and translates canonical-atom indices to mol-atom indices via RDKit substructure match. String fusion locants (‘4a’, ‘10b’,…) are converted to (int, str) tuples at this boundary per a phase Decision (compare_locant_sets tuple-aware after Plan 01).

Returns a dict covering ALL ring atoms of the PAH, mixing plain int locants (peripheral) with (int, str) tuple locants (fusion atoms). Suitable for ring_info["iupac_locants"] in the cascade step 6 gate.

The 4 populated PAHs in v17 are: naphthalene (10 keys, 2 fusion tuples), anthracene (14 keys, 4 fusion tuples), phenanthrene (14 keys, 4 fusion tuples), pyrene (16 keys, 6 fusion tuples). Other entries with empty iupac_numbering (fluorene, acenaphthene,…) return None — a phase audit candidates.

Parameters:
  • mol – RDKit Mol object.

  • pah_name (str) – Canonical PAH name (key in POLYCYCLIC_DATA).

Returns:

Dict[int, int | (int, str)] when pah_name is a populated PAH. None when pah_name is not in POLYCYCLIC_DATA, its iupac_numbering is empty, or the substructure match fails.

Return type:

Dict[int, Any] | None

Source: https://iupac.qmul.ac.uk/BlueBook/P2.html (PAH numbering

is FIXED, not reoriented per substituents — match data directly).

Source: a phase internal notes (tuple encoding); (function form).

orthonym.rules.polycyclics.name_substituted_polycyclic(mol, pah_name, substituents, principal_group=None)#

Generate IUPAC name for a substituted polycyclic aromatic.

Parameters:
  • mol – RDKit Mol object

  • pah_name (str) – Name of the PAH parent (e.g., ‘naphthalene’)

  • substituents (Dict[int, List[Dict]]) – Dict from get_polycyclic_substituents

Returns:

IUPAC name string (e.g., ‘2-methylnaphthalene’)

Return type:

str

IUPAC Rules: - Locants are FIXED to IUPAC standard numbering - Alphabetize substituent prefixes - Use multiplicative prefixes (di-, tri-) for repeated substituents - Format: locants-substituent-parent - Functional groups as suffixes: parent-locant-suffix (e.g., naphthalene-2-carboxylic acid)

orthonym.rules.polycyclics.get_pah_substituent_locants(mol, core_match)#

Find atoms not in the PAH core and map them to IUPAC locants.

For substituted PAHs, identifies substituent atoms and maps them to their attachment point locants.

Parameters:
  • mol – RDKit Mol object

  • core_match (Dict[int, int]) – Dict mapping atom index to IUPAC locant (from match_polycyclic_core)

Returns:

Dict mapping IUPAC locant -> substituent name

Return type:

Dict[int, str]

Example

>>> from rdkit import Chem
>>> mol = Chem.MolFromSmiles('Cc1ccc2ccccc2c1') # 2-methylnaphthalene
>>> from src.orthonym.data.polycyclic_data import match_polycyclic_core
>>> _, core_match = match_polycyclic_core(mol)
>>> get_pah_substituent_locants(mol, core_match)
{2: 'methyl'}
orthonym.rules.polycyclics.is_peri_condensed(mol)#

Detect peri-condensed PAH systems.

Peri-condensed PAHs have interior atoms (not on the periphery) that are shared by more than two rings. Examples include pyrene, perylene, and coronene.

In full IUPAC numbering, these interior atoms may have letter suffixes (like 4a, 8a in naphthalene for fusion atoms, but more complex in peri systems).

Parameters:

mol – RDKit Mol object

Returns:

True if the molecule is a peri-condensed PAH

Return type:

bool

Examples

>>> from rdkit import Chem
>>> mol = Chem.MolFromSmiles('c1ccc2ccccc2c1') # naphthalene
>>> is_peri_condensed(mol)
False
>>> mol = Chem.MolFromSmiles('c1cc2ccc3cccc4ccc(c1)c2c34') # pyrene
>>> is_peri_condensed(mol)
True
>>> mol = Chem.MolFromSmiles('c1cc2ccc3ccc4ccc5ccc6ccc1c1c2c3c4c5c61') # coronene
>>> is_peri_condensed(mol)
True
orthonym.rules.polycyclics.get_pah_type(mol)#

Classify PAH as ortho-fused, peri-condensed, or not a PAH.

Parameters:

mol – RDKit Mol object

Returns:

‘peri-condensed’ – PAH with interior atoms shared by 3+ rings ‘ortho-fused’: Linear PAH with only edge fusion ‘not-pah’: Not a polycyclic aromatic hydrocarbon

Return type:

str

Examples

>>> from rdkit import Chem
>>> mol = Chem.MolFromSmiles('c1ccc2ccccc2c1') # naphthalene
>>> get_pah_type(mol)
'ortho-fused'
>>> mol = Chem.MolFromSmiles('c1cc2ccc3cccc4ccc(c1)c2c34') # pyrene
>>> get_pah_type(mol)
'peri-condensed'
orthonym.rules.polycyclics.name_polycyclic(mol)#

Generate IUPAC name for a polycyclic aromatic hydrocarbon.

Main entry point for PAH naming. Handles: 1. Fully aromatic PAHs (naphthalene, anthracene, etc.) 2. Substituted PAHs (2-methylnaphthalene) 3. Partially saturated PAHs (tetrahydronaphthalene)

The routing order is: 1. Check for partially saturated carbocycles (tetrahydronaphthalene, etc.) 2. Check for fully aromatic PAHs 3. Return None if not a recognized PAH

Parameters:

mol – RDKit Mol object

Returns:

IUPAC name string if PAH identified, None otherwise

Return type:

str | None

Examples

>>> from rdkit import Chem
>>> mol = Chem.MolFromSmiles('c1ccc2ccccc2c1')
>>> name_polycyclic(mol)
'naphthalene'
>>> mol = Chem.MolFromSmiles('Cc1ccc2ccccc2c1')
>>> name_polycyclic(mol)
'2-methylnaphthalene'
>>> mol = Chem.MolFromSmiles('c1cc2ccc3cccc4ccc(c1)c2c34') # pyrene
>>> name_polycyclic(mol)
'pyrene'
>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2') # tetrahydronaphthalene
>>> name_polycyclic(mol)
'1,2,3,4-tetrahydronaphthalene'
orthonym.rules.polycyclics.name_partially_saturated_carbocycle(mol)#

Generate IUPAC name for a partially saturated carbocyclic fused system.

Thin wrapper over:func:name_partially_saturated_carbocycle_with_locants that discards the numbering. Prefer the sibling whenever the caller has to place its own locants — re-deriving a second numbering for the same name is a defect class, not an implementation detail (see that function’s docstring).

Handles compounds like tetrahydronaphthalene, dihydroanthracene, etc. These are PAH systems with some ring atoms saturated (sp3).

IUPAC 2013 format: [locants]-[prefix][parent] Example: 1,2,3,4-tetrahydronaphthalene

Parameters:

mol – RDKit Mol object

Returns:

IUPAC name string if partially saturated carbocycle detected, None otherwise.

Return type:

str | None

Examples

>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2')
>>> name_partially_saturated_carbocycle(mol)
'1,2,3,4-tetrahydronaphthalene'
class orthonym.rules.polycyclics.PartialSatName(name, atom_to_locant, spelled_offring_atoms)#

Bases: NamedTuple

What the partially-saturated fused-carbocycle producer publishes.

name alone is not enough to decorate: a consumer that adds substituent prefixes needs the numbering the name was spelled from AND the set of atoms the name already spells. Withholding either one produced a wrong name – the first gave 1-methyl- for a 2-substituted tetralin, the second gave 6-methyl-6-methyl-…. Both were caught by, i.e. both cost a correct name rather than shipping a wrong one.

name: str#

Alias for field number 0

atom_to_locant: Dict[int, Any]#

ORIGINAL atom idx -> IUPAC ring locant, incl. '4a'/'8a'.

spelled_offring_atoms: Set[int]#

Off-ring atoms name already accounts for (aromatic-ring substituent prefixes it built itself + the exocyclic atoms of its PCG suffix).

orthonym.rules.polycyclics.name_partially_saturated_carbocycle_with_locants(mol)#

The partially-saturated fused-carbocycle name TOGETHER WITH the atom_to_locant map it was spelled from and the off-ring atoms it already spells.

The map is the one:func:detect_carbocyclic_partial_saturation chose (ORIGINAL atom idx -> IUPAC ring locant, including the lettered fusion locants '4a' / '8a'), never a re-derivation. A consumer that has to place its own substituent or suffix locants MUST inherit this map: the hydro locants, the stereodescriptors, the PCG suffix and any downstream substituent prefix all have to agree on one numbering, and two independently-derived numberings for one name spell a different molecule. This mirrors the standing contract stated verbatim in vonbaeyer_universal.RingAnalysis.atom_to_locant and terminal_ring.TerminalRingName.numbering.

Parameters:

mol – RDKit Mol object

Returns:

A:class:PartialSatName, or None when this is not a partially saturated fused carbocycle this path can name correctly (fail closed).

Return type:

PartialSatName | None

Examples

>>> mol = Chem.MolFromSmiles('c1ccc2c(c1)CCCC2')
>>> name_partially_saturated_carbocycle_with_locants(mol).name
'1,2,3,4-tetrahydronaphthalene'
orthonym.rules.polycyclics.is_fused_aromatic_system(mol)#

Check if molecule contains a fused aromatic ring system.

A fused aromatic system has 2+ aromatic rings sharing edges. This includes both carbocyclic PAHs and fused heterocycles.

Parameters:

mol – RDKit Mol object

Returns:

True if fused aromatic system detected

Return type:

bool

Examples

>>> mol = Chem.MolFromSmiles('c1ccc2ccccc2c1') # naphthalene
>>> is_fused_aromatic_system(mol)
True
>>> mol = Chem.MolFromSmiles('c1ccc2[nH]ccc2c1') # indole
>>> is_fused_aromatic_system(mol)
True
>>> mol = Chem.MolFromSmiles('c1ccccc1') # benzene
>>> is_fused_aromatic_system(mol)
False
orthonym.rules.polycyclics.get_fused_aromatic_core(mol)#

Identify if molecule contains a known fused aromatic core.

Checks both carbocyclic PAHs (naphthalene, anthracene) and fused heterocycles (indole, quinoline).

Parameters:

mol – RDKit Mol object

Returns:

Core name if found (e.g., ‘naphthalene’, ‘1H-indole’), None otherwise

Return type:

str | None

Examples

>>> mol = Chem.MolFromSmiles('c1ccc2ccccc2c1')
>>> get_fused_aromatic_core(mol)
'naphthalene'
>>> mol = Chem.MolFromSmiles('c1ccc2[nH]ccc2c1')
>>> get_fused_aromatic_core(mol)
'1H-indole'
orthonym.rules.polycyclics.name_substituted_fused_aromatic(mol, core_name, atom_map=None)#

Generate name for a substituted fused aromatic system.

Handles substituents on both PAHs and fused heterocycles with consistent locant assignment using IUPAC numbering.

Parameters:
  • mol – RDKit Mol object

  • core_name (str) – Base core name (e.g., ‘naphthalene’, ‘1H-indole’)

  • atom_map (Dict[int, int] | None) – Optional pre-computed atom index to locant mapping

Returns:

Complete IUPAC name with substituent prefixes

Return type:

str

Examples

>>> mol = Chem.MolFromSmiles('Cc1ccc2ccccc2c1') # 2-methylnaphthalene
>>> name_substituted_fused_aromatic(mol, 'naphthalene')
'2-methylnaphthalene'
orthonym.rules.polycyclics.identify_fused_system(mol)#

Identify and classify a fused ring system.

Central coordinator for fused system identification. Returns information about the type of fused system and its naming.

Parameters:

mol – RDKit Mol object

Returns:

Dict with –

  • ‘type’: ‘carbocyclic’ or ‘heterocyclic’

  • ’core_name’: Base name of the fused system

  • ’is_substituted’: Whether molecule has substituents on core

  • ’full_name’: Complete IUPAC name

Or None if not a recognized fused system

Return type:

Dict[str, Any] | None

Examples

>>> mol = Chem.MolFromSmiles('c1ccc2ccccc2c1')
>>> info = identify_fused_system(mol)
>>> info['type']
'carbocyclic'
>>> info['core_name']
'naphthalene'