orthonym.rules.ions#

Note

Internal API. Names and behaviour may change between releases.

Ion naming rules per IUPAC 2013.

Handles naming of: - Anions: carboxylates (-ate), alkoxides (-olate), phenolates, aminides, carbanions (-ide) - Cations: aminium (-aminium), carbenium/ylium (-ylium), onium, diazonium

IUPAC 2013 References: -: Anion nomenclature -: Cation nomenclature

Key naming patterns: - Carboxylate anions: acetic acid -> acetate - Alkoxide anions: methanol -> methanolate (PIN) or methoxide (acceptable) - Aminium cations: amine -> aminium (protonated amine) - Carbenium cations: alkane -> ylium (loss of H-)

orthonym.rules.ions.classify_anion(mol, anion_site)#

Classify anion type based on the anionic atom environment.

Examines the local chemical environment of the anionic atom to determine the appropriate naming suffix.

Parameters:
  • mol – RDKit Mol object

  • anion_site (Dict[str, Any]) – Dictionary from get_ion_sites containing: - atom_idx: int - charge: int - element: str - hybridization: str - n_hydrogens: int

Returns:

One of –

‘carboxylate’, ‘alkoxide’, ‘phenolate’, ‘aminide’,

’carbanion’, ‘thiolate’, or ‘unknown’

Return type:

str

Example

>>> mol = Chem.MolFromSmiles('CC(=O)[O-]')
>>> sites = get_ion_sites(mol)
>>> classify_anion(mol, sites['anions'][0])
'carboxylate'
orthonym.rules.ions.classify_cation(mol, cation_site)#

Classify cation type based on the cationic atom environment.

Examines the local chemical environment of the cationic atom to determine the appropriate naming suffix.

Parameters:
  • mol – RDKit Mol object

  • cation_site (Dict[str, Any]) – Dictionary from get_ion_sites containing: - atom_idx: int - charge: int - element: str - hybridization: str - n_hydrogens: int

Returns:

One of – ‘aminium’, ‘ylium’, ‘onium’, ‘diazonium’, or ‘unknown’

Return type:

str

Example

>>> mol = Chem.MolFromSmiles('[NH4+]')
>>> sites = get_ion_sites(mol)
>>> classify_cation(mol, sites['cations'][0])
'aminium'
orthonym.rules.ions.name_carboxylate_anion(parent_name)#

Convert carboxylic acid name to carboxylate anion name.

Transformation rules: - ‘ic acid’ -> ‘ate’ (e.g., acetic acid -> acetate) - ‘oic acid’ -> ‘oate’ (e.g., propanoic acid -> propanoate)

Parameters:

parent_name (str) – Parent acid name (e.g., ‘acetic acid’, ‘propanoic acid’)

Returns:

Anion name (e.g., ‘acetate’, ‘propanoate’)

Return type:

str

Example

>>> name_carboxylate_anion('acetic acid')
'acetate'
>>> name_carboxylate_anion('propanoic acid')
'propanoate'
orthonym.rules.ions.name_alkoxide_anion(parent_name, style='pin')#

Convert alcohol name to alkoxide anion name.

IUPAC 2013 PIN style uses -olate (methanolate, ethanolate). Common style uses -oxide (methoxide, ethoxide).

Parameters:
  • parent_name (str) – Parent alcohol name (e.g., ‘methanol’, ‘ethanol’)

  • style (str) – ‘pin’ for -olate, ‘common’ for -oxide

Returns:

Anion name

Return type:

str

Example

>>> name_alkoxide_anion('methanol')
'methanolate'
>>> name_alkoxide_anion('methanol', style='common')
'methoxide'
orthonym.rules.ions.name_phenolate_anion(parent_name)#

Convert phenol name to phenolate anion name.

Parameters:

parent_name (str) – Parent phenol name (e.g., ‘phenol’)

Returns:

Anion name (e.g., ‘phenolate’)

Return type:

str

Example

>>> name_phenolate_anion('phenol')
'phenolate'
orthonym.rules.ions.name_aminium_cation(parent_name)#

Convert amine name to aminium cation name.

Parameters:

parent_name (str) – Parent amine name (e.g., ‘methanamine’, ‘ethylamine’)

Returns:

Cation name (e.g., ‘methanaminium’, ‘ethylaminium’)

Return type:

str

Example

>>> name_aminium_cation('methanamine')
'methanaminium'
>>> name_aminium_cation('ammonia')
'ammonium'
orthonym.rules.ions.name_carbenium_cation(parent_name)#

Convert alkane/alkyl name to carbenium (ylium) cation name.

The ylium suffix indicates loss of hydride (H-) from the parent.

Parameters:

parent_name (str) – Parent name (e.g., ‘methane’, ‘methyl’)

Returns:

Cation name (e.g., ‘methylium’)

Return type:

str

Example

>>> name_carbenium_cation('methane')
'methylium'
>>> name_carbenium_cation('ethane')
'ethylium'
orthonym.rules.ions.get_anion_suffix(anion_type)#

Get the appropriate suffix for an anion type.

Parameters:

anion_type (str) – Type from classify_anion

Returns:

Suffix string (e.g., ‘ate’, ‘olate’, ‘ide’)

Return type:

str

orthonym.rules.ions.get_cation_suffix(cation_type)#

Get the appropriate suffix for a cation type.

Parameters:

cation_type (str) – Type from classify_cation

Returns:

Suffix string (e.g., ‘ium’, ‘ylium’, ‘aminium’)

Return type:

str

orthonym.rules.ions.emit_bis_quaternary_aminium(mol, cation_sites)#

Substitutive ‘-bis(aminium)’ PIN for two quaternary ammonium centres on the ends of a straight carbon chain:

C[N+](C)(C)CCCCCC[N+](C)(C)C
    -> N1,N1,N1,N6,N6,N6-hexamethylhexane-1,6-bis(aminium)

The rule: an N+ carrying carbon groups is a cationic AMINE, named by the cationic suffix ‘aminium’, the Blue Book; ‘N,*N*,*N*- trimethylmethanaminium (PIN)’:41438, not ‘tetramethylazanium’:41354). Two of them on one chain take the suffix twice: “Polycations with cationic centers on characteristic groups are named by substitutive nomenclature or multiplicative nomenclature” (:42138), with the quaternary ‘*N*^1,*N*^1,*N*^3,*N*^3,*N*^3-hexamethylpropanebis(amidium) (PIN)’ (:42154) and ‘butanebis(nitrilium) (PIN)’ beside the non-PIN multiplicative ‘butanediylidynebis(azanium)’ (:42160,:42162); ‘3-(azaniumylmethyl) pentane-1,5-bis(aminium) (PIN)’ (:42366). The chain between the two N is the parent: it is the only chain that carries both suffixes. The N-substituents take N-locants with the chain locant as superscript (written ‘N1’, ‘N6’;:26348), in alphanumerical order.

SCOPE (fail closed, ‘’ on anything else): exactly two acyclic N+ with no H and four carbon neighbours; one unbranched saturated all-carbon bridge of >= 2 atoms between them (_walk_linear_carbon_bridge); the same multiset of N-substituent names on both ends, so the bridge’s two numbering directions are equivalent. The name ships only on a full-InChIKey round trip.

orthonym.rules.ions.emit_bis_quaternary_ammonium(mol, cation_sites)#

/ (a phase): a SYMMETRIC dication with exactly two IDENTICAL quaternary-ammonium centres joined by a straight, saturated, unbranched, unsubstituted all-carbon bridge is named as a multiplicative assembly of parent cations:

{bridge parent-diyl}bis({onium unit})

C[N+](C)(C)CCCCCC[N+](C)(C)C
    -> hexane-1,6-diylbis(trimethylazanium)

(BB, cf. the PIN example ‘(1,4-phenylene)bis(phosphanium)’ and ‘4,4′-(ethane-1,2-diyl)bis(1-methylpyridin-1-ium)’.)

SCOPE (fail closed, i.e. return ‘’, on anything else — asymmetric onium substituents, >2 cations, a ring-borne cationic centre, or a branched/heteroatom-bearing/unsaturated bridge; the caller’s existing fallback then owns the molecule unchanged):

  • exactly 2 cation sites, each N, formal charge +1, 0 attached H, degree 4 (quaternary,, NOT in a ring.

  • the two nitrogens are joined by a SINGLE simple, unbranched, saturated, acyclic, all-carbon bridge (_walk_linear_carbon_bridge) of length >= 2 – this both bounds scope and guarantees the bridge is trivially symmetric (its two numbering directions are equivalent by construction, so locants 1 and n are forced with no orientation choice to make).

  • the two onium substituent sets (cation_to_prefix(..., as_free_ion= True) on each N, reusing the existing zwitterion-onium-prefix machinery) must be BYTE-IDENTICAL strings – the “two units identical” requirement.

orthonym.rules.ions.name_anion(mol, style='pin', _depth=0, retained_only=False)#

Generate IUPAC name for an anionic molecule.

Workflow: 1. Get canonical SMILES 2. Check retained names (unless systematic style) 3. Detect anion sites 4. Classify anion type 5. Generate systematic name

Parameters:
  • mol – RDKit Mol object (must have negative charge)

  • style (str) – Naming style (‘pin’, ‘systematic’, ‘common’)

  • _depth (int) – Internal recursion depth guard (do not set manually)

  • retained_only (bool) – If True, only check retained names and return None if no retained name found (used for namer.py fall-through).

Returns:

Anion name (e.g., ‘acetate’, ‘methoxide’, ‘phenolate’), or empty string if naming fails. When retained_only=True, returns None if no retained name found.

Return type:

str

Example

>>> mol = Chem.MolFromSmiles('CC(=O)[O-]')
>>> name_anion(mol)
'acetate'
orthonym.rules.ions.name_cation(mol, style='pin', _depth=0, retained_only=False)#

Generate IUPAC name for a cationic molecule.

Workflow: 1. Get canonical SMILES 2. Check retained names (unless systematic style) 3. Detect cation sites 4. Classify cation type 5. Generate systematic name

Parameters:
  • mol – RDKit Mol object (must have positive charge)

  • style (str) – Naming style (‘pin’, ‘systematic’, ‘common’)

  • _depth (int) – Internal recursion depth guard (do not set manually)

  • retained_only (bool) – If True, only check retained names and return None if no retained name found (used for namer.py fall-through).

Returns:

Cation name (e.g., ‘ammonium’, ‘methylammonium’, ‘methylium’), or empty string if naming fails. When retained_only=True, returns None if no retained name found.

Return type:

str

Example

>>> mol = Chem.MolFromSmiles('[NH4+]')
>>> name_cation(mol)
'ammonium'
orthonym.rules.ions.apply_ion_suffix_to_name(name, total_charge, allowed_suffixes=None, cation_class=None)#

The SINGLE ionic-suffix re-application primitive for every charged class.

Generalized from _ionize_acid_name (169.6-02 / CHOKE-02). Re-applies the class-correct ionic suffix to a re-entered NEUTRAL name (the output of Orthonym(style).name(neutral_smiles)). Drives the structured resolvers seam (_apply_anion_modification / _apply_cation_modification off the canonical _ANION_SUFFIX_MAP / _CATION_SUFFIX_MAP) — single source of truth, NEVER a per-molecule .replace band-aid (fix-methodology.md).

Two paths:

  1. cation_class in {ylium, acylium, diazonium} -> the class-keyed transform table _CLASS_KEYED_CATION_TRANSFORMS (these are NOT plain suffix swaps and depend on the neutralized form, internal notes):

    • ylium (carbenium): parent-hydride ane->``ylium`` (methane->``methylium``;; methylium is the PIN).

    • acylium: on the ACID name, oic acid->``oylium`` / carboxylic acid->``carbonylium``.

    • diazonium: append diazonium to the hydride name.

  2. Otherwise the generic map-driven path (anion/protonated-amine etc.): longest trailing neutral suffix in the relevant SUFFIX_MAP wins.

allowed_suffixes (GUARD 1 — the heptanolate fix): when given, ONLY those neutral suffixes are eligible for the trailing match, so a name ending “…sulfonic acid” with allowed_suffixes={"ol"} returns ‘’ (it cannot mis-fire to -olate). classify_anion passes the per-class subset so a sulfonate stem can never be mistaken for an alkoxide. Applies to the generic (map) path only; the class-keyed cation path is already class-gated.

Returns the ionized name, or ‘’ if no canonical transform applies (the caller then falls through to the existing cascade — byte-identical contract).

The ion keeps the neutral name’s PIN status: a recorded non-PIN fragment in name (decision A’s demoted ‘N-<acyl>’ float,…) does not survive the suffix swap, so the ionized name is recorded too (metrics.provenance.record_derived_non_pin_fragment).

orthonym.rules.ions.emit_parent_hydride_cumulative_suffix(mol, center_idx, suffix)#

Name a parent hydride with a cumulative anionic / radical / cationic suffix on a single carbon centre, with a FIRST-CLASS locant.

suffix in {‘ide’, ‘ium’, ‘yl’, ‘ylidene’, ‘ylidyne’}.

‘ide’ -> / Table 3.4 (anion, loss of H+) ‘yl’/’ylidene’/’ylidyne’ -> / Table 3.4 (radical, loss of H.) ‘ium’ -> (cation; reserved for later reuse)

center_idx is the atom index OF THE CHARGED/RADICAL CARBON ON mol (the index-preserving mol passed in — NOT a freshly canonicalized copy; canonical SMILES reorders atoms, RESEARCH Pitfall 1). The centre gets the LOWEST locant over the re-found chain, competing with unsaturation and substituents per

/ (orient_chain criterion (a), principal_group_atoms =

{center_idx}). This is a NEW numbering call (“locants identify positions of the negative charges”, the Blue Book lines 40876-40902), NOT a reused -ol / FG anchor and NOT a hand-rolled carbon-counting scan.

Returns ‘’ on any failure (caller falls through to legacy).

orthonym.rules.ions.emit_mono_ionized_polyfunctional(mol, ion_idx, kind)#

Name a mono-ionized ACYCLIC polyfunctional chain, demoting the identical NEUTRAL sibling groups to prefixes (see _MONO_IONIZED_SPEC / /.

ion_idx is the charged heteroatom index on the index-preserving mol. Fail-closed (’’ -> caller keeps the legacy single-group path) for any shape outside the tight scope: a ring, a substituted / secondary sibling, no sibling (the ordinary single-group case), an extra substituent on the chain, or a chain the acyclic machinery cannot number. Accuracy first: never emit a wrong name.

orthonym.rules.ions.emit_parent_hydride_polyvalent_suffixes(mol, centers)#

/ multi-site free-valence (polyradical) PIN on ONE acyclic all-carbon parent hydride.

centers = [(atom_idx, n_electrons)] with n_electrons in {1, 2, 3} and >= 2 DISTINCT centers (the single-center case is emit_parent_hydride_cumulative_suffix’s job). Returns the polyradical PIN (ethane-1,2-diyl, propane-1,2,3-triyl, pentane-2,4-diylidene, the mixed ethan-1-yl-2-ylidene, …) or '' (fail closed) on anything out of scope.

Generalizes the single-center primitive above to N carbon free-valence centers: saturate every centre on one index-preserving RWMol, choose the longest carbon chain carrying ALL centers (a): the parent must retain the maximum radical centers — demoting a centre to an off-parent prefix is out of v1 scope, so refuse rather than drop), then number by

— “low locants to the free valences as a SET, then in the order

‘yl’, ‘ylidene’, ‘ylidyne’”. Substituents/unsaturation reuse the same machinery as the single-center primitive. ‘’ on any unnameable piece.

orthonym.rules.ions.emit_poly_carbanion_ide(mol, centers)#

(BB 40902): a MULTI-carbanion on one acyclic all-carbon parent hydride — >= 2 carbon -ide centres — named with the ‘-ide’ suffix and the numerical multiplier ‘di’/’tri’: ‘ide’ takes basic multipliers). centers = the carbanion atom indices. Returns e.g. ethynediide ([C-]#[C-], BB 40918), methanediide ([CH2-2]), butane-1,4-diide, or ‘’ on any out-of-scope shape (fail closed).

Mirrors emit_parent_hydride_polyvalent_suffixes (saturate every centre on one index-preserving RWMol -> longest chain carrying ALL centres -> orient for lowest locants to the -ide set), but the ending is ‘-<locs>-<mult>ide’. Locant elision, ‘no ambiguity’): the anionic locants are OMITTED when they occupy EVERY carbon of the chain (a single unambiguous placement: ethyne->ethynediide, methane->methanediide); the unsaturation locant is likewise omitted for a 2-carbon parent (ethyne/ethene carry none).

orthonym.rules.ions.emit_poly_cation_ylium(mol, centers)#

(the Blue Book): a MULTI-carbenium on one acyclic all-carbon parent hydride — carbon -ylium free valences totalling >= 2 across ring-free carbons that each lost a hydride H- — named with the ‘-ylium’ compound suffix multiplied by ‘bis’/’tris’ (the Blue Book note: ‘bis(ylium)’ NOT ‘diylium’, so the COMPLEX multiplier series, mirroring the poly-aminium ‘bis(aminium)’ rule, not the basic ‘di’ the -ide anion uses). centers = the carbenium atom indices; a centre bearing charge +2 counts as TWO -ylium valences on one atom (the geminal degenerate, C[C+2]C). Returns e.g. ethane-1,2-bis(ylium) ([CH2+][CH2+]), propane-1,3-bis(ylium) ([CH2+]C[CH2+]), propane-2,2-bis(ylium) (C[C+2]C), or ‘’ on any out-of-scope shape (fail closed -> caller abstains, never a wrong bis-name).

Mirrors emit_poly_carbanion_ide (saturate every centre — +1 H per unit of positive charge — on one index-preserving RWMol -> longest chain carrying ALL centres -> orient for lowest locants to the -ylium set), but the ending is ‘-<locs>-<bis/tris>(ylium)’. Unlike the -ide anion, the ylium locants are ALWAYS cited (never elided): the examples keep them even when the set covers every carbon (ethane-1,2-bis(ylium), not ethanebis(ylium)).

orthonym.rules.ions.emit_halogen_onium(mol, center_idx)#

(the Blue Book “diphenyliodanium (PIN)”, the Blue Book “…are the preferred IUPAC names and not those given in Table 7.3”): name a disubstituted halogen(III) cation R2X+ on the halogen parent hydride with the ‘-ium’ suffix. The exact CATION mirror of _emit_group13_uide (which builds diphenyliodanuide on the anion side): ligands become prefixes on the ‘-ium’ parent cation.

(C6H5)2I+ -> diphenyliodanium (PIN) [the Blue Book]

Scope (fail closed, so a mono-coordinate halonium / a genuine radical cation / any non-halogen never matches): the centre is a halogen in _HALOGEN_ONIUM_STEMS (I/Br/Cl/F), formal charge +1, and degree >= 2 (a hypervalent halonium — a 1-coordinate R-X+ is a distinct sulfanylium-style species and is excluded). Every heavy atom must be covered by the emitted ligand set (atom-drop veto, identical to the uide emitter — classify_substituent names carbon groups by carbon count and silently drops a heteroatom in the branch, so a mismatch declines).

Named DIRECTLY: a drop-charge neutralize yields an invalid neutral halogen radical, and an add-H neutralize yields the λ-parent whose ‘-ium’ form (…-λ3-iodanium, a 4-coordinate [IH2+]) is NOT the PIN. No λ-convention is used — diphenyliodanium (the Blue Book) carries none, exactly as diphenyliodanuide (the Blue Book) carries none. Returns ‘’ on any decline (the caller falls through).

orthonym.rules.ions.emit_chalcogen_ylium(mol, center_idx)#

(BB 41565 phenylsulfanylium PIN): name a 1-coordinate chalcogen cation R-S+ / R-Se+ as <R-prefix>sulfanylium / <R-prefix>selanylium.

Scope (tight, so oxonium/sulfonium/phosphonium are never disturbed — they return ‘’ here): the centre is S or Se, formal charge +1, ZERO hydrogens, and exactly ONE heavy neighbour which is a CARBON substituent R (alkyl/aryl). R is named via classify_substituent; a complex R gets enclosing marks: (2,2-dichloroethyl)sulfanylium). Acyl R (R-CO-S+) and R-S-S+ (disulfanylium) are out of scope -> ‘’. Returns ‘’ on any decline (caller falls through).

orthonym.rules.ions.emit_onium_hydride_parent(mol, center_idx)#

(the Blue Book “Cationic compounds derived from… suffixes”; “ethylideneoxidanium (PIN)”:41463, “acetyloxidanium (PIN)”:41474): a single mononuclear chalcogen cation centre (O/S/Se/Te, +1, NOT in a ring, NOT bonded to another chalcogen) carrying substituents (alkyl / acyl / alkylidene) + optional remaining H is named on the mononuclear parent hydride oxidane/sulfane/selane/tellane + ‘-ium’, its substituents cited as detachable prefixes – the systematic PIN, NOT the Table-7.3 ‘-onium’ spelling , general only) nor the ‘-a’/’-onia’-replacement form: CC=[OH+] -> ethylideneoxidanium, not 1-oxaprop-1-en-1-ium.

Mirrors emit_halogen_onium / _emit_group13_uide in SHAPE: substituents named by TYPE from the bond order to the centre (classify_substituent mis-names the ylidene =CHCH3 and the acyl CH3CO- both ‘ethyl’), an atom-drop veto over the whole heavy-atom count, and ‘’ on ANY decline so the caller’s RT gate can never be handed a mis-built name. The substituent-prefix assembly (citation order, multipliers, and the enclosing marks that make ‘benzoyldi(methyl)sulfanium’, BB 16286 ‘tert-butyldi(methyl)phosphane’) follows the same rules as catenated_hydrides._try_aba_parent (called from name_heterochalcogen_aba) – the sibling substituent-prefix assembler – via the shared naming_utils primitives, so no ordering or marking rule is re-spelled here; the ~18-line assembly loop below is a PARALLEL implementation of that sequence (generalized from its local count table to get_multiplier_prefix), not a call into shared code – part of the deferred locant/prefix-render duplication (internal notes). A catenated chalcogen-chalcogen cation (dioxidane, disulfane; the ‘-C(=O)-O-[OH2+]’ -> dioxidan-1-ium family) is out of scope and declines. Returns ‘’ on any decline.

orthonym.rules.ions.emit_catenated_hydride_cation(mol, center_idx)#

(the Blue Book “General rule for systematically naming cationic centers in parent hydrides”; examples:41386-41390): a CATENATED homonuclear parent-hydride cation, or a SUBSTITUTED mononuclear pnictogen cation, named on the parent hydride + ‘-ium’ at the cationic-centre locant.

CN(C)[N+](C)(C)C -> pentamethylhydrazinium (PIN) CS[S+](C)SC -> 1,2,3-trimethyltrisulfan-2-ium (PIN) C[P+](C)(C)Cl -> chlorotri(methyl)phosphanium (PIN) C[P+](C)(C)P(Cl)Cl -> 2,2-dichloro-1,1,1-trimethyldiphosphan-1-ium (PIN) O=C(O[OH2+])c1ccccc1 -> 2-benzoyldioxidan-1-ium (PIN)

Approach (a trace-confirmed: neutralize-re-enter is structurally impossible for a 0-H fully-substituted onium/quaternary centre, and even where a neutral is formable the neutral namer names a DIFFERENT parent – benzenecarboperoxoic acid, not benzoyldioxidane): name the BARE homonuclear parent chain by re-entering the neutral namer (_bare_parent_hydride_name -> trisulfane / hydrazine / diphosphane / dioxidane / phosphane – so the multiplier / retained tables are NOT re-spelled here), number the chain giving the cationic centre the lowest locant /, cite the substituents as detachable prefixes, and append ‘-{loc}-ium’ with ‘e’-elision. Substituents named by TYPE (halogen / acyl-on-chalcogen / organyl) with a whole-heavy-atom drop veto; ‘’ on ANY decline so the caller’s RT gate never sees a mis-built name. A chalcogen MONONUCLEAR onium is out of scope (emit_onium_hydride_parent owns it) as is a mononuclear N (the aminium path).

The hydrazine (retained N-N parent) fully-substituted cation omits all locants per the example ‘pentamethylhydrazinium (PIN)’ (and: every substitutable position of hydrazinium bears the one identical group).

orthonym.rules.ions.emit_zwitterion_ring_carboxylate(mol, cation_idx, anion_idx)#

F- (DD3,: a zwitterion whose cationic centre is a RING atom of the same ring that bears a carboxylate anion -> the cumulative <prefixes><ring>-<cation-locant>-ium-<carboxyl-locant>-carboxylate name, the cationic suffix cited before the anionic one.

[O-]C(=O)c1ccc[nH+]c1 -> pyridin-1-ium-3-carboxylate C[n+]1ccccc1C(=O)[O-] -> 1-methylpyridin-1-ium-2-carboxylate C[n+]1c(C(=O)[O-])cc(-c2ccccc2)cc1-c1ccccc1 -> 1-methyl-4,6-diphenylpyridin-1-ium-2-carboxylate

the last being the Blue Book’s own worked (PIN) example, :42456, under ** “Zwitterionic compounds with at least one ionic center on a characteristic group”** (heading :42445).

ONE numbering for the whole name. This emitter previously ran two branches that each took the ring numbering from the namer’s neutral-ring supplier and then concatenated a separately-built cation-substituent prefix onto the resulting substituted ring name. Two different numberings met in one string. Measured consequences on the DEFAULT path, all of which round-trip cleanly through OPSIN — the molecule is right and only the spelling is wrong, so neither the round-trip metric nor could see them:

  • 1-methyl4-methylpyridin-1-ium-2-carboxylate — no hyphen, and 1-methyl + 4-methyl never merged to 1,4-dimethyl;

  • 1-methyl4-bromopyridin-1-ium-2-carboxylate — ‘bromo’ must precede ‘methyl’ alphanumerical order);

  • 1-methyl2,4-dimethylpyridin-1-ium-6-carboxylate — the carboxylate numbered 6 where :42447 requires 2.

and one that did catch, turning it into an abstention rather than a wrong molecule: 1-propylpyridin-1-ium-2-carboxylate for an isopropyl group, because the old branch named substituents with the carbon-counting classify_substituent.

So the numbering is now derived ONCE over the ORIGINAL charged mol, with the

anion-attachment criterion

(_charged_ring_locants(..., anion_attach_idx=...)); every ring substituent is located in THAT numbering by _p74_ring_substituent_prefix; and the stem comes from a ring stripped of all of them.

Fails closed (‘’) whenever a part is uncorroborated — a fused/bridged ring (no simple cycle, so no numbering), a substituent the authoritative namer cannot spell, or a formal charge anywhere outside the cation/anion pair. The caller then falls through to the legacy path.

orthonym.rules.ions.express_junior_anionic_chalcogens(mol, anions, ionized)#

The name of a multi-anion whose parent is an S/P-oxoacid anion and whose other anionic centres are chalcogen anions (-O-/-S-): the junior centres are cited by their anionic prefixes, ‘oxido’ / ‘sulfido’,:41223 “–O– oxido (preselected prefix)”,:41225 “–S– sulfido (preselected prefix)”), never by the neutral ‘hydroxy’ / ‘sulfanyl’, which drops a charge and denotes a different molecule (‘5-hydroxybenzene-1,3-disulfonate’ is the dianion, not the trianion).

ionized is the neutral acid name with its parent suffix already made anionic (‘5-hydroxybenzene-1,3-disulfonate’). Returns ‘’ unless:

  • every anionic centre is either an S/P-oxoacid centre (sulfonate / sulfinate / phosphonate: the parent) or a chalcogen anion of ONE kind (phenolate/alkoxide -> oxido, or thiolate -> sulfido); a carboxylate belongs to the carboxylate path;

  • (:41261, “CHOICE OF AN ANIONIC PARENT STRUCTURE”) picks the oxoacid parent: (a):41265 “parent with the maximum number of anionic centers, including anionic suffixes” – so the junior centres may not outnumber the oxoacid centres (for [O-]CC([O-])CS(=O)(=O)[O-] the bis(olate) parent has more centres; that shape is left to a producer that builds it); a tie goes on to (d):41281 (O > S: a sulfonate beats a thiolate) and (e):41289, the suffix seniority of (sulfonic and phosphonic acids are senior to alcohols), as in the BB’s “3-oxidonaphthalene-2-carboxylate (PIN) (carboxylate senior to olate)” (:41295);

  • the neutral prefix occurs exactly as often as there are junior centres (a genuine neutral -OH / -SH is never converted);

  • the result round-trips to the input at the full InChIKey.

A swap that may change the prefix citation order is shipped demoted, never as pin_verified (_demote_if_swap_changes_citation_rank).

orthonym.rules.ions.emit_uronium(mol, sites)#

(the Blue Book) substituted URONIUM / THIOURONIUM cation.

Task 8B RC3. A cation formed by adding a hydron to (iso)urea — a carbon bonded to exactly two N and one O (uronium) or one S (thiouronium), one C=heteroatom double bond, net +1 on the skeleton — is named on the retained parent cation uronium / thiouronium: numerical locants are dropped in the PIN; the N/N’/O/S locants follow urea/isourea). ->

CNC(=[NH+]C)Oc1ccccc1 -> N,N’-dimethyl-O-phenyluronium (the Blue Book) CNC(=[NH+]c1ccccc1)SC -> N,S-dimethyl-N’-phenylthiouronium (the Blue Book)

The neutralize -> re-name -> re-suffix path CANNOT reach this class: the neutral isourea names as a carbamimidate ESTER (phenyl N,N'-dimethylcarbamimidate), not a uronium-convertible amine. So this is a bespoke skeleton producer.

Locant priming: the two nitrogens are N / N’; the nitrogen bearing the alphabetically-earliest substituent takes the unprimed N (lowest locant to the prefix cited first). The chalcogen is O or S. Identical substituents group under one multiplier (N,N'-dimethyl); the C-substituent prefixes alphabetise.

Scope (build-the-class-or-degrade): requires >= 1 substituent (the UNSUBSTITUTED parent keeps its retained-name routing) and a single skeleton cation, else returns ‘’ (fail closed). Guanidinium (three N) never matches. The caller RT-gates the result, so a mis-built name fails closed rather than shipping a wrong structure.

orthonym.rules.ions.name_quaternary_aminium(mol, cation_site)#

Systematic -aminium PIN for a STANDALONE quaternary ammonium cation.

a phase.1 method (1) + Table 7.4; the Blue Book:41354, 41429-41438): a quaternary N (formal charge +1, 0 H, degree >= 4) is named by treating the SENIOR carbon chain through N as the amine parent (-amine suffix), the other three N-branches as N,N,N-/N,N-/N- substituent prefixes, then appending -ium (methanamine -> methanaminium):

C[N+](C)(C)C -> ‘N,N,N-trimethylmethanaminium’ OCC[N+](C)(C)C -> ‘2-hydroxy-N,N,N-trimethylethan-1-aminium’ CC[N+](C)(C)CC -> ‘N-ethyl-N,N-dimethylethanaminium’

Mechanism : the quaternary N CANNOT be neutralized — removing a (non-existent) proton leaves an over-valent neutral N and SanitizeMol raises (RESEARCH Pitfall 2). Instead the N is DEMOTED to a neutral carbon-context amine parent: a tertiary_amine functional-group match is injected on the ORIGINAL (still-charged) mol and forced as the principal group, so find_principal_chain selects the senior chain THROUGH N as the parent and the remaining N-branches become N-locant prefixes (NOT select_parent, NOT the azaniumyl prefix). The well-formed neutral amine name is then converted to -aminium via name_aminium_cation (the existing amine->aminium text transform). NO postprocessor, NO regex band-aid, NO per-molecule.replace.

Returns ‘’ (fail-closed) when the cation is NOT a quaternary N or any step fails, so callers fall through to the proven aminium / legacy cascade (the no-crash byte-identical contract).

orthonym.rules.ions.emit_cumulative_ium_ide(mol)#

cumulative same-parent zwitterion: one cationic (‘-ium’) and one anionic (‘-ide’) skeletal centre on the SAME homogeneous heteroatom chain parent hydride. Covers amine imides, azo/azomethine imides /.2) and carbonyl oxides:

C[N-][N+](C)(C)C -> 1,2,2,2-tetramethylhydrazin-2-ium-1-ide (BB 42423)
C[N+]([N-]C)=NC -> 1,2,3-trimethyltriaz-2-en-2-ium-1-ide (BB 43121)
C[N-][N+](=C)C -> 1,2-dimethyl-2-methylidenehydrazin-2-ium-1-ide (BB 43143)
CC(C)=[O+][O-] -> 2-(propan-2-ylidene)dioxidan-2-ium-1-ide (BB 43187)

Numbering: anionic suffixes get seniority for low locants (so the ‘-ide’ locant is minimised before the ‘-ium’ locant); the cationic suffix is cited FIRST in the name (‘<stem>-<ium>-ium-<ide>-ide’). Fail-closed (None).

orthonym.rules.ions.emit_ylide(mol)#

‘ylide’: a closed-shell onium cation X+ (X = N/P/O/S/…, no free H) bonded directly to a carbanion Y-. Named (method 1 = PIN) as the carbanion ‘-ide’ parent hydride with the onium cation as an ‘<…>-aniumyl’ prefix:

C[N+](C)(C)[C-](C)C -> 2-(trimethylazaniumyl)propan-2-ide
C[P+](C)(C)[C-](C)C -> 2-(trimethylphosphaniumyl)propan-2-ide (BB 42526)
CC[C-](CC)[O+](C)C -> 3-(dimethyloxidaniumyl)pentan-3-ide
CC[C-](CC)[S+](C)C -> 3-(dimethylsulfaniumyl)pentan-3-ide
C[N-][O+]=C(C)C -> N-[(propan-2-ylidene)oxidaniumyl]methanaminide

The anion parent is a carbanion (‘-ide’) OR a nitrogen aminide (‘-aminide’, ; the latter carries the cation prefix at the ‘N’ locant.

The nitrogen PIN is the amine-based ‘2-(N,N-dimethylmethanaminiumyl)propan-2- ide’, but that form is OPSIN-unparseable; the azane-based ‘trimethylazaniumyl’ equivalent (which OPSIN accepts) is emitted instead (W4-I4). Fail-closed.