Module:SMILES2IUPAC/Alkanes
Appearance
-- Module:SMILES2IUPAC/Alkanes
-- Names saturated hydrocarbon chains and simple halo/hydroxy derivatives.
local Chain = require('Module:SMILES2IUPAC/Chain')
local Data = require('Module:SMILES2IUPAC/Data')
local p = {}
local function getChainRoot(carbonCount)
if Data.chainRoot[carbonCount] then return Data.chainRoot[carbonCount] end
if Data.chainExceptions[carbonCount] then return Data.chainExceptions[carbonCount] end
local ones = carbonCount % 10
local tens = math.floor((carbonCount % 100) / 10) * 10
local hundreds = math.floor(carbonCount / 100) * 100
local name = ""
if ones > 0 then name = name .. (Data.chainOnes[ones] or "") end
if tens > 0 then name = name .. (Data.chainTens[tens] or "") end
if hundreds > 0 then name = name .. (Data.chainHundreds[hundreds] or "") end
return name
end
local function classifyC4Substituent(root, nodes, adj)
local nodeSet = {}
for _, x in ipairs(nodes) do nodeSet[x] = true end
local degIn = {}
for _, x in ipairs(nodes) do
local d = 0
for _, nb in ipairs(adj[x]) do if nodeSet[nb] then d = d + 1 end end
degIn[x] = d
end
if degIn[root] == 3 then return 'tert-butyl'
elseif degIn[root] == 2 then return 'sec-butyl'
elseif degIn[root] == 1 then
for _, nb in ipairs(adj[root]) do
if nodeSet[nb] and degIn[nb] == 3 then return 'isobutyl' end
end
end
return nil
end
local function nameSubstituent(root, nodes, adj)
local n = #nodes
if n == 1 then return 'methyl' end
if n == 3 then
local degIn0, nodeSet = 0, {}
for _, x in ipairs(nodes) do nodeSet[x] = true end
for _, nb in ipairs(adj[root]) do if nodeSet[nb] then degIn0 = degIn0 + 1 end end
if degIn0 == 2 then return 'isopropyl' end
if degIn0 == 1 then return 'propyl' end
end
if n == 4 then
local named = classifyC4Substituent(root, nodes, adj)
if named then return named end
end
local nodeSet = {}
for _, x in ipairs(nodes) do nodeSet[x] = true end
local isStraight = true
for _, x in ipairs(nodes) do
local d = 0
for _, nb in ipairs(adj[x]) do if nodeSet[nb] then d = d + 1 end end
if d > 2 then isStraight = false; break end
end
if isStraight then
local root_ = getChainRoot(n)
if not root_ or root_ == '' then return nil, ('substituent chain of %d carbons has no name'):format(n) end
return root_ .. 'yl'
end
return nil, ('branched substituent of %d carbons not supported'):format(n)
end
function p.name(graph)
for _, bond in ipairs(graph.bonds) do
if bond.order ~= 1 then
return nil, 'double/triple bonds present'
end
end
local carbonNodes = {}
for i, atom in ipairs(graph.atoms) do
if atom.element == 'C' then table.insert(carbonNodes, i) end
end
if #carbonNodes == 0 then return nil, 'no carbon skeleton' end
local adj = Chain.buildAdjacency(carbonNodes, graph.bonds)
local isTree, treeErr = Chain.isTree(carbonNodes, adj)
if not isTree then return nil, treeErr .. ' — rings not supported' end
if #carbonNodes == 1 then
-- Check for single carbon with heteroatom
for _, atom in ipairs(graph.atoms) do
if atom.element ~= 'C' then
local subName = Data.heteroSubstituents and Data.heteroSubstituents[atom.element]
if subName then return subName .. 'methane' end
end
end
return 'methane'
end
local path = Chain.chooseBestChain(carbonNodes, adj)
local branches = Chain.branchesAlongPath(path, adj)
local forward = Chain.forwardIsBetter(path, branches)
local n = #path
local pathMap = {}
for idx, node in ipairs(path) do pathMap[node] = forward and idx or (n - idx + 1) end
local substituentsByName = {}
for idx, list in ipairs(branches) do
local locant = forward and idx or (n - idx + 1)
for _, branch in ipairs(list) do
local name, err = nameSubstituent(branch.root, branch.nodes, adj)
if not name then return nil, err end
substituentsByName[name] = substituentsByName[name] or {}
table.insert(substituentsByName[name], locant)
end
end
-- Handle heteroatoms attached to path carbons
for _, bond in ipairs(graph.bonds) do
local cNode, hElem
local aAtom, bAtom = graph.atoms[bond.a], graph.atoms[bond.b]
if aAtom.element == 'C' and bAtom.element ~= 'C' then
cNode, hElem = bond.a, bAtom.element
elseif bAtom.element == 'C' and aAtom.element ~= 'C' then
cNode, hElem = bond.b, aAtom.element
end
if cNode and pathMap[cNode] then
local subName = Data.heteroSubstituents and Data.heteroSubstituents[hElem]
if subName then
substituentsByName[subName] = substituentsByName[subName] or {}
table.insert(substituentsByName[subName], pathMap[cNode])
end
end
end
local names = {}
for name in pairs(substituentsByName) do table.insert(names, name) end
table.sort(names)
local parts = {}
for _, name in ipairs(names) do
local locants = substituentsByName[name]
table.sort(locants)
local count = #locants
local prefix = count > 1 and (Data.multiplyingPrefix[count] or '') or ''
local locantStrs = {}
for _, l in ipairs(locants) do table.insert(locantStrs, tostring(l)) end
table.insert(parts, table.concat(locantStrs, ',') .. '-' .. prefix .. name)
end
local parentRoot = getChainRoot(n)
if not parentRoot or parentRoot == '' then return nil, ('parent chain of %d carbons has no name'):format(n) end
local parentName = parentRoot .. 'ane'
if #parts == 0 then return parentName end
return table.concat(parts, '-') .. parentName
end
return p