Module:Mediants: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
Removed tenney height search (for now); comments/todo
Ganaram inukshuk (talk | contribs)
update code to be documentation
 
(11 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local mos = require("Module:MOS") -- For testing
-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
local utils = require("Module:Utils") -- For testing
local rat  = require("Module:Rational")
local utils = require("Module:Utils")
 
local p = {}
local p = {}


-- Module for finding mediants, either by search depth or by search function.
-- Mediants consists of code used to find a tree of mediants, starting from a
-- set of starting ratios (default 1/1 and 1/0). Search can be by int limit,
-- depth, or a custom search function.
-- Ratios produced this way are a table consisting of the numerator and
-- denominator, which allows for non-simplified ratios to be represented.


-- TODO: Add int-limit search
--------------------------------------------------------------------------------
------------------------------ UTILITY FUNCTIONS -------------------------------
--------------------------------------------------------------------------------
 
-- Given a table of depths, return the deepest depth
function p.deepest_depth(depths)
local deepest = nil
for _, value in ipairs(depths) do
if not deepest or value > deepest then
deepest = value
end
end
return deepest
end
 
-- Given a ratio, return its simplified form.
function p.simplify_ratio(ratio)
local gcd = utils._gcd(ratio[1], ratio[2])
return { ratio[1] / gcd, ratio[2] / gcd }
end
 
-- Sort ratios in ascending order. Comparison function is built-in.
function p.sort_ratios(ratios)
table.sort(ratios, function(ratio_1, ratio_2)
return ratio_1[1] / ratio_1[2] < ratio_2[1] / ratio_2[2]
end
)
end
 
--------------------------------------------------------------------------------
----------------------------- CONVERTER FUNCTIONS ------------------------------
--------------------------------------------------------------------------------
 
-- Converts ratios into the form defined by [[Module:Rational]], a table
-- consisting of its prime factorization.
 
-- Given a single ratio, as a table of two numbers, convert to rational and
-- return it.
function p.to_rational(ratio)
return rat.new(ratio[1], ratio[2])
end
 
-- Given a table of ratios, each a table of two numbers, return an array of
-- ratios in the form as defined by module:Rational.
function p.to_rationals(ratios)
local rats = {}
for i = 1, #ratios do
table.insert(rats, p.to_rational(ratios[i]))
end
return rats
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 13: Line 70:
-- Search functions determine whether a mediant meets a specific criteria for
-- Search functions determine whether a mediant meets a specific criteria for
-- being added to a set of mediants, be it based on something about the mediant,
-- being added to a set of mediants, be it based on something about the mediant,
-- its search depth, or both.
-- its search depth, the ratios that produced the mediant, or any combination
-- thereof.
-- NOTE: some search criteria, such as prime limit, are considered unsuitable,
-- NOTE: some search criteria, such as prime limit, are considered unsuitable,
-- since mediants not within a prime limit are used to find ratios within a
-- since mediants not within a prime limit are used to find ratios within a
Line 22: Line 80:
-- A search function has two params: a table containing the mediant and the
-- A search function has two params: a table containing the mediant and the
-- depth it was found at, and a search param.
-- depth it was found at, and a search param.
-- Mediant data, the first param, is of the form:
-- Mediant data is a table that contains the mediant, the search depth it was
-- { ["mediant"] = { p, q }, ["depth"] = d }, where, p, q, d are integers.
-- found at, and the two ratios that were used to find the mediant.
-- The search params can be a single numeric value, or a table of values for
-- The search params can be a single numeric value, or a table of values for
-- finer control.
-- finer control.


-- Int limit search determines whether a ratio is within an int limit. Does not
-- Int limit search determines whether a ratio is within an int limit. Only uses
-- use depth. Meant for use with searching for JI ratios.
-- information about the mediant. Meant for use with searching for JI ratios.
function p.int_limit_search(mediant_data, int_limit)
function p.int_limit_search(mediant_data, int_limit)
local mediant = mediant_data["mediant"]
local mediant = mediant_data["mediant"]
Line 34: Line 92:
end
end


-- Depth search determines whether a ratio is within a target depth. Does not
-- Depth search determines whether a ratio is within a target depth. Only uses
-- use the mediant itself. Meant for use with searching for step ratios.
-- the depth it was found at. Meant for use with searching for step ratios.
function p.depth_search(mediant_data, search_depth)
function p.depth_search(mediant_data, search_depth)
local depth = mediant_data["depth"]
local depth = mediant_data["depth"]
Line 79: Line 137:
table.insert(new_depths, depth_1)
table.insert(new_depths, depth_1)
local mediant_data = { ["mediant"] = mediant, ["depth"] = new_depth }
local mediant_data = { ["mediant"] = mediant, ["depth"] = new_depth, ["ratio_1"] = ratio_1, ["ratio_2"] = ratio_2 }
if search_func(mediant_data, search_args) then
if search_func(mediant_data, search_args) then
table.insert(new_ratios, mediant)
table.insert(new_ratios, mediant)
Line 113: Line 171:
-- it's a common enough operation.
-- it's a common enough operation.


-- Find mediants by depth, how many times mediants are found in a set of ratios.
-- Find mediants by depth of its search tree.
function p.find_mediants(init_ratios, depth)
function p.find_mediants(init_ratios, depth)
local init_ratios = init_ratios or {{1,1}, {1,0}}
local init_ratios = init_ratios or {{1,1}, {1,0}}
Line 124: Line 182:
end
end


-- Find mediants by depth, how many times mediants are found in a set of ratios.
-- Find mediants by depth of its search tree. Does not return depths.
-- Does not return depths.
function p.find_only_mediants(init_ratios, depth)
function p.find_only_mediants(init_ratios, depth)
local init_ratios = init_ratios or {{1,1}, {1,0}}
local init_ratios = init_ratios or {{1,1}, {1,0}}
Line 143: Line 200:
-- whose numerator or denominator exceeds the int limit. This is made a stand-
-- whose numerator or denominator exceeds the int limit. This is made a stand-
-- alone function under the reasoning that it's a common enough operation.
-- alone function under the reasoning that it's a common enough operation.
-- Find mediants within an int limit.
function p.find_mediants_by_int_limit(init_ratios, int_limit)
local init_ratios = init_ratios or {{1,1}, {1,0}}
local int_limit = int_limit or 50
local ratios, depths
ratios, depths = p.find_mediants_by_search_func(init_ratios, p.int_limit_search, int_limit)
return ratios, depths
end
-- Find mediants within an int limit. Does not return depth.
function p.find_only_mediants_by_int_limit(init_ratios, int_limit)
local init_ratios = init_ratios or {{1,1}, {1,0}}
local int_limit = int_limit or 50
local ratios, depths
ratios, depths = p.find_mediants_by_search_func(init_ratios, p.int_limit_search, int_limit)
return ratios
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 149: Line 228:


function p.tester()
function p.tester()
local func = p.int_limit_search
--return p.find_only_mediants_by_int_limit()
local ratios = {{4,3}, {5,1}, {3,2}}
local ratios, depths = p.find_mediants_by_search_func({{1,1}, {1,0}}, func, 50)
p.sort_ratios(ratios)
--ratios, depths = p.find_mediants({{1,1}, {1,0}}, 7)
return p.to_rationals(ratios)
local generators = {}
for i = 1, #ratios do
local input_mos = mos.new(5,2)
local gen = mos.bright_gen_to_cents(input_mos, ratios[i])
local gcd = utils._gcd(ratios[i][1], ratios[i][2])
local edo = (ratios[i][1] * 5 + ratios[i][2] * 2)/gcd
--local new_string = string.format("%s:%s\t%s\t%sedo\t%.3f", ratios[i][1]/gcd, ratios[i][2]/gcd, depths[i], edo, gen)
local new_string = string.format("%s/%s\t%s", ratios[i][1]/gcd, ratios[i][2]/gcd, depths[i])
table.insert(generators, new_string)
end
return generators
end
end


return p
return p

Latest revision as of 07:23, 24 October 2025

Module documentation[view] [edit] [history] [purge]
This module primarily serves as a library for other modules and has no corresponding template.

Module:Mediants is used for finding mediants starting from a set of starting ratios (by default, 1/1 and 1/0), either by search depth, integer limit, or by a custom search function.

Introspection summary for Module:Mediants 
Functions provided (14)
Line Function Params
18 deepest_depth (depths)
30 simplify_ratio (ratio)
36 sort_ratios (ratios)
52 to_rational (ratio)
58 to_rationals (ratios)
89 int_limit_search (mediant_data, int_limit)
96 depth_search (mediant_data, search_depth)
112 find_mediants_by_search_func (init_ratios, search_func, search_args)
157 find_only_mediants_by_search_func (init_ratios, search_func, search_args)
174 find_mediants (init_ratios, depth)
185 find_only_mediants (init_ratios, depth)
204 find_mediants_by_int_limit (init_ratios, int_limit)
215 find_only_mediants_by_int_limit (init_ratios, int_limit)
229 tester none
Lua modules required (2)
Variable Module Functions used
rat Module:Rational new
utils Module:Utils _gcd

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


-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
local rat   = require("Module:Rational")
local utils = require("Module:Utils")

local p = {}

-- Mediants consists of code used to find a tree of mediants, starting from a
-- set of starting ratios (default 1/1 and 1/0). Search can be by int limit,
-- depth, or a custom search function.
-- Ratios produced this way are a table consisting of the numerator and
-- denominator, which allows for non-simplified ratios to be represented.

--------------------------------------------------------------------------------
------------------------------ UTILITY FUNCTIONS -------------------------------
--------------------------------------------------------------------------------

-- Given a table of depths, return the deepest depth
function p.deepest_depth(depths)
	local deepest = nil
	for _, value in ipairs(depths) do
		if not deepest or value > deepest then
			deepest = value
		end
	end
	
	return deepest
end

-- Given a ratio, return its simplified form.
function p.simplify_ratio(ratio)
	local gcd = utils._gcd(ratio[1], ratio[2])
	return { ratio[1] / gcd, ratio[2] / gcd }
end

-- Sort ratios in ascending order. Comparison function is built-in.
function p.sort_ratios(ratios)
	table.sort(ratios, function(ratio_1, ratio_2)
		return ratio_1[1] / ratio_1[2] < ratio_2[1] / ratio_2[2]
		end
	)
end

--------------------------------------------------------------------------------
----------------------------- CONVERTER FUNCTIONS ------------------------------
--------------------------------------------------------------------------------

-- Converts ratios into the form defined by [[Module:Rational]], a table 
-- consisting of its prime factorization.

-- Given a single ratio, as a table of two numbers, convert to rational and
-- return it.
function p.to_rational(ratio)
	return rat.new(ratio[1], ratio[2])
end

-- Given a table of ratios, each a table of two numbers, return an array of
-- ratios in the form as defined by module:Rational.
function p.to_rationals(ratios)
	local rats = {}
	for i = 1, #ratios do
		table.insert(rats, p.to_rational(ratios[i]))
	end
	return rats
end

--------------------------------------------------------------------------------
------------------------------- SEARCH FUNCTIONS -------------------------------
--------------------------------------------------------------------------------

-- Search functions determine whether a mediant meets a specific criteria for
-- being added to a set of mediants, be it based on something about the mediant,
-- its search depth, the ratios that produced the mediant, or any combination
-- thereof.
-- NOTE: some search criteria, such as prime limit, are considered unsuitable,
-- since mediants not within a prime limit are used to find ratios within a
-- prime limit, it will likely prevent desired ratios from being found at all.
-- For this reason, these functions are meant for broad search, and finer
-- filtering must be done afterwards.

-- A search function has two params: a table containing the mediant and the
-- depth it was found at, and a search param.
-- Mediant data is a table that contains the mediant, the search depth it was
-- found at, and the two ratios that were used to find the mediant.
-- The search params can be a single numeric value, or a table of values for
-- finer control.

-- Int limit search determines whether a ratio is within an int limit. Only uses
-- information about the mediant. Meant for use with searching for JI ratios.
function p.int_limit_search(mediant_data, int_limit)
	local mediant = mediant_data["mediant"]
	return math.max(mediant[1], mediant[2]) <= int_limit
end

-- Depth search determines whether a ratio is within a target depth. Only uses
-- the depth it was found at. Meant for use with searching for step ratios.
function p.depth_search(mediant_data, search_depth)
	local depth = mediant_data["depth"]
	return depth <= search_depth
end

--------------------------------------------------------------------------------
---------------------------- GENERAL SEARCH FUNCTION ---------------------------
--------------------------------------------------------------------------------

-- General search function searches for mediants using a filter function. A
-- custom filter function can be passed in to "filter" out mediants. Ratios
-- are added using a while loop, which exits if a loop iteration adds no new
-- ratios.

-- Find mediants by filter, where the filter function and its args are passed in
-- as part of the function call.
function p.find_mediants_by_search_func(init_ratios, search_func, search_args)
	local init_ratios = init_ratios or {{1,1}, {1,0}}
	
	local ratios = {}
	local depths = {}
	for i = 1, #init_ratios do
		table.insert(ratios, init_ratios[i])
		table.insert(depths, 0)
	end
	
	local new_ratios_added = true
	while new_ratios_added do
		new_ratios_added = false
		local new_ratios = {}
		local new_depths = {}
		
		for i = 1, #ratios-1 do
			local ratio_1 = ratios[i]
			local ratio_2 = ratios[i+1]
			local mediant = { ratio_1[1] + ratio_2[1], ratio_1[2] + ratio_2[2] }
			table.insert(new_ratios, ratio_1)
			
			local depth_1 = depths[i]
			local depth_2 = depths[i+1]
			local new_depth = math.max(depth_1, depth_2) + 1
			table.insert(new_depths, depth_1)
			
			local mediant_data = { ["mediant"] = mediant, ["depth"] = new_depth, ["ratio_1"] = ratio_1, ["ratio_2"] = ratio_2 }
			if search_func(mediant_data, search_args) then
				table.insert(new_ratios, mediant)
				table.insert(new_depths, new_depth)
				new_ratios_added = true
			end
		end
		table.insert(new_ratios, ratios[#ratios])
		table.insert(new_depths, depths[#depths])
		
		ratios = new_ratios
		depths = new_depths
	end
	return ratios, depths
end

-- Find mediants by filter, where the filter function and its args are passed in
-- as part of the function call. Only returns mediants, not depths.
function p.find_only_mediants_by_search_func(init_ratios, search_func, search_args)
	local init_ratios = init_ratios or {{1,1}, {1,0}}
	
	local ratios, depths
	ratios, depths = p.find_mediants_by_search_func(init_ratios, search_func, search_args)
	return ratios
end

--------------------------------------------------------------------------------
------------------------- DEPTH-BASED SEARCH FUNCTION --------------------------
--------------------------------------------------------------------------------

-- Depth-based search finds mediants by building a tree of mediants up to a
-- specified depth. This is made a standalone function under the reasoning that
-- it's a common enough operation.

-- Find mediants by depth of its search tree.
function p.find_mediants(init_ratios, depth)
	local init_ratios = init_ratios or {{1,1}, {1,0}}
	local depth = depth or 5

	local ratios, depths
	ratios, depths = p.find_mediants_by_search_func(init_ratios, p.depth_search, depth)
	
	return ratios, depths
end

-- Find mediants by depth of its search tree. Does not return depths.
function p.find_only_mediants(init_ratios, depth)
	local init_ratios = init_ratios or {{1,1}, {1,0}}
	local depth = depth or 5

	local ratios, depths
	ratios, depths = p.find_mediants_by_search_func(init_ratios, p.depth_search, depth)
	
	return ratios
end

--------------------------------------------------------------------------------
---------------------- INT-LIMIT-BASED SEARCH FUNCTION -------------------------
--------------------------------------------------------------------------------

-- Int limit search finds mediants up to an integer limit, not permitting ratios
-- whose numerator or denominator exceeds the int limit. This is made a stand-
-- alone function under the reasoning that it's a common enough operation.

-- Find mediants within an int limit.
function p.find_mediants_by_int_limit(init_ratios, int_limit)
	local init_ratios = init_ratios or {{1,1}, {1,0}}
	local int_limit = int_limit or 50
	
	local ratios, depths
	ratios, depths = p.find_mediants_by_search_func(init_ratios, p.int_limit_search, int_limit)
	
	return ratios, depths
end

-- Find mediants within an int limit. Does not return depth.
function p.find_only_mediants_by_int_limit(init_ratios, int_limit)
	local init_ratios = init_ratios or {{1,1}, {1,0}}
	local int_limit = int_limit or 50
	
	local ratios, depths
	ratios, depths = p.find_mediants_by_search_func(init_ratios, p.int_limit_search, int_limit)
	
	return ratios
end

--------------------------------------------------------------------------------
----------------------------------- TESTER -------------------------------------
--------------------------------------------------------------------------------

function p.tester()
	--return p.find_only_mediants_by_int_limit()
	local ratios = {{4,3}, {5,1}, {3,2}}
	p.sort_ratios(ratios)
	return p.to_rationals(ratios)
end

return p