Introduction

Currently, the built-in function MoleculeName generates names for Molecule objects by looking up various databases for names corresponding to a given molecule. On the other hand, it is certainly possible to algorithmically name molecules according to the IUPAC system, following the rules of the IUPAC Blue Book (for organic compounds) or Red Book (for inorganics); software like Beilstein's AUTONOM and ACD's ACD/Name are some examples of software for doing so.
IUPAC nomenclature is a dense thicket, as evidenced by the thickness of the corresponding rule books. To give an idea of what it takes, I will implement here a function for naming bicyclic saturated hydrocarbons. That is to say, these are organic compounds that only contain carbon and hydrogen, do not have multiple bonds, and has exactly two rings. (Another way to state the "two ring" requirement is that the Euler characteristic, bonds - atoms + 1, should be equal to 2.) As you may have surmised at this point, we will need to borrow some machinery from graph theory to algorithmically generate IUPAC names; luckily for us, Mathematica has a nice selection of functions for computing with graphs.
With these restrictions, we can consider two kinds of bicyclics, bridged and spiro compounds:
In a spiro compound, the two rings share a single common atom, called the spiro atom; in a bridged compound, the two rings share at least one bond. (Some references further distinguish between "fused" and "bridged" compounds, but I won't do so for the purposes of this notebook.)

Spiro compounds

Consider the following spiro compound (generated from its SMILES string):
m1=Molecule["C1CCC2(CC1)CCCC2"]
Molecule
Formula:
C
10
H
18
Atoms:
28
Bonds:
29

One system used by the IUPAC for naming polycyclic compounds in general is von Baeyer nomenclature. For spiro compounds, here is the relevant section of the Blue Book:
Monospiro parent hydrides consisting of two saturated cycloalkane rings are named by placing the nondetachable prefix "spiro" before the name of the unbranched acyclic hydrocarbon with the same total number of skeletal atoms. The number of skeletal atoms linked to the spiro atom in each ring is indicated by Arabic numbers separated by a full stop, cited in ascending order and enclosed in square brackets; this descriptor (called in these recommendations the "von Baeyer spiro descriptor") is placed between the spiro prefix and the name of the acyclic alkane.
To start things off, here is a function for testing if a molecule is a bicyclic saturated hydrocarbon:
bicyclicSaturatedHydrocarbonQ[mol_]:=MoleculeQ[mol]&&AllTrue[MoleculeValue[mol,"BondOrder"],EqualTo[1]]&&With[{atoms=Counts[AtomList[mol,All,"AtomicSymbol"]]},ContainsOnly[Keys[atoms],{"C","H"}]&&Lookup[atoms,"C"]-Quotient[Lookup[atoms,"H"],2]===1]
This function uses a shortcut, after verifying that the molecule of interest only has single bonds, and carbon and hydrogen atoms: it computes the degree of unsaturation, which should be equal to 2 for a bicyclic compound.
We can then use the "SpiroAtomCount" property of MoleculeValue to count the number of spiro atoms (for bicyclics, we should only have one), and the "SpiroAtoms" property to get the corresponding atom indices:
MoleculeValue[m1,"SpiroAtomCount"]
1
MoleculeValue[m1,"SpiroAtoms"]
{4}
For generating the name of the corresponding acyclic alkane (that is, the alkane with the same number of carbon atoms), we can use the resource function AlkaneName:
ResourceFunction["AlkaneName"][AtomCount[m1,Atom["C"]]]
decane
Thus, the name of this hydrocarbon should take the following form:
StringTemplate["spiro[`1`.`2`]`3`"][x,y,ResourceFunction["AlkaneName"][AtomCount[m1,Atom["C"]]]]
spiro[x.y]decane
The only thing left to do now is to fill in the spiro descriptors; that is, the two numbers corresponding to x and y that count the number of atoms in the two rings that are not the spiro atom. One way to do this is to first convert the molecule to a graph, using MoleculeGraph:
g1=MoleculeGraph[m1,IncludeHydrogensNone]
We can then use FindCycle to get the list of edges in the graph that correspond to the two rings, using the spiro atom as a specified vertex. The required numbers are then the number of edges, minus 1:
ids=(Length/@FindCycle[{g1,First@MoleculeValue[m1,"SpiroAtoms"]},∞,All])-1
{4,5}
Recalling that the two spiro descriptors should be specified in ascending order, we can now give the name like so:
name=StringTemplate["spiro[`1`.`2`]`3`"][Min[ids],Max[ids],ResourceFunction["AlkaneName"][AtomCount[m1,Atom["C"]]]]
spiro[4.5]decane
Since Molecule is able to generate molecules from IUPAC names, we can feed this name to Molecule to confirm if we have recovered the original:
MoleculeMatchQ[Molecule[name],m1]
True
With that, we can now write a function for naming spiro compounds:
spiroName[mol_]/;bicyclicSaturatedHydrocarbonQ[mol]:=Module[{mg,ids,sp},​​mg=MoleculeGraph[mol,IncludeHydrogensNone];​​sp=MoleculeValue[mol,"SpiroAtoms"];​​If[Length[sp]≠1,Return[$Failed,Module]];​​ids=(Length/@FindCycle[{mg,First[sp]},∞,All])-1;​​StringTemplate["spiro[`1`.`2`]`3`"][Min[ids],Max[ids],ResourceFunction["AlkaneName"][AtomCount[mol,Atom["C"]]]]​​]
To test the function out, here is a function for generating the name of a random bicyclic spiro hydrocarbon:
randomSpiro[n_Integer?Positive]:=Module[{p=Round[RandomReal[](n-1)],q},​​q=n-p-1;​​{p,q}={Min[p,q],Max[p,q]};​​If[p==0,p++;q--];​​StringTemplate["spiro[`1`.`2`]`3`"][p,q,ResourceFunction["AlkaneName"][n]]]
The two names should be the same:
n1=randomSpiro[RandomInteger[{5,20}]];​​m1=Molecule[n1];​​{n1,spiroName[m1]}
{spiro[6.8]pentadecane,spiro[6.8]pentadecane}

Bridged compounds

Now, consider the following bridged compound:
m2=Molecule["C1CCC2CCCCC2C1"]
Molecule
Formula:
C
10
H
18
Atoms:
28
Bonds:
29

Here is the relevant section of the Blue Book for bridged compounds:
Saturated homogeneous bicyclic hydrocarbons having two or more atoms in common are named by prefixing "bicyclo" to the name of the acyclic hydrocarbon having the same total number of skeletal atoms. The numbers of skeletal atoms in each of the two segments connecting the main bridgeheads and in the main bridge are given by Arabic numbers cited in descending numerical order, separated by full stops, and enclosed in square brackets.
Although MoleculeValue has the "BridgeheadAtomCount" and "BridgeheadAtoms" properties, these are too restrictive for our needs; instead, we use an Atom pattern to find the bridgeheads, and ensure that we have two of them (for a spiro compound, the pattern used will only match one atom):
bh=Flatten[Values[FindMoleculeSubstructure[m2,Atom["RingCount"GreaterThan[1],"HeavyAtomCoordinationNumber"3],All]]]
{4,9}
Once again, let us use MoleculeGraph to convert the molecule into a graph:
g2=MoleculeGraph[m2,IncludeHydrogensNone]
This time, the function needed for generating the three required von Baeyer descriptors is FindPath; this function can be used to enumerate the three paths connecting the two specified vertices. From there, the required descriptors are equal to the lengths of the paths minus 2, which we then sort in descending order:
bl=Sort[(Length/@FindPath[g2,bh[[1]],bh[[2]],∞,All])-2,GreaterEqual]
{4,4,0}
Constructing the name is similar to what was previously done for spiro compounds:
name2=StringTemplate["bicyclo[`1`.`2`.`3`]`4`"]@@Append[bl,ResourceFunction["AlkaneName"][AtomCount[m2,Atom["C"]]]]
bicyclo[4.4.0]decane
Check that the name reproduces the original molecule:
MoleculeMatchQ[Molecule[name2],m2]
True
Here then is a function for naming bridged compounds:
bridgedName[mol_]/;bicyclicSaturatedHydrocarbonQ[mol]:=Module[{bh,mg,ids},​​mg=MoleculeGraph[mol,IncludeHydrogensNone];​​bh=Flatten[Values[FindMoleculeSubstructure[mol,Atom["RingCount"GreaterThan[1],"HeavyAtomCoordinationNumber"3],All]]];​​If[Length[bh]≠2,Return[$Failed,Module]];​​ids=Sort[(Length/@FindPath[mg,bh[[1]],bh[[2]],∞,All])-2,GreaterEqual];​​StringTemplate["bicyclo[`1`.`2`.`3`]`4`"]@@Append[ids,ResourceFunction["AlkaneName"][AtomCount[mol,Atom["C"]]]]​​]
Similar to the spiro case, here is a function for generating the name of a random bicyclic bridged hydrocarbon:
randomBridged[n_Integer?Positive]:=StringTemplate["bicyclo[`1`.`2`.`3`]`4`"]@@Append[RandomChoice[PadRight[IntegerPartitions[n-2,{2,3}]]],ResourceFunction["AlkaneName"][n]]
The two names should be the same:

Conclusion

As you have just seen, naming bicyclics is already rather elaborate; complications arise when you now have more than two rings (where you can have both bridged and spiro components!), as well as multiple bonds and heteroatoms. These I will leave to future notes.

CITE THIS NOTEBOOK

Naming bicyclic saturated hydrocarbons​
by J. M.​
Wolfram Community, STAFF PICKS, February 19, 2025
​https://community.wolfram.com/groups/-/m/t/3396843