Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:SMILES2IUPAC/Alkanes

From Wikipedia, the free encyclopedia
-- 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