orthonym.rules.locant_validation#
Note
Internal API. Names and behaviour may change between releases.
Locant validation module for IUPAC nomenclature.
Provides standalone validation functions that detect and resolve locant conflicts between suffix groups, prefix substituents, and stereo descriptors on ring and chain parent structures.
This module is pure data validation – no RDKit dependency, no side effects. It will be wired into the composer pipeline by Plan 17-03.
- Key problem addressed:
32 of 70 OPSIN parse failures are “unphysical valency” errors caused by suffix and prefix locants colliding on the same atom (e.g., ketone at C-3 and chloro at C-3 on cyclohexanone producing an impossible valence).
Reference: IUPAC 2013 Blue Book,,
- orthonym.rules.locant_validation.validate_suffix_locants(locants, parent_size, count)#
Validate suffix locants against parent structure capacity.
Filters out: - Locants <= 0 (not valid IUPAC; numbering is 1-indexed) - Locants > parent_size (cannot exist on the parent structure)
Adjusts count to match the number of remaining valid locants.
- Parameters:
locants (List[int]) – Suffix locant positions (e.g., [1, 3] for propane-1,3-diol).
parent_size (int) – Number of atoms in the parent ring or chain.
count (int) – Original multiplier count (e.g., 2 for “di”, 3 for “tri”).
- Returns:
(filtered_locants, adjusted_count) – both aligned to valid positions.
- Return type:
Tuple[List[int], int]
Examples
>>> validate_suffix_locants([1, 3], parent_size=6, count=2) ([1, 3], 2) >>> validate_suffix_locants([1, 3, 8], parent_size=6, count=3) ([1, 3], 2) >>> validate_suffix_locants([0, 2, 4], parent_size=6, count=3) ([2, 4], 2)
- orthonym.rules.locant_validation.detect_locant_collisions(suffix_locants, prefix_locant_groups, parent_type='ring', parent_size=0)#
Detect collisions between suffix locants and prefix locant groups.
A collision occurs when a suffix locant (functional group position) and a prefix locant (substituent position) share the same numeric value on a ring system. On chains, suffix and prefix atoms at the same locant number can coexist (different chemistry), so chain systems are skipped entirely.
- Parameters:
suffix_locants (List[int]) – Locant positions for the principal characteristic group suffix (e.g., [1] for cyclohexan-1-one).
prefix_locant_groups (List[List[int]]) – List of locant lists, one per prefix substituent group (e.g., [[3], [5]] for 3-chloro-5-methyl-).
parent_type (str) –
"ring"or"chain". Chain systems skip detection.parent_size (int) – Number of atoms in the parent (currently informational; reserved for future resolution strategies).
- Returns:
List of
(prefix_group_index, colliding_locant)tuples. Empty list if no collisions or if parent is a chain.- Return type:
List[Tuple[int, int]]
Examples
>>> detect_locant_collisions([3], [[3]], parent_type="ring", parent_size=6) [(0, 3)] >>> detect_locant_collisions([1], [[3]], parent_type="chain", parent_size=5)
- orthonym.rules.locant_validation.validate_stereo_locants(stereo_descriptors, parent_size)#
Filter stereo descriptors to remove entries with invalid locants.
Removes entries where: - Locant is 0 (not valid IUPAC) - Locant is negative (integer check) - Locant (if integer) exceeds parent_size
String locants like
'4a'are kept if their numeric base (leading digits) parses to a value <= parent_size. This covers fused ring systems where positional labels include letter suffixes.- Parameters:
stereo_descriptors (List[Tuple[Any, str]]) – List of
(locant, descriptor)tuples where locant isintorstrand descriptor is e.g.'R','S','E','Z'.parent_size (int) – Number of atoms in the parent structure.
- Returns:
Filtered list preserving original order.
- Return type:
List[Tuple[Any, str]]
Examples
>>> validate_stereo_locants([(2, 'R'), (4, 'S')], parent_size=6) [(2, 'R'), (4, 'S')] >>> validate_stereo_locants([(0, 'R'), (3, 'S'), (99, 'R')], parent_size=6) [(3, 'S')]
- orthonym.rules.locant_validation.reconcile_multiplier_count(count, locants)#
Ensure multiplier count matches locant count.
OPSIN requires exact agreement between the multiplier prefix (di, tri, tetra) and the number of locants cited. This function forces them to agree by using
len(locants)as the canonical count.If locants is empty, the original count is preserved because some naming contexts use implicit locants (e.g., terminal acids on chains where locant-1 is omitted).
- Parameters:
count (int) – Current multiplier count.
locants (List[int]) – Locant positions that were actually generated.
- Returns:
Reconciled count.
- Return type:
int
Examples
>>> reconcile_multiplier_count(count=3, locants=[1, 3]) 2 >>> reconcile_multiplier_count(count=2, locants=) 2