Module:Q-odd-limit intervals: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ideally we should only make the table when called for. Here's the first step to approach that.
experiment with a larger limit
 
(46 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local p = {}
bit32 = require("bit32")
bit32 = require("bit32")
utils = require("Module:Utils")
ET = require("Module:ET")
ET = require("Module:ET")
getArgs = require("Module:Arguments").getArgs
limits = require("Module:Limits")
limits = require("Module:Limits")
 
utils = require("Module:Utils")
local p = {}
yesno = require("Module:Yesno")


local PRIME_LIST = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61}
local PRIME_LIST = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61}
Line 86: Line 88:


local function find_error(val, subgroup, monzo_list)
local function find_error(val, subgroup, monzo_list)
local step_size = 1200/val[1]
local step_size = 1200 / val[1]
local true_size
local true_size
local approx_size
local approx_size
Line 100: Line 102:
error_rel_direct = 100 * error_abs_direct / step_size
error_rel_direct = 100 * error_abs_direct / step_size
error_rel_val = 100 * error_abs_val / step_size
error_rel_val = 100 * error_abs_val / step_size
error_list[i] =  
error_list[i] = {
{
ratio = ratio,  
ratio = ratio,  
comp = comp,  
comp = comp,  
Line 110: Line 111:
}
}
end
end
table.sort(error_list, function(a, b) return a.error_abs_val < b.error_abs_val end)
table.sort(error_list, function(a, b)
return a.error_abs_val < b.error_abs_val
end)
return error_list
return error_list
end
end
Line 120: Line 125:
local errlist_val = {}
local errlist_val = {}
local val = {}
local val = {}
local bgclr, result


for i = 1, #subgroup do
for i = 1, #subgroup do
Line 126: Line 132:
error_list = find_error(val, subgroup, monzo_list)
error_list = find_error(val, subgroup, monzo_list)
for i = 1, #error_list do
for i = 1, #error_list do
ratiocomp = string.format("%d/%d, %d/%d", error_list[i].ratio.num, error_list[i].ratio.den, 2 * error_list[i].ratio.den, error_list[i].ratio.num)
ratiocomp = string.format("%d/%d, %d/%d",
error_list[i].ratio.num, error_list[i].ratio.den,
2 * error_list[i].ratio.den, error_list[i].ratio.num)
eav = error_list[i].error_abs_val
eav = error_list[i].error_abs_val
erv = error_list[i].error_rel_val
erv = error_list[i].error_rel_val
Line 135: Line 143:
error_abs_direct = string.format("%.3f", ead)
error_abs_direct = string.format("%.3f", ead)
error_rel_direct = string.format("%.1f", erd)
error_rel_direct = string.format("%.1f", erd)
if bit32.band(error_list[i].ratio.den, error_list[i].ratio.den - 1) == 0 and utils.table_contains(subgroup, error_list[i].ratio.num) then -- check power of 2 for den and prime for num
if bit32.band(error_list[i].ratio.den, error_list[i].ratio.den - 1) == 0
and utils.table_contains(subgroup, error_list[i].ratio.num) then -- check power of 2 for den and prime for num
ratiocomp = "'''" .. ratiocomp .. "'''"
ratiocomp = "'''" .. ratiocomp .. "'''"
error_abs_val = "'''" .. error_abs_val .. "'''"
error_abs_val = "'''" .. error_abs_val .. "'''"
Line 141: Line 151:
error_abs_direct = "'''" .. error_abs_direct .. "'''"
error_abs_direct = "'''" .. error_abs_direct .. "'''"
error_rel_direct = "'''" .. error_rel_direct .. "'''"
error_rel_direct = "'''" .. error_rel_direct .. "'''"
bgclr = ""
end
end
if error_list[i].error_rel_val > 50 then
if error_list[i].error_rel_val > 50 then
Line 148: Line 159:
error_abs_direct = "''" .. error_abs_direct .. "''"
error_abs_direct = "''" .. error_abs_direct .. "''"
error_rel_direct = "''" .. error_rel_direct .. "''"
error_rel_direct = "''" .. error_rel_direct .. "''"
bgclr = " style=\"background-color: #dddddd;\""
else
bgclr = ""
end
end
errlist_direct[i] = {
errlist_direct[i] = {
bgclr = bgclr,
ratiocomp = ratiocomp,
ratiocomp = ratiocomp,
ead = ead,
ead = ead,
Line 159: Line 174:
errlist_val[i] = {
errlist_val[i] = {
bgclr = bgclr,
ratiocomp = ratiocomp,
ratiocomp = ratiocomp,
eav = eav,
eav = eav,
Line 167: Line 183:
end
end
table.sort(errlist_direct, function(a, b) return a.ead < b.ead end)
table.sort(errlist_direct, function(a, b)
return a.ead < b.ead
end)
for i = 1, #error_list do
for i = 1, #error_list do
t_body_direct[i] = string.format("|-\n| %s || %s || %s", errlist_direct[i].ratiocomp, errlist_direct[i].error_abs_direct, errlist_direct[i].error_rel_direct)
t_body_direct[i] = string.format("|-%s\n| %s || %s || %s",
t_body_val[i] = string.format("|-\n| %s || %s || %s", errlist_val[i].ratiocomp, errlist_val[i].error_abs_val, errlist_val[i].error_rel_val)
errlist_direct[i].bgclr,
errlist_direct[i].ratiocomp,
errlist_direct[i].error_abs_direct,
errlist_direct[i].error_rel_direct)
t_body_val[i] = string.format("|-%s\n| %s || %s || %s",
errlist_val[i].bgclr,
errlist_val[i].ratiocomp,
errlist_val[i].error_abs_val,
errlist_val[i].error_rel_val)
end
end
local t_head_0 = "{| class=\"wikitable center-all mw-collapsible mw-collapsed sortable\"\n"
local t_head_0 = "{| class=\"wikitable center-all mw-collapsible mw-collapsed sortable\"\n"
.. "|+ style=\"white-space: nowrap;\" | "
.. "|+ style=\"font-size: 105%; white-space: nowrap;\" | "
local t_head_1 = "|-\n"
local t_head_1 = "|-\n"
.. "! class=\"unsortable\" | Interval and complement !! Error (abs, [[Cent|¢]]) !! Error (rel, [[Relative cent|%]])\n"
.. "! class=\"unsortable\" | Interval and complement"
.. " !! Error (abs, [[Cent|&#162;]])"
.. " !! Error (rel, [[Relative cent|%]])\n"
local t_foot = "\n|}"
local t_foot = "\n|}"
Line 189: Line 217:
if approx_t == "direct" then
if approx_t == "direct" then
local out_str_direct = t_head_0 .. t_title .. tag_direct .. "\n" .. t_head_1 .. table.concat(t_body_direct, "\n") .. t_foot
result = t_head_0 .. t_title .. tag_direct .. "\n" .. t_head_1 .. table.concat(t_body_direct, "\n") .. t_foot
return out_str_direct
elseif approx_t == "val" then
elseif approx_t == "val" then
local out_str_val = t_head_0 .. t_title .. tag_val .. "\n" .. t_head_1 .. table.concat(t_body_val, "\n") .. t_foot
result = t_head_0 .. t_title .. tag_val .. "\n" .. t_head_1 .. table.concat(t_body_val, "\n") .. t_foot
return out_str_val
else
result = ""
end
end
return result
end
end
-- local function prec_by_equal (steps)
-- return math.floor (math.log (steps*1.9)/math.log (10))
-- end


function p.q_odd_limit_intervals(frame)
function p.q_odd_limit_intervals(frame)
local args = getArgs(frame)
local steps = tonumber(frame.args["steps"])
local steps = tonumber(frame.args["steps"])
local limit = math.max(tonumber(frame.args["limit"]), 2)
local limit = math.max(tonumber(frame.args["limit"]), 2)
local constcy = limits.consistency_limit(ET.parse(steps .. "edo"), false, 43)
local constcy = limits.consistency_limit(ET.parse(steps .. "edo"), false, 63)
-- local prec = tonumber (frame.args['prec']) or prec_by_equal (steps)
    local wtext = yesno(frame.args["wtext"] or args["wtext"])
local note = frame.args["note"]
local note = frame.args["note"]
local title = frame.args["title"]
local title = frame.args["title"]
local header = string.lower(frame.args["header"]:gsub("%s+", ""))
local header = string.lower(frame.args["header"]:gsub("%s+", ""))
local apx = string.lower(frame.args["apx"]:gsub("%s+", ""))
local tag
local tag
local apx = string.lower(frame.args["apx"]:gsub("%s+", ""))
if not utils.value_provided (title) then
if not utils.value_provided(title) then
title = string.format("%d-odd-limit intervals in " .. steps .. "edo", limit)
title = string.format("%d-odd-limit intervals in %dedo", limit, steps)
end
end
local subgroup = table_filter(PRIME_LIST, limit)
local subgroup = table_filter(PRIME_LIST, limit)
local monzo_list = odd_limit_monzo_list_gen(limit)
local monzo_list = odd_limit_monzo_list_gen(limit)
local out_str
local result
if header ~= "none" then
if header ~= "none" then
out_str = "The following "
if constcy >= limit then
if constcy >= limit then
apx = "val"
apx = "val"
end
end
if apx == "direct" or apx == "val" then
result = string.format("The following %s how [[%d-odd-limit]] intervals are represented in %dedo. ",
out_str = out_str .. "table shows"
string.gsub("table show", "()", {[((apx == "direct" or apx == "val") and 11 or 6)] = "s"}), limit, steps)
else
.. "Prime harmonics are in '''bold'''"
out_str = out_str .. "tables show"
.. (constcy >= limit and string.format(".\n\nAs %dedo is consistent in the %d-odd-limit, "
end
.. "the mappings by direct approximation and through the patent val are identical.", steps, limit)
out_str = out_str .. " how [[" .. limit .. "-odd-limit]] intervals are represented in " .. steps .. "edo. Prime harmonics are in '''bold'''"
or "; inconsistent intervals are in ''italics''.")
.. "\n\n"
if constcy >= limit then
if constcy >= limit then
out_str = out_str .. ".\n\nAs " .. steps .. "edo is consistent in the " .. limit .. "-odd-limit, the mappings by direct approximation and through the patent val are identical."
tag = "none"
tag = "none"
else
out_str = out_str .. "; inconsistent intervals are in ''italics''."
end
end
out_str = out_str .. "\n\n"
if note ~= "" then
if note ~= "" then
out_str = out_str .. note .. "\n\n"
result = result .. note .. "\n\n"
end
end
if steps == 12 then
if steps == 12 then
out_str = out_str .. "Note that since the [[cent]] was defined in terms of 12edo, the absolute and relative errors for 12edo are identical.\n\n"
result = result .. "Note that, since the [[cent]] was defined in terms of 12edo, the absolute and relative errors for 12edo are identical.\n\n"
end
end
else
else
tag = string.lower(frame.args["tag"]:gsub("%s+", ""))
tag = string.lower(frame.args["tag"]:gsub("%s+", ""))
out_str = ""
result = ""
end
end
if utils.value_provided (apx) then
if utils.value_provided(apx) then
out_str = out_str .. approx(steps, subgroup, monzo_list, title, tag, apx)
result = result .. approx(steps, subgroup, monzo_list, title, tag, apx)
else
else
out_str = out_str .. approx(steps, subgroup, monzo_list, title, tag, "direct") .. approx(steps, subgroup, monzo_list, title, tag, "val")
result = result .. approx(steps, subgroup, monzo_list, title, tag, "direct") .. "\n\n" .. approx(steps, subgroup, monzo_list, title, tag, "val")
end
end
return out_str
if wtext then
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
end
return frame:preprocess(result)
end
end


return p;
return p

Latest revision as of 13:30, 2 January 2026

Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:Q-odd-limit intervals.

This module automatically calculates a given equal-tempered tuning's approximations of intervals in a given odd limit, and lists them in a table.

Currently, odd limits up to and including 63 and prime limits up to and including 61 are supported.

Introspection summary for Module:Q-odd-limit intervals 
Functions provided (1)
Line Function Params
229 q_odd_limit_intervals (invokable) (frame)
Lua modules required (0)
Variable Module Functions used

No function descriptions were provided. The Lua code may have further information.


local p = {}

bit32 = require("bit32")
ET = require("Module:ET")
getArgs = require("Module:Arguments").getArgs
limits = require("Module:Limits")
utils = require("Module:Utils")
yesno = require("Module:Yesno")

local PRIME_LIST = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61}

local function table_filter(t, thres)
	for i = 1, #t do
		if t[i] > thres then
			return {unpack(t, 1, i - 1)}
		end
	end
	return t
end

local function inner_product(val, monzo)
	local result = 0
	for i = 1, #val do
		result = result + val[i] * monzo[i]
	end
	return result
end

local function monzo2ratio(monzo, subgroup)
	local num = 1
	local den = 1
	for i = 1, #subgroup do
		if monzo[i] > 0 then
			num = num * subgroup[i]^monzo[i]
		elseif monzo[i] < 0 then
			den = den * subgroup[i]^(-monzo[i])
		end
	end
	return {num = num, den = den}
end

local function ratio2monzo(ratio, subgroup)
	local monzo = {}
	for i = 1, #subgroup do
		monzo[i] = 0
		while ratio.num % subgroup[i] == 0 do
			monzo[i] = monzo[i] + 1
			ratio.num = ratio.num / subgroup[i]
		end while ratio.den % subgroup[i] == 0 do
			monzo[i] = monzo[i] - 1
			ratio.den = ratio.den / subgroup[i]
		end
	end
	return monzo
end

local function monzo2cent(monzo, subgroup)
	local jip = {}
	for i = 1, #subgroup do
		jip[i] = 1200 * utils._log(subgroup[i], 2)
	end
	return inner_product(jip, monzo)
end

local function ratio_8ve_reduction(ratio)
	local oct = math.floor(utils._log(ratio.num / ratio.den, 2))
	if oct > 0 then
		ratio.den = ratio.den * 2^oct
	elseif oct < 0 then
		ratio.num = ratio.num * 2^(-oct)
	end
	return ratio
end

local function odd_limit_monzo_list_gen(limit)
	local monzo_list = {}
	local subgroup = table_filter(PRIME_LIST, limit)
	for num = 1, limit, 2 do
		for den = 1, num, 2 do
			if utils._gcd(num, den) == 1 then
				ratio = ratio_8ve_reduction({num = num, den = den})
				table.insert(monzo_list, ratio2monzo(ratio, subgroup))
			end
		end
	end
	return monzo_list
end

local function find_error(val, subgroup, monzo_list)
	local step_size = 1200 / val[1]
	local true_size
	local approx_size
	local error_list = {}
	for i = 1, #monzo_list do
		ratio = monzo2ratio(monzo_list[i], subgroup)
		comp = {2 * ratio.den, ratio.num}
		true_size = monzo2cent(monzo_list[i], subgroup)
		approx_size = step_size * inner_product(val, monzo_list[i])
		nearest_size = math.floor(true_size / step_size + 0.5)
		error_abs_direct = math.abs(nearest_size * step_size - true_size)
		error_abs_val = math.abs(approx_size - true_size)
		error_rel_direct = 100 * error_abs_direct / step_size
		error_rel_val = 100 * error_abs_val / step_size
		error_list[i] = {
			ratio = ratio, 
			comp = comp, 
			error_abs_direct = error_abs_direct,
			error_abs_val = error_abs_val, 
			error_rel_direct = error_rel_direct,
			error_rel_val = error_rel_val
		}
	end
	
	table.sort(error_list, function(a, b)
		return a.error_abs_val < b.error_abs_val
	end)
	
	return error_list
end

local function approx(steps, subgroup, monzo_list, t_title, tag, approx_t)
	local t_body_direct = {}
	local errlist_direct = {}
	local t_body_val = {}
	local errlist_val = {}
	local val = {}
	local bgclr, result

	for i = 1, #subgroup do
		val[i] = utils._round_dec(steps*utils._log(subgroup[i], 2))
	end
	error_list = find_error(val, subgroup, monzo_list)
	for i = 1, #error_list do
		ratiocomp = string.format("%d/%d, %d/%d",
			error_list[i].ratio.num, error_list[i].ratio.den,
			2 * error_list[i].ratio.den, error_list[i].ratio.num)
		eav = error_list[i].error_abs_val
		erv = error_list[i].error_rel_val
		ead = error_list[i].error_abs_direct
		erd = error_list[i].error_rel_direct
		error_abs_val = string.format("%.3f", eav)
		error_rel_val = string.format("%.1f", erv)
		error_abs_direct = string.format("%.3f", ead)
		error_rel_direct = string.format("%.1f", erd)
		
		if bit32.band(error_list[i].ratio.den, error_list[i].ratio.den - 1) == 0
		and utils.table_contains(subgroup, error_list[i].ratio.num) then -- check power of 2 for den and prime for num
			ratiocomp = "'''" .. ratiocomp .. "'''"
			error_abs_val = "'''" .. error_abs_val .. "'''"
			error_rel_val = "'''" .. error_rel_val .. "'''"
			error_abs_direct = "'''" .. error_abs_direct .. "'''"
			error_rel_direct = "'''" .. error_rel_direct .. "'''"
			bgclr = ""
		end
		if error_list[i].error_rel_val > 50 then
			ratiocomp = "''" .. ratiocomp .. "''"
			error_abs_val = "''" .. error_abs_val .. "''"
			error_rel_val = "''" .. error_rel_val .. "''"
			error_abs_direct = "''" .. error_abs_direct .. "''"
			error_rel_direct = "''" .. error_rel_direct .. "''"
			bgclr = " style=\"background-color: #dddddd;\""
		else
			bgclr = ""
		end
		
		errlist_direct[i] = {
			bgclr = bgclr,
			ratiocomp = ratiocomp,
			ead = ead,
			erd = erd,
			error_abs_direct = error_abs_direct,
			error_rel_direct = error_rel_direct,
		}
		
		errlist_val[i] = {
			bgclr = bgclr,
			ratiocomp = ratiocomp,
			eav = eav,
			erv = erv,
			error_abs_val = error_abs_val,
			error_rel_val = error_rel_val,
		}
	end
	
	table.sort(errlist_direct, function(a, b)
		return a.ead < b.ead
	end)
	
	for i = 1, #error_list do
		t_body_direct[i] = string.format("|-%s\n| %s || %s || %s",
			errlist_direct[i].bgclr,
			errlist_direct[i].ratiocomp,
			errlist_direct[i].error_abs_direct,
			errlist_direct[i].error_rel_direct)
		t_body_val[i] = string.format("|-%s\n| %s || %s || %s",
			errlist_val[i].bgclr,
			errlist_val[i].ratiocomp,
			errlist_val[i].error_abs_val,
			errlist_val[i].error_rel_val)
	end
	
	local t_head_0 = "{| class=\"wikitable center-all mw-collapsible mw-collapsed sortable\"\n"
		.. "|+ style=\"font-size: 105%; white-space: nowrap;\" | "
	local t_head_1 = "|-\n"
		.. "! class=\"unsortable\" | Interval and complement"
		.. " !! Error (abs, [[Cent|&#162;]])"
		.. " !! Error (rel, [[Relative cent|%]])\n"
	local t_foot = "\n|}"
	
	if tag ~= "none" then
		tag_direct = " (direct approximation, even if inconsistent)"
		tag_val = " (patent val mapping)"
	else
		tag_direct = ""
		tag_val = ""
	end
	
	if approx_t == "direct" then
		result = t_head_0 .. t_title .. tag_direct .. "\n" .. t_head_1 .. table.concat(t_body_direct, "\n") .. t_foot
	elseif approx_t == "val" then
		result = t_head_0 .. t_title .. tag_val .. "\n" .. t_head_1 .. table.concat(t_body_val, "\n") .. t_foot
	else
		result = ""
	end
	
	return result
end

function p.q_odd_limit_intervals(frame)
	local args = getArgs(frame)
	local steps = tonumber(frame.args["steps"])
	local limit = math.max(tonumber(frame.args["limit"]), 2)
	local constcy = limits.consistency_limit(ET.parse(steps .. "edo"), false, 63)
    local wtext = yesno(frame.args["wtext"] or args["wtext"])
	local note = frame.args["note"]
	local title = frame.args["title"]
	local header = string.lower(frame.args["header"]:gsub("%s+", ""))
	local apx = string.lower(frame.args["apx"]:gsub("%s+", ""))
	local tag
	
	if not utils.value_provided(title) then
		title = string.format("%d-odd-limit intervals in %dedo", limit, steps)
	end
	
	local subgroup = table_filter(PRIME_LIST, limit)
	local monzo_list = odd_limit_monzo_list_gen(limit)
	local result
	if header ~= "none" then
		if constcy >= limit then
			apx = "val"
		end
		result = string.format("The following %s how [[%d-odd-limit]] intervals are represented in %dedo. ",
			string.gsub("table show", "()", {[((apx == "direct" or apx == "val") and 11 or 6)] = "s"}), limit, steps)
			.. "Prime harmonics are in '''bold'''"
			.. (constcy >= limit and string.format(".\n\nAs %dedo is consistent in the %d-odd-limit, "
				.. "the mappings by direct approximation and through the patent val are identical.", steps, limit)
				or "; inconsistent intervals are in ''italics''.")
			.. "\n\n"
		if constcy >= limit then
			tag = "none"
		end
		if note ~= "" then
			result = result .. note .. "\n\n"
		end
		if steps == 12 then
			result = result .. "Note that, since the [[cent]] was defined in terms of 12edo, the absolute and relative errors for 12edo are identical.\n\n"
		end
	else
		tag = string.lower(frame.args["tag"]:gsub("%s+", ""))
		result = ""
	end
	
	if utils.value_provided(apx) then
		result = result .. approx(steps, subgroup, monzo_list, title, tag, apx)
	else
		result = result .. approx(steps, subgroup, monzo_list, title, tag, "direct") .. "\n\n" .. approx(steps, subgroup, monzo_list, title, tag, "val")
	end
	
	if wtext then
		result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
	end
	
	return frame:preprocess(result)
end

return p