orthonym.rules.esters#
Note
Internal API. Names and behaviour may change between releases.
Ester naming following IUPAC two-component format.
Esters are named as “alkyl alkanoate” where: - alkyl: derived from the alcohol portion (attached to ester oxygen) - alkanoate: derived from the acid portion (contains C=O)
- Example: CH3-COO-CH2-CH3 -> “ethyl acetate”
(acetate from acetic acid, ethyl from ethanol)
- SMARTS: “[CX3](=O)[OX2][#6]”
Position 0: carbonyl carbon (acid side)
Position 1: carbonyl oxygen (=O)
Position 2: ester oxygen (-O-)
Position 3: first alkyl carbon (alcohol side)
- orthonym.rules.esters.parse_ester_fragments(mol, ester_match)#
Split ester into acid and alkyl fragments.
- Parameters:
mol – RDKit Mol object
ester_match (tuple) – Tuple of atom indices from ester SMARTS match (carbonyl_c, carbonyl_o, ester_o, alkyl_c)
- Returns:
Tuple of (acid_atoms, alkyl_atoms) where each is a list of atom indices
- Return type:
Tuple[List[int], List[int]]
The acid fragment includes the carbonyl carbon and everything attached to it except via the ester oxygen. The alkyl fragment includes everything attached to the ester oxygen except the carbonyl carbon.
- orthonym.rules.esters.acid_fragment_has_ring(mol, acid_atoms)#
Check if the acid fragment of an ester contains ring atoms.
When the acid portion contains a ring (e.g., benzoate, indole-carboxylate), simple carbon-counting is wrong – the ring atoms must not be linearized.
- Parameters:
mol – RDKit Mol object
acid_atoms (List[int]) – Atom indices of the acid fragment
- Returns:
True if any acid atom is in a ring
- Return type:
bool
- orthonym.rules.esters.acid_is_ring_acid(mol, acid_atoms)#
True iff the acid’s carbonyl carbon is DIRECTLY bonded to a ring atom.
task 9: only then do the ring-acid forms apply (benzoic,
cyclohexanecarboxylic —. An acid fragment that merely CONTAINS a ring further down the chain (cyclohexyl-CH2CH2CH2-COO-) is a CHAIN acid with a ring substituent (‘4-cyclohexylbutanoate’); naming it ‘cyclohexanecarboxylate’ described a different molecule.
- orthonym.rules.esters.get_ring_acid_name(mol, acid_atoms)#
Name the acid portion of an ester when it contains a ring.
Handles cases like: - Benzene + COOH -> “benzoic” (trivial) - Ring + COOH -> “[ring-name]carboxylic” (systematic)
- Parameters:
mol – RDKit Mol object
acid_atoms (List[int]) – Atom indices of the acid fragment
- Returns:
Acid name stem (e.g., “benzoic”), or None if cannot determine
- Return type:
str | None
- orthonym.rules.esters.get_acid_fragment_name(mol, acid_atoms)#
Get the acid name from the acid fragment.
For simple chain acids, determines chain length and returns systematic name. For recognized trivial acids, returns trivial name.
- Parameters:
mol – RDKit Mol object
acid_atoms (List[int]) – Atom indices of the acid fragment
- Returns:
Acid stem name (e.g., “acetic”, “propanoic”, “benzoic”)
- Return type:
str
- orthonym.rules.esters.get_alkyl_fragment_name(mol, alkyl_atoms)#
Get the alkyl name from the alkyl fragment.
For simple chains, returns methyl, ethyl, propyl, etc. When the alkyl fragment contains a ring, tries to name it properly (e.g., “phenyl” for benzene, “cyclohexyl” for cyclohexane).
- Parameters:
mol – RDKit Mol object
alkyl_atoms (List[int]) – Atom indices of the alkyl fragment
- Returns:
Alkyl name (e.g., “methyl”, “ethyl”, “phenyl”)
- Return type:
str
- orthonym.rules.esters.is_lactone(mol, ester_match)#
Check if the ester is a lactone (cyclic ester).
Lactones have the carbonyl carbon and an alkyl carbon in the same ring.
- orthonym.rules.esters.name_ester(mol, ester_match)#
Generate IUPAC name for an ester.
Format: “alkyl alkanoate” (e.g., “methyl acetate”, “ethyl propanoate”)
For ring-containing acid portions (e.g., methyl benzoate), the ring acid naming is used to produce correct names like “methyl benzoate” instead of incorrectly linearizing ring atoms (“methyl heptanoate”).
- Parameters:
mol – RDKit Mol object
ester_match (tuple) – Tuple from ester SMARTS match
- Returns:
Ester name string, or None if cannot be named (e.g., lactone, complex ring)
- Return type:
str | None
- orthonym.rules.esters.find_ester_match(mol)#
Find the first ester match in a molecule.
- Parameters:
mol – RDKit Mol object
- Returns:
Tuple of atom indices for the ester, or None if no ester found
- Return type:
tuple | None
- orthonym.rules.esters.name_noncarbon_ester(mol, match)#
Name an ester whose acid OR alcohol component is not the ordinary carbon-on-oxygen carboxylic ester / /:
- pseudoester R-CO-O-Z (Z a Group-13/14/15 organyl) -> ‘Zyl acylate’
(CH3-CO-O-Si(CH3)3 -> ‘trimethylsilyl acetate’)
- sulfonic ester R-SO2-O-R’ -> ‘R’yl R-sulfonate’
(CH3-SO2-O-CH3 -> ‘methyl methanesulfonate’)
sulfinic ester R-S(=O)-O-R’ -> ‘R’yl R-sulfinate’
Single mechanism (root-cause, not a per-class string surgery): the acid center is
match[0](a carbonyl C or an S). Locate the ester oxygen and the O-side organyl structurally, sever the ester bond to recover the NEUTRAL free acid, name it through the general pipeline (name_compound— handles retained/systematic, ring, unsaturated, substituted acids), convert the ‘-ic acid’ ending to the ‘-ate’ anion stem (_acid_name_to_ate), and name the O-side organyl vianame_substituent(yields ‘methyl’ / ‘trimethylsilyl’ / ‘ethyl’). Compose ‘<organyl> <acid>ate’. Fail-closed (return None) on any unnameable component so the caller cascade-continues.match[0]= acid center; the ester oxygen is the single-bonded O on the acid center whose OTHER heavy neighbour is the organyl.
- orthonym.rules.esters.get_acyloxy_prefix(acid_name)#
Convert an acid name to its acyloxy prefix form (IUPAC.
Used when an ester group is named as a substituent prefix rather than the principal characteristic group. The R-CO-O- portion becomes an “acyloxy” prefix.
Conversion rule: drop “-ic” (or “-ic acid”), add “-yloxy”.
⚠ This is a SPELLING converter, not the PIN decision point. It converts the stem its caller already chose; whether that stem is preferred is decided by get_acid_fragment_name. See the note on TRIVIAL_ACID_TO_ACYLOXY above – the table deliberately retains non-PIN general-nomenclature spellings, and they are unreachable from the namer.
- Parameters:
acid_name (str) – The acid name without “acid” suffix (e.g., “acetic”, “propanoic”, “benzoic”)
- Returns:
The acyloxy prefix (e.g., “acetyloxy”, “propanoyloxy”, “benzoyloxy”)
- Return type:
str
Examples
>>> get_acyloxy_prefix("acetic") 'acetyloxy' >>> get_acyloxy_prefix("propanoic") 'propanoyloxy' >>> get_acyloxy_prefix("benzoic") 'benzoyloxy' >>> get_acyloxy_prefix("formic") 'formyloxy'
- orthonym.rules.esters.name_ester_as_prefix(mol, ester_match)#
Generate an acyloxy prefix name for an ester group.
Used when the ester is a substituent (not the principal characteristic group). Extracts the acid fragment, determines its name, and converts to the acyloxy prefix form.
For branched acid fragments, the principal chain is used for the acid stem and branch substituents are included as prefixes in the acyloxy name (IUPAC.
- Parameters:
mol – RDKit Mol object
ester_match (tuple) – Tuple of atom indices from ester SMARTS match
- Returns:
Acyloxy prefix string (e.g., “acetyloxy”, “propanoyloxy”, “(2-methylpropanoyl)oxy” for branched acids), or None if the ester is a lactone or cannot be named.
- Return type:
str | None
Examples
For methyl acetate (COC(C)=O), returns “acetyloxy” For methyl propanoate (COC(=O)CC), returns “propanoyloxy” For isobutyrate ester (OC(=O)C(C)C), returns “(2-methylpropanoyl)oxy”
- orthonym.rules.esters.detect_exocyclic_esters(mol)#
Detect ester groups where the ester oxygen is bonded to a ring atom.
These are “exocyclic” esters: the ring is the parent structure and the ester should be named as an acyloxy prefix on the ring.
For example, cyclohexyl acetate (CC(=O)OC1CCCCC1) has an ester oxygen bonded to a cyclohexane ring carbon. The ester should be named as “acetyloxy” prefix on cyclohexane.
Excludes lactones (cyclic esters where both the carbonyl C and alkyl C are in the same ring).
- Parameters:
mol – RDKit Mol object
- Returns:
List of dicts, each containing – - ester_match: tuple of atom indices from SMARTS match - ring_attach_atom_idx: index of the ring atom bonded to ester O - acyloxy_prefix: the acyloxy prefix string (e.g., “acetyloxy”)
- Return type:
List[dict]
Examples
- For cyclohexyl acetate: returns [{“ester_match”: (…),
“ring_attach_atom_idx”: 3, “acyloxy_prefix”: “acetyloxy”}]
For ethyl acetate: returns (no ring attachment)
- orthonym.rules.esters.classify_multi_ester(mol, ester_matches)#
Classify a multi-ester compound by its structural pattern.
- Parameters:
mol – RDKit Mol object
ester_matches (list) – List of ester SMARTS match tuples (carbonyl_c, carbonyl_o, ester_o, alkyl_c)
- Returns:
Classification string –
“single”: only one ester match
”dicarboxylic_diester”: two esters sharing a diacid backbone
”polyol_polyester”: multiple esters on a polyol (handled in plan 41-02)
”independent”: multiple esters with no shared backbone
- Return type:
str
- orthonym.rules.esters.name_independent_esters(mol, ester_matches)#
Name a molecule with independent ester groups (IUPAC.
Independent esters are multiple ester groups that do not share an acid backbone (not dicarboxylic diester) or alcohol backbone (not polyol polyester). The most senior ester bond becomes the principal suffix (-oate) and the remaining ester bonds become acyloxy prefixes.
- Strategy:
Score each ester by acid fragment size (largest acid = principal).
The principal ester uses standard “alkyl [parent]oate” format.
Non-principal esters become acyloxy prefixes on the parent chain.
If the molecule is too complex, return None (decomposition handles it).
- Parameters:
mol – RDKit Mol object
ester_matches (list) – List of ester SMARTS match tuples (carbonyl_c, carbonyl_o, ester_o, alkyl_c)
- Returns:
Name string, or None if the molecule is too complex for this handler.
- Return type:
str | None
- orthonym.rules.esters.name_polyfunctional_ester_via_acid(mol, ester_match, verified_acid=False)#
Name a polyfunctional compound whose most-senior group is a SINGLE ester.
The ester stays the principal group (suffix ‘-oate’); every junior group (acyl halide -> oxo+halo, ketone/aldehyde -> oxo, nitrile -> cyano, -OH -> hydroxy,…) is a prefix on the acid-side chain (IUPAC +: esters outrank acyl halides/amides/nitriles/aldehydes/ketones/alcohols).
Strategy (mirrors the diester ring path): build the ACID analog (ester -> free -COOH), name it via the GENERAL pipeline (which already emits those junior groups as prefixes), then convert ‘-ic acid’ -> ‘-ate’ and prepend the alkyl group as a separate word. The junior groups must lie on the ACID side; the removed alkyl side must be a plain hydrocarbon. Fail-closed.
- orthonym.rules.esters.name_polyfunctional_diester_free_hydroxy(mol, ester_matches, principal_chain)#
(BB verbatim worked example at: ‘2-(acetyl- oxy)-3-(hexadecanoyloxy)propyl (9Z)-octadec-9-enoate’) + (greater number of skeletal atoms): a partially-esterified acyclic polyol bearing exactly TWO different noncyclic ester groups plus >=1 free hydroxyl, all on the SAME short saturated carbon backbone (the diacylglycerol shape).
The senior acid (the ester whose acid-side principal chain has MORE carbons – seniority of chains) stays the functional-class parent (‘<yl> <acid>oate’); the OTHER ester is demoted to an ‘acyloxy’ prefix and the free hydroxyl(s) to ‘hydroxy’ prefixes, both cited on the ‘yl’ word, exactly as ‘s worked examples do it.
a phase: this closes the gap where ``name_polyfunctional_ester_via_
acid`` (the single-ester acid-analog strategy) declines outright for >1 ester match, and the legacy fallback in
rules/polyfunctional.py::name_polyfunctionalthen demoted BOTH esters and promoted the junior hydroxy class to principal – inverting(ester class 9 outranks hydroxy class 17).
- Fail-closed scope (returns None – caller falls through to the legacy
acyloxy-all/’-ol’ demotion, which is uglier but not wrong – for
- anything broader):
exactly 2 ester matches;
principal_chain is a plain acyclic, saturated, all-carbon chain;
BOTH esters’ alkyl (alcohol-side) attachment atoms sit ON that chain, and neither acid is a ring acid;
every atom of principal_chain not consumed by an ester attachment is either UNDECORATED or bears exactly one free hydroxyl (any other decoration – halogen, amine, a third ester,… – declines);
the two acids’ principal-chain lengths are NOT tied (a tie is outside this narrow scope).
- orthonym.rules.esters.name_dicarboxylic_diester(mol, ester_matches)#
Name a dicarboxylic acid diester compound.
Produces names in the format “[multiplier]alkyl [parent]anedioate”. If the two alkyl groups differ, they are listed in alphabetical order.
- Parameters:
mol – RDKit Mol object
ester_matches (list) – List of exactly 2 ester SMARTS match tuples
- Returns:
Name string (e.g., “dimethyl propanedioate”), or None on failure.
- Return type:
str | None
- orthonym.rules.esters.ester_carbonyl_atoms(mol)#
The carbonyl carbons of the carboxylic ester groups C(=O)-O-C of
mol: not a lactone (carbonyl carbon and ester oxygen in one ring), not an anhydride (the ‘alkyl’ carbon is itself an acyl carbon).
True when the acid carbons
c0andc1of two ester groups belong to ONE acid component whose parent can carry both as suffixes, the Blue Book: 18875 “The senior parent structure has the maximum number of substituents corresponding to the principal characteristic group (suffix)”): they are bonded (‘oxalate’), or joined by a path of acyclic carbon atoms (a chain through both: ‘…dioate’), or each bonded to an atom of the same ring system (’…-1,2- dicarboxylate’). A path through a heteroatom, or from a ring into a side chain, gives no such parent (‘4-(2-methoxy-2-oxoethyl)benzoate’ stays an ester prefix, ,:31698).
True when another ester group’s acid carbon shares
carbonyl_c’s acid parent (ester_carbonyls_share_an_acid_parent): the molecule is then an ester of one polyacid, and a name citing onlycarbonyl_c’s ester as the functional-class ester (the other as an ‘(alkoxycarbonyl)’ prefix) is not its PIN, the Blue Book “Fully esterified acids derived from a single acid are systematically named by placing the name(s) of the hydroxylic component… in front of the name of the acid component”;:31775 ‘dimethyl butanedioate (PIN)’).
- orthonym.rules.esters.record_polyacid_monoester_non_pin(mol, carbonyl_c, name)#
Label
namebelow the PIN (metrics.provenance.record_non_pin_label) when it namesmolas a mono-ester ofcarbonyl_c’s acid although the molecule is an ester of one polyacid (ester_shares_its_acid_with_another_ ester). The name is unchanged; the round trip still judges it.
- orthonym.rules.esters.name_polyacid_polyester(mol)#
(the Blue Book): the fully esterified ester of ONE polyacid, named ‘dimethyl 2-methylbutanedioate’ – the organyl word, multiplied, in front of the anion name of the acid component.
The acid component is built from structure (every ester alkyl stripped,
_build_diacid_from_diester) and named by the strict pipeline, exactly as the ring dicarboxylate path does (_name_ring_dicarboxylic_diester); its name must carry EVERY acid group as a suffix (’…dioic acid’, ‘…tricarboxylic acid’), else the parent is not one polyacid parent and this declines. Scope, fail-closed: every ester group of the molecule shares one acid parent, no free acid group, all organyl groups identical and stereo-free (different organyl groups need the alphanumerical order and, when necessary, locants,:31769), 2-4 esters.
- orthonym.rules.esters.name_symmetric_multiplicative_diacid_diester(mol, ester_matches)#
– functional-class MULTIPLICATIVE ester name for a fully esterified, SYMMETRIC di-ester built from two identical DIBASIC acid units bridged by one central symmetric divalent diol and capped by two identical monovalent alcohols, e.g.
CH3-O-CO-CH2-CH2-CO-O-CH2-CH2-O-CO-CH2-CH2-CO-O-CH3 -> ‘dimethyl ethane-1,2-diyl dibutanedioate’ (PIN)
(the Blue Book) “Polyester names formed by using
functional class multiplicative nomenclature”: “Symmetrical esters are named by including the organyl constituent in the multiplied anion component name.” The bi-/polyvalent central group is cited as the LAST organyl group, as a separate word immediately before the multiplied anion name, the Blue Book); the monovalent terminal organyl groups are cited first.
This is the PIN ONLY because no senior characteristic group survives – every acid is esterified, so the seniority layer sets principal_group == “ester” and the molecule reaches the ester dispatcher at all. When a free -COOH (or any group senior to the ester) survives, principal_group is that group, the molecule never reaches here, and the substitutive acyloxy-prefix name stays the PIN, the Blue Book). This builder therefore cannot flip a surviving-suffix ester row.
Fail-closed scope (returns None -> caller falls through to the existing substitutive/independent cascade UNCHANGED): anything but exactly two identical clean-linear-saturated dibasic acid units + exactly one clean acyclic symmetric divalent diol bridging them + exactly two identical monovalent terminal alcohols. The constructed name is finally RT-gated (OPSIN offer): only returned when it round-trips to the input structure, so it is 0-wrong and never relaxes anything without a working OPSIN.
- orthonym.rules.esters.name_polyol_polyester(mol, ester_matches)#
Name a fully-esterified polyol compound using acyloxy prefixes.
Identifies the polyol backbone, determines acyloxy prefixes for each ester group, and assembles the name with locants and multipliers.
- Parameters:
mol – RDKit Mol object
ester_matches (list) – List of ester SMARTS match tuples (carbonyl_c, carbonyl_o, ester_o, alkyl_c)
- Returns:
Name string (e.g., “1,2,3-tri(acetyloxy)propane”), or None on failure.
- Return type:
str | None