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.

// request.cf · coarse context

A page that knows where it met you.

Only coarse request metadata is shown. This demo does not display or persist visitor IP addresses.

Country
US
Cloudflare location
CMH
Connection
HTTP/2
Language
Not provided

Ray ID: a40441d18bd618aa

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