Module:Uniform map

From Xenharmonic Wiki
Jump to navigation Jump to search

This module is called by Template:Uniform map to produce a table of p-limit uniform maps between two size boundaries.


local getArgs = require("Module:Arguments").getArgs
local utils = require("Module:Utils")
local p = {}

-- stylua: ignore
local warts = {
	'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
    'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
    's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
}

function p.new()
	local m = {}
	m.map = {} -- The prime map
	m.lower = {} -- List of smallest sizes that match a map for each prime
	m.upper = {} -- List of largest sizes that match a map for each prime
	m.pbi = 0 -- Index of the prime that defines the boundary
	return m
end

-- Find the minimum size that corresponds to a given map
function p.lower_bound(m)
	local lowermax
	for i = 1, #m.map do
		m.lower[i] = (m.map[i] - 0.5) / utils.log2(utils.primes[i])
		lowermax = math.max(unpack(m.lower))
	end
	return math.max(0, lowermax)
end

-- Find the maximum size that corresponds to a given map
function p.upper_bound(m)
	local uppermin
	for i = 1, #m.map do
		m.upper[i] = (m.map[i] + 1 / 2) / utils.log2(utils.primes[i])
		uppermin = math.min(unpack(m.upper))
		m.pbi = utils.index_of(m.upper, uppermin)
	end
	return math.max(0, uppermin)
end

-- Find the p-limit just tuning map corresponding to a given size
function p.just_tuning_map(size, prime)
	prime = tonumber(prime)
	local p_index = utils.index_of(utils.primes, prime)
	assert(p_index ~= nil, "index of prime " .. prime .. " not found")
	local just_tuning_map = {}
	for i = 1, utils.index_of(utils.primes, prime) do
		just_tuning_map[i] = size * utils.log2(utils.primes[i])
	end
	return just_tuning_map
end

-- Find the p-limit simple map corresponding to a given size
function p.simple_map(size, prime)
	prime = tonumber(prime)
	local p_index = utils.index_of(utils.primes, prime)
	assert(p_index ~= nil, "index of prime " .. prime .. " not found")
	local simple_map = {}
	for i = 1, p_index do
		simple_map[i] = math.floor(0.5 + size * utils.log2(utils.primes[i]))
	end
	return simple_map
end

-- Find the wart notation corresponding to a given map
function p.wart(map)
	local edo = map[1]
	local simple_map_edo = p.simple_map(edo, utils.primes[#map])
	local just_tuning_map_edo = p.just_tuning_map(edo, utils.primes[#map])
	local wart_notation = edo
	for i = 2, #map do
		-- direction is 1 if current harmonic mapped flatter than just, otherwise 0
		local direction = math.ceil(just_tuning_map_edo[i] - simple_map_edo[i])
		local difference = map[i] - simple_map_edo[i]
		if difference ~= 0 then
			local number_warts = (2 * math.abs(difference) + (utils.signum((-1) ^ direction * difference) - 1) / 2)
			for _ = 1, number_warts do
				wart_notation = wart_notation .. warts[i]
			end
		end
	end
	return wart_notation
end

-- Generate table of p-limit uniform maps between min and max, for use with print_table
-- Could potentially be used standalone for raw data with headers
function p.make_table(prime, min, max)
	local maptable = {} -- Table of uniform maps with boundaries and wart notation
	local map_table = p.new()
	map_table.map = p.simple_map(min, prime) -- Set p-limit map for minimum size
	local lb = p.lower_bound(map_table) -- Minimum size for the current map
	local ub = p.upper_bound(map_table) -- Maximum size for the current map
	local row = { "Min. size", "Max. size", "[[Wart notation]]" }
	for i = 1, #map_table.map do
		table.insert(row, utils.primes[i])
	end
	table.insert(maptable, row)
	while lb < max do
		row = { string.format("%.4f", lb), string.format("%.4f", ub), p.wart(map_table.map) }
		for j = 1, #map_table.map do
			table.insert(row, map_table.map[j])
		end
		table.insert(maptable, row)
		map_table.map[map_table.pbi] = map_table.map[map_table.pbi] + 1
		lb = p.lower_bound(map_table)
		ub = p.upper_bound(map_table)
	end
	return maptable
end

-- Print wiki-formatted table (string) of p-limit uniform maps between min and max
function p.print_table(frame)
	local args = getArgs(frame)
	local prime = args[1]
	local min = args[2]
	local max = args[3]
	if utils.index_of(utils.primes, utils.eval_num_arg(prime, 5)) == nil then
		prime = 5 -- Default to 5-limit
	end
	local luatable = p.make_table(prime, utils.eval_num_arg(min, 11.5), utils.eval_num_arg(max, 12.5))
	local wikitable =
		string.format('{| class="wikitable"\n|+ %d-limit [[uniform map]]s between %g and %g', prime, min, max)
	for i = 1, 3 do
		wikitable = wikitable .. "\n! " .. luatable[1][i]
	end
	wikitable = wikitable .. "\n! Map"
	for i = 2, #luatable do
		wikitable = wikitable .. "\n|-"
		for j = 1, 3 do
			wikitable = wikitable .. "\n| " .. luatable[i][j]
		end
		local wikimap = ""
		for j = 4, #luatable[i] do
			wikimap = wikimap .. " " .. luatable[i][j]
		end
		wikitable = wikitable .. "\n| " .. frame:expandTemplate({ title = "map", args = { wikimap } })
	end
	wikitable = wikitable .. "\n|}"
	return wikitable
end

return p