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_chainonly 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_chainscored 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_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, *, ring_prefixes=False)#
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)
ring_prefixes (bool) –
False (every production caller): an atom of
exclude_atomsbonded to a chain atom is neither counted nor located nor named as a substituent by criteria 8, 9 and the tie-break. True: such an atom (a ring bonded to the chain) is a substituent cited as a prefix like any other, as torequire (the Blue Book,:21698,:21791; the chain
parent of the example at:21624 counts its ring substituent). In this mode the tie-break names each whole branch with the side-effect-free
name_substituent_for_ordering, and the call returns None when two different chains reach that tie-break and a branch has no name, or when two different chains still tie after it (the comparison cannot be decided). Used bychain_parent_prefix_seniorityto check the chain a producer was given.
- Returns:
List of atom indices forming the principal chain, in order (None only with
ring_prefixes=True, see above)- Return type:
List[int] | None
- orthonym.perception.chains.chain_symmetry_key(mol, chain)#
The chain as its sequence of atom symmetry classes (constitution only, no tie-breaking), read in the lower of its two directions: two chains with the same key name the molecule alike (the two methyl ends of an isopropyl group; ASSUMED for a sequence of equal classes that no symmetry of the whole molecule maps onto each other, a case that leaves a name below the PIN at worst).
- orthonym.perception.chains.chain_parent_prefix_seniority(features)#
Check a chain parent against to where a ring is bonded to the chain or to another chain of the same rank.
find_principal_chainchooses the chain of a cyclic molecule with the ring atoms excluded, and its criteria 8 and 9 and its tie-break then skip every ring atom: a ring bonded to a chain atom is never a prefix there, and a branch that reaches a ring is named only up to the ring (‘methyl’ for ‘(3-nitrophenyl)methyl’). The book counts, locates and orders a ring prefix like any other:, “the maximum number of substituents cited as prefixes” (the Blue Book; the chain parent of example (4),:21624, counts its ring substituent), (:21698), and (:21791), “the lower locant or set of locants for substituents cited as prefixes… in their order of citation in the name” (‘3-bromo-2-(2-bromo-1-hydroxyethyl)-4- hydroxybutanoic acid (PIN)’,:22108). So forN#CC(CO)Cc1cccc([N+](=O)[O-])c1the selector keeps the CH2OH chain, ‘3-hydroxy-2-[(3-nitrophenyl)methyl]propanenitrile’ (prefix locants 3,2 in citation order), while the PIN is ‘2-(hydroxymethyl)-3-(3-nitrophenyl) propanenitrile’ (2,3); and forOC(=O)C(C)Cc1ccccc1it gives ‘2-benzylpropanoic acid’ (one prefix) for the PIN ‘2-methyl-3- phenylpropanoic acid’ (two).The same selector, asked to count ring prefixes (
ring_prefixes=True), answers which chain makes senior. Returns'senior_alternative'when that is another chain of the same length (a name built on the features’ chain is not the PIN),'undecided'when the comparison cannot be made (a branch without a name in a tie, two different chains that still tie, or an error), and None when the chain stands or the check does not apply (no ring, a ring parent, or a chain this selector did not choose: one with a heteroatom, or of another length). The answer is kept onfeatures, keyed by its chain and principal group.
- orthonym.perception.chains.principal_chain_with_ring_prefixes(features)#
The principal chain of a cyclic molecule as chooses it: the chain
find_principal_chaingives with the ring atoms excluded from the chain and counted, located and ordered as prefixes (ring_prefixes=True).(the Blue Book) ‘The preferred IUPAC name is based on the senior
parent structure that has the maximum number of substituents cited as prefixes’ (the hydro prefixes excepted, as the rule says); its example (4),:21624, counts the ring substituent of the parent chain; (:21698), (:21791). With the ring atoms left out of the count, two chains of the same length bearing one prefix each tie and the input’s atom order decides:
COC(=O)C(C)Cc1ccccc1(methyl 2-methyl-3-phenylpropanoate, the PIN) gave ‘methyl 2-benzylpropanoate’ for some spellings.None when the comparison cannot be made (a branch without a name in a tie, two different chains that still tie, no chain, an error, or the call is made while the branches of another comparison are being named:
_P45_NAMING_BRANCHES); the caller then keeps the chain of the call without ring prefixes. The answer is kept onfeatures, keyed by the principal group and the ring atoms, so that the check of a chain parent (chain_parent_prefix_seniority()) does not make it twice.
- orthonym.perception.chains.amine_parent_tie(features)#
True when the parent of a secondary or tertiary amine is a choice among structurally different chains through its nitrogen that tie on and on and: the same number of prefixes and the same locants, so that only the alphanumerical key of the prefixes, the Blue Book) told them apart. (:21791) “the lower locant or set of locants for substituents cited as prefixes… in their order of citation in the name” reads the N-locants of the nitrogen’s prefixes too, and N is lower than a numeral,:3195); the key does not, so the choice is not made by the rule: the name is not certified as the PIN.
(4-bromo-3-nitrophenyl)-N-[(2-fluorophenyl)methyl]-N- methylmethanamineis such a choice between the two benzylic carbons,N-benzyl(chloro)-N-methylmethanamineanother, between the CH2Cl and the CH2Ph. Two chains that a symmetry of the molecule maps onto each other (the two benzylic carbons of dibenzylmethylamine) are one chain and no tie. False for a group that is not a secondary or tertiary amine and for a cyclic molecule whose parent is not a chain. A molecule without a ring has no ring prefix to count: the chain selector’s own last key decides between its chains (ClCN(C)CBr: the CH2Cl and the CH2Br).
- orthonym.perception.chains.GIVEN_CHAIN_GROUPS = frozenset({'acid_bromide', 'acid_chloride', 'aldehyde', 'amidine', 'carboxylic_acid', 'ester', 'hydrazide', 'imide', 'imine', 'ketone', 'nitrile', 'peroxy_acid', 'primary_alcohol', 'primary_amide', 'primary_amine', 'primary_sulfonamide', 'secondary_alcohol', 'secondary_amide', 'secondary_amine', 'sulfonic_acid', 'tertiary_alcohol', 'tertiary_amide', 'tertiary_amine', 'thioamide', 'thioic_S_acid', 'thiol'})#
Principal groups whose producer builds the name on
features.principal_chain, the chain it is given, and for which:func:p45_principal_chain hands parent selection the chain makes senior. Each group is here because 20 random SMILES orders of a molecule with a tie between two chains of one length (CC(Cc1ccccc1)Xwith X the group, a ring on one of the chains) gave the PIN spelling, labelled pin_verified, for every order, where the chain without ring prefixes gave ‘benzyl’ spellings for some orders (tests/unit/namer/test_leads_l1_n8b_chain_prefixes.py). A group that is not here keeps the chain of the call without ring prefixes, as before; among them are the producers that choose the chain themselves: the thio-, seleno- and telluroester handler, the oxime handler (‘CC(Cc1ccccc1)C=NO’ gives ‘2-benzyl-N-hydroxypropan-1-imine’ for every order, labelled pin_verified, as before), the imidate and anhydride producers. The ester’s producer walked the acid fragment itself (rules/esters.py:_find_acid_principal_chain, breadth first, the first longest path wins, so a tie was decided by the input’s atom order) and takes the chain of:func:p45_acid_chain now. A group joins the set when its producer takes the chain it is given.
- orthonym.perception.chains.p45_pending(features, chain)#
True when
namer.Orthonym._classifycompares the chain it found with the chain makes senior (p45_principal_chain()): the group’s producer builds on the chain it is given (GIVEN_CHAIN_GROUPS) and the chain has two carbons or more, or is one carbon of a secondary or tertiary amine. The nitrogen of such an amine is bonded to several carbons, and the one-carbon chains through them tie on the length “SENIORITY OF ACYCLIC CHAINS (THE PRINCIPAL CHAIN)”, the Blue Book, “(b) has the greater number of skeletal atoms”) and, with a ring bonded to the chain left out of the count, on the prefixes they carry: ‘CN(C)Cc1ccccc1’ has the chains of its two methyls and of its benzylic carbon, which the selector told apart by the order of the atoms. (:21604, “the maximum number of substituents cited as prefixes”) makes the benzylic carbon the parent, ‘N,N-dimethyl(phenyl)methanamine’ (three prefixes), not ‘N-benzyl-N-methylmethanamine’ (two).
- orthonym.perception.chains.p45_principal_chain(features, default_chain)#
The chain parent selection is given for a cyclic molecule:
default_chain(find_principal_chainwith the ring atoms excluded and not counted) replaced by the chain makes senior (principal_chain_with_ring_prefixes()) when that is a chain of the same length and the group’s producer builds on the chain it is given (GIVEN_CHAIN_GROUPS).default_chainstands where the comparison is undecided too.namer.Orthonym._classifymakes the comparison after parent selection and only for a chain parent: a ring parent never reads the chain, and counting the ring branches of a large ring system names whole macrocyclic branches. A fragment named inside another name takes it like the whole (the string of a part built on another chain than the whole’s would be recorded as a non-PIN part of the name that contains it: measured, ‘2-hydroxyethyl 2-methyl-3-phenylpropanoate’, whose acid is a nested name, labelled systematic_verified for the 7 of 10 orders in which it was pin_verified), except a branch that another comparison names as a sort key (_P45_NAMING_BRANCHES).
- orthonym.perception.chains.p45_acid_chain(mol, acid_atoms, carbonyl_c)#
The acid chain of an ester as chooses it: the chain
find_principal_chaingives with the ring atoms and the alkyl side left out and the ring prefixes counted (ring_prefixes=True), carbonyl carbon first; None when it cannot be decided or does not start atcarbonyl_c(the caller keeps its own walk).‘Maximum number of substituents cited as prefixes’ (the Blue Book): of two
chains of one length the one with the ring branches counted as prefixes is the parent.
rules/esters.py:_find_acid_principal_chainwalks the acid fragment breadth first and the first longest path wins, so a tie between a methyl and a ring-bearing CH2 on one carbon was decided by the input’s atom order (‘methyl 2-benzylpropanoate’ for 7 of 20 orders of ‘COC(=O)C(C)Cc1ccccc1’).
- 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.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)}