Module:JI ratios: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
m m.member_name -> med.member_name; use standalone int-limit search function
Ganaram inukshuk (talk | contribs)
Rename/reorganize functions; add prime-limit search by reusing subgroup-search code
Line 6: Line 6:


-- TODO:
-- TODO:
-- Adopt mediants module by using custom mediant search function
-- Replace old int-limit search function with new one
-- Add tenney height to int-limit and subgroup search so filtering is done
-- mid-search instead of afterwards


-- Template for handling multiple entry of JI ratios into a template, and for
-- Template for handling multiple entry of JI ratios into a template, and for
Line 15: Line 13:


-- JI ratios are searched by the following params in a hierarchy:
-- JI ratios are searched by the following params in a hierarchy:
-- - The absolute minimum for ratio search int limit, which limits the maximum
-- - Search by prime limit. Int limit is used to limit the num/den of ratios.
--  size of the numerator and denominator.
--  Prime limit takes precedence over subgroup.
-- - If subgroup is present, ratios are searched by subgroup within an int
-- - Search by subgroup. (Subgroup may contain nonprime numbers, but ratios are
--  limit. Subgroup takes precedence over prime limit, as subgroup is
--  currently not supported.) Int limit is used to limit the num/den of ratios.
--  (typically) a subset of prime limit, so prime limit is ignored. (Nonprime
-- - If neither prime limit or subgroup is present, search by prime limit. This
--  subgroups take precedence over prime subgroups.)
--  is considered the absolute minimum requirement for ratio searching.
-- - If prime limit is present, ratios are searched by prime limit within an int
--  limit.
-- NOTES:
-- NOTES:
-- - Prime limits are infinite sets, so int limit is used to restrain the set
-- - Prime limits are infinite sets, so int limit is used to restrain the set
--  to a finite size. The same is true for subgroup.
--  to a finite size. The same is true for subgroup.
-- - Tenney height is used for further filtering of ratios, and is considered
-- - Tenney height is used for further filtering of ratios, and is considered
--  optional.
--  optional. If omitted, tenney height defaults to infinity.


-- INT_LIMIT_MAX is hardcoded to limit the size of output.
-- INT_LIMIT_MAX is hardcoded to limit the size of output. This only applies to
-- int limit search, as other search functions (subgroup, prime-limit) may allow
-- higher search maxima. For reference, searching within the octave yields this
-- many ratios:
-- 400 -> ~24000 ratios
-- 400 -> ~24000 ratios
-- 300 -> ~14000 ratios
-- 300 -> ~14000 ratios
Line 44: Line 43:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Find JI ratios up to an integer limit within the octave by finding mediants.
-- Function to be replaced with new one
-- A cent value can be passed in to either exclude ratios that are above an
-- interval below the octave or include ratios above the octave.
function p.search_by_int_limit(integer_limit, max_cents)
function p.search_by_int_limit(integer_limit, max_cents)
local max_cents = max_cents or 1200
local max_cents = max_cents or 1200
Line 92: Line 89:
end
end


--------------------------------------------------------------------------------
-- Int-limit-based search; finds ratios between 1/1 and an equave, within an int
------------------------ SUBGROUP-BASED SEARCH FUNCTION ------------------------
-- limit. An optional tenney height can be passed in.
--------------------------------------------------------------------------------
-- Int limit is hardcoded to a max size to restrict the size of output, to avoid
 
-- risk of out-of-memory operations or the like.
-- Subgroup-based search
function p.search_by_int_limit_within_equave(int_limit, equave, tenney_height)
-- Can support higher int limits than int-limit search can, provided the sub-
local int_limit = int_limit or DEFAULT_INT_LIMIT
-- group is sufficiently small (about 10 members)
local equave = equave or rat.new(2,1) -- Defualt equave is 2/1.
function p.search_by_subgroup(subgroup, int_limit, equave)
local tenney_height = tenney_height or 1/0 -- Defualt tenney height is infinity.
local subgroup = subgroup or { 2, 3, 7, 11 }
local int_limit = int_limit or 50
int_limit = math.max(0, math.min(INT_LIMIT_MAX, int_limit))
local equave = equave or {2,1}
local possible_values = p.find_products(subgroup, int_limit)
local init_ratios = {{1,1}, {2,1}}
local ratios = p.find_ratios_using_values(possible_values, equave)
local search_func = p.int_limit_mediant_search
local search_args = { ["equave"] = equave, ["int_limit"] = int_limit, ["tenney_height"] = tenney_height }
local ratios = med.find_only_mediants_by_search_func(init_ratios, search_func, search_args)
-- Convert to ratios that Module:Rational can work with
-- Convert to ratios that Module:Rational can work with
Line 115: Line 113:
end
end


-- Helper function
-- Int limit search function, with equave and tenney height cutoffs.
-- Finds all eligible values for the numerator and denominator
-- If nil is passed in for the tenney height, it will defualt to infinity.
function p.find_products(factors, max_product)
-- To be passed into mediant-search function, as part of int-limit-search
local factors = factors or { 2, 3, 7, 11 }
-- function call.
local max_product = max_product or 50
function p.int_limit_mediant_search(mediant_data, search_args)
local mediant  = mediant_data["mediant"]
local ratio_1  = mediant_data["ratio_1"]
local equave        = search_args["equave"]
local int_limit    = search_args["int_limit"]
local tenney_height = search_args["tenney_height"]
local equave_as_float = rat.as_float(equave)
local rat_1_as_float = ratio_1[1] / ratio_1[2]
local mediant_th = math.log(mediant[1] * mediant[2]) / math.log(2)
-- Perform a breadth-first-search.
return math.max(mediant[1], mediant[2]) <= int_limit and rat_1_as_float < equave_as_float and mediant_th <= tenney_height
-- Starting with the number 1 at the root node of a (simulated) search tree,
end
-- explore the possible products (child nodes) of multiplying that number
 
-- with exactly one each of the given factors. Any products that are less
--------------------------------------------------------------------------------
-- than the max product are added to the search tree, and the search
------------------------ SUBGROUP-BASED SEARCH FUNCTION ------------------------
-- recurses for each child node by finding its children produced by multi-
--------------------------------------------------------------------------------
-- plying by one of each factor. The search on any one branch stops if the
 
-- resulting products exceed that of the max product.
-- Int-limit-based search; finds ratios between 1/1 and an equave, within a sub-
-- Products are stored as a jagged array, where the index of each inner
-- group. An int limit is passed in to limit the size of output, since subgroups
-- array is the search depth. Duplicate products are excluded.
-- are infinite sets. An optional tenney height can be passed in to further
-- NOTE: the search starts with the number 1 for this operation to work. To
-- limit output.
-- make sense of this, this operation can be thought of a BFS for powers
-- Unlike int limit search, subgroup search can allow for very high int limits,
-- pi raising factors fi (f1^p1 * f2^p2 * ... * fn^pn), so 1 is where each
-- as long as the subgroup is reasonably small and has reasonably small terms.
-- factor fi is raised by zero, thus BFS increases the exponents by 1.
-- Note that members in a subgroup need not be prime, as long as the terms are,
-- for the most part, relatively prime.
function p.search_by_subgroup_within_equave(subgroup, int_limit, equave, tenney_height)
local subgroup = subgroup or { 2, 3, 7 }
local int_limit = int_limit or 50
local equave = equave or rat.new(2,1) -- Defualt equave is 2/1.
local tenney_height = tenney_height or 1/0 -- Defualt tenney height is infinity.
-- Be absolutely sure the subgroup's members are sorted!
table.sort(subgroup)
-- Find all possible products given the factors in the subgroup.
-- These will be used to find all possible ratios.
local products = {{1}}
local products = {{1}}
local new_products_found = true
local new_products_found = true
while new_products_found do
while new_products_found do
local new_products = {}
local new_products = {}
for i = 1, #factors do
for i = 1, #subgroup do
for j = 1, #products[#products] do
for j = 1, #products[#products] do
local new_product = products[#products][j] * factors[i]
local new_product = products[#products][j] * subgroup[i]
if new_product <= max_product then
if new_product <= int_limit then
local product_already_added = false
local product_already_added = false
for k = 1, #new_products do
for k = 1, #new_products do
Line 170: Line 189:
products = consolidated_products
products = consolidated_products
table.sort(products)
table.sort(products)
return products
end


-- Finds all potential ratios whose numerator and denominator is from the list
-- Using the products produced earlier, combine them to make all possible
-- of given values, and whose value, as a float, is between 1 and a given
-- ratios from 1/1 to the equave. Ratios with non-coprime numerator and
-- equave.
-- denominator, or exceed the tenney height, are omitted.
function p.find_ratios_using_values(values, equave)
local values = values or p.find_products()
local equave = equave or { 2, 1 }
local equave_as_float = equave[1]/equave[2]
local ratios = {}
local ratios = {}
for i = 1, #values do
local equave_as_float = rat.as_float(equave)
local denominator = values[i]
for i = 1, #products do
for j = i, #values do
local denominator = products[i]
local numerator = values[j]
for j = i, #products do
local numerator = products[j]
local gcd = utils._gcd(numerator, denominator)
local gcd = utils._gcd(numerator, denominator)
if gcd == 1 then
if gcd == 1 then
local within_equave = numerator / denominator <= equave_as_float
local within_equave = numerator / denominator <= equave_as_float
if within_equave then
local within_tenney_height = math.log(numerator * denominator) / math.log(2) <= tenney_height
if within_equave and within_tenney_height then
table.insert(ratios, {numerator, denominator})
table.insert(ratios, {numerator, denominator})
else
else
Line 198: Line 210:
end
end
end
end
end
-- Convert to ratios that Module:Rational can work with
for i = 1, #ratios do
ratios[i] = rat.new(ratios[i][1], ratios[i][2])
end
end
Line 203: Line 220:
end
end


--------------------------------------------------------------------------------
---------------------- PRIME-LIMIT-BASED SEARCH FUNCTION -----------------------
--------------------------------------------------------------------------------
-- Int-limit-based search; finds ratios between 1/1 and an equave, within a
-- prime limit. An int limit is passed in to limit the size of output, since
-- prime limits are inifinite sets. An optional tenney height can be passed in
-- to further limit output.
-- Like subgroup search, prime limit search can also allow for very high int
-- limits, as long as the prime is reasonably small.
function p.search_by_prime_limit_within_equave(prime_limit, int_limit, equave, tenney_height)
local prime_limit = 3
local int_limit = int_limit or 1000
local equave = equave or rat.new(2,1) -- Defualt equave is 2/1.
local tenney_height = tenney_height or 1/0 -- Defualt tenney height is infinity.
-- Find all primes up to the prime limit.
local primes = {}
for i = 2, prime_limit do
local is_prime = true
for j = 2, math.floor(math.sqrt(i)) do
if i % j == 0 then
is_prime = false
break
end
end
if is_prime then
table.insert(primes, i)
end
end
-- Perform subgroup search on the primes found, as subgroup-search code can
-- be reused for prime-limit search.
return p.search_by_subgroup_within_equave(primes, int_limit, equave, tenney_height)
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 371: Line 423:
end
end


-- Convert a table of tables into a table of text
-- Convert a table of ratios (tables, as defined by rational module) into a
-- line of text, with options for delimiters.
function p.ratios_as_texts(ratios, add_links, delimiter)
function p.ratios_as_texts(ratios, add_links, delimiter)
local add_links = add_links == true
local add_links = add_links == true
Line 384: Line 437:
end
end


-- Int limit search function, with an equave cutoff.
function p.tester()
-- Ratios are added by int limit as normally except when the mediant straddles
-- the equave. If the equave is strictly less than the first ratio, add the
-- mediant formed by it and ratio 2. This minimizes the number of ratios larger
-- than the equave being added, but requires some post-search cleanup.
function p.int_limit_equave_cutoff_search(mediant_data, search_args)
local mediant  = mediant_data["mediant"]
local ratio_1  = mediant_data["ratio_1"]
local equave    = search_args["equave"]
local int_limit = search_args["int_limit"]
local equave_as_float = equave[1] / equave[2]
local rat_1_as_float = ratio_1[1] / ratio_1[2]
return math.max(mediant[1], mediant[2]) <= int_limit and rat_1_as_float < equave_as_float
end


function p.tester()
local ratios = p.search_by_prime_limit_within_equave()
local params = p.parse_search_params("Int Limit: 30; Prime Limit: 17")
--ratios = p.search_by_params(params)
--ratios = p.sort_by_closeness_to_cent_values(ratios, {0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200}, 15)
--return p.ratios_as_texts(ratios)
--local ratios = p.search_by_int_limit(250)
--return p.ratios_as_text(ratios) .. " " .. #ratios
-- Using these params with the naive search algorithm (iterating through
-- every number from to to the int limit and checking whether its factors
-- are present in the subgroup) takes several seconds to return only 1563
-- results using these params: factors 2, 3, 7, 11; max product: 10 million.
local factors = { 2, 3 }
local max_product = 5000
--return p.ratios_as_text(p.search_by_subgroup(factors, max_product, {3,1}))
--return p.find_products(factors, max_product)
local search_args = {}
search_args["equave"] = {5,4}
search_args["int_limit"] = 30
local ratios = med.find_only_mediants_by_search_func({{1,1},{1,0}}, p.int_limit_equave_cutoff_search, search_args)
--local ratios = p.search_by_int_limit(30)
-- Convert to ratios that Module:Rational can work with
for i = 1, #ratios do
ratios[i] = rat.new(ratios[i][1], ratios[i][2])
end
return p.ratios_as_text(ratios)
return p.ratios_as_text(ratios)

Revision as of 07:55, 18 September 2024

Module documentation[view] [edit] [history] [purge]
This module may be invoked by templates using its corresponding template Template:JI ratios, or used directly from other modules.
Module:JI ratios is a draft module. It is incomplete and may not be in active development. If possible, editors are encouraged to help with its development. In the meantime, editors should avoid using this module across the Xenharmonic Wiki, except for testing.
Introspection summary for Module:JI ratios 
Functions provided (0)
Line Function Params
Lua modules required (4)
Variable Module Functions used
med Module:Mediants find_only_mediants_by_int_limit
find_only_mediants_by_search_func
rat Module:Rational new
as_float
tenney_height
max_prime
cents
as_ratio
tip Module:Template input parse parse_kv_pairs
parse_numeric_pairs
utils Module:Utils _gcd

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


local rat = require("Module:Rational")
local utils = require("Module:Utils")
local tip = require("Module:Template input parse")
local med = require("Module:Mediants")
p = {}

-- TODO:
-- Replace old int-limit search function with new one

-- Template for handling multiple entry of JI ratios into a template, and for
-- searching for JI ratios if automatic entry is desired.
-- This is a successor/replacement for JI ratio finder.

-- JI ratios are searched by the following params in a hierarchy:
-- - Search by prime limit. Int limit is used to limit the num/den of ratios.
--   Prime limit takes precedence over subgroup.
-- - Search by subgroup. (Subgroup may contain nonprime numbers, but ratios are
--   currently not supported.) Int limit is used to limit the num/den of ratios.
-- - If neither prime limit or subgroup is present, search by prime limit. This
--   is considered the absolute minimum requirement for ratio searching.
-- NOTES:
-- - Prime limits are infinite sets, so int limit is used to restrain the set
--   to a finite size. The same is true for subgroup.
-- - Tenney height is used for further filtering of ratios, and is considered
--   optional. If omitted, tenney height defaults to infinity.

-- INT_LIMIT_MAX is hardcoded to limit the size of output. This only applies to
-- int limit search, as other search functions (subgroup, prime-limit) may allow
-- higher search maxima. For reference, searching within the octave yields this
-- many ratios:
-- 400 -> ~24000 ratios
-- 300 -> ~14000 ratios
-- 250 -> ~9500 ratios
-- 200 -> ~6000 ratios
-- 150 -> ~3400 ratios
-- 128 -> ~2500 ratios
-- 100 -> ~1500 ratios
local INT_LIMIT_MAX = 200
local DEFAULT_INT_LIMIT = 50

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

-- Function to be replaced with new one
function p.search_by_int_limit(integer_limit, max_cents)
	local max_cents = max_cents or 1200
	local integer_limit = integer_limit or DEFAULT_INT_LIMIT
	
	integer_limit = math.max(0, math.min(INT_LIMIT_MAX, integer_limit))
	
	local init_ratios = {{1,1}, {2,1}}
	local ratios = med.find_only_mediants_by_int_limit(init_ratios, integer_limit)
	
	-- If the max cents is greater than the octave, duplicate all existing
	-- ratios and raise them by the required number of octaves.
	if max_cents > 1200 then
		local new_ratios = {}
		local num_octaves_up = math.ceil(max_cents / 1200)
		
		for j = 1, num_octaves_up do
			for i = 2, #ratios do
				local num = ratios[i][1] * math.pow(2, j)
				local den = ratios[i][2]
				
				local gcd = utils._gcd(num, den)
				num = num / gcd
				den = den / gcd
				
				if math.max(num, den) <= integer_limit then
					table.insert(new_ratios, {num, den})
				end
			end
		end
		
		for i = 1, #new_ratios do
			table.insert(ratios, new_ratios[i])
		end
	end
	
	-- Remove any ratios that exceed the max cents
	
	-- Convert to ratios that Module:Rational can work with
	for i = 1, #ratios do
		ratios[i] = rat.new(ratios[i][1], ratios[i][2])
	end
	
	return ratios
end

-- Int-limit-based search; finds ratios between 1/1 and an equave, within an int
-- limit. An optional tenney height can be passed in.
-- Int limit is hardcoded to a max size to restrict the size of output, to avoid
-- risk of out-of-memory operations or the like.
function p.search_by_int_limit_within_equave(int_limit, equave, tenney_height)
	local int_limit = int_limit or DEFAULT_INT_LIMIT
	local equave = equave or rat.new(2,1)			-- Defualt equave is 2/1.
	local tenney_height = tenney_height or 1/0		-- Defualt tenney height is infinity.
	
	int_limit = math.max(0, math.min(INT_LIMIT_MAX, int_limit))
	
	local init_ratios = {{1,1}, {2,1}}
	local search_func = p.int_limit_mediant_search
	local search_args = { ["equave"] = equave, ["int_limit"] = int_limit, ["tenney_height"] = tenney_height }
	local ratios = med.find_only_mediants_by_search_func(init_ratios, search_func, search_args)
	
	-- Convert to ratios that Module:Rational can work with
	for i = 1, #ratios do
		ratios[i] = rat.new(ratios[i][1], ratios[i][2])
	end
	
	return ratios
end

-- Int limit search function, with equave and tenney height cutoffs.
-- If nil is passed in for the tenney height, it will defualt to infinity.
-- To be passed into mediant-search function, as part of int-limit-search
-- function call.
function p.int_limit_mediant_search(mediant_data, search_args)
	local mediant   = mediant_data["mediant"]
	local ratio_1   = mediant_data["ratio_1"]
	local equave        = search_args["equave"]
	local int_limit     = search_args["int_limit"]
	local tenney_height = search_args["tenney_height"]
	
	local equave_as_float = rat.as_float(equave)
	local rat_1_as_float = ratio_1[1] / ratio_1[2]
	local mediant_th = math.log(mediant[1] * mediant[2]) / math.log(2)
	
	return math.max(mediant[1], mediant[2]) <= int_limit and rat_1_as_float < equave_as_float and mediant_th <= tenney_height
end

--------------------------------------------------------------------------------
------------------------ SUBGROUP-BASED SEARCH FUNCTION ------------------------
--------------------------------------------------------------------------------

-- Int-limit-based search; finds ratios between 1/1 and an equave, within a sub-
-- group. An int limit is passed in to limit the size of output, since subgroups
-- are infinite sets. An optional tenney height can be passed in to further
-- limit output.
-- Unlike int limit search, subgroup search can allow for very high int limits,
-- as long as the subgroup is reasonably small and has reasonably small terms.
-- Note that members in a subgroup need not be prime, as long as the terms are,
-- for the most part, relatively prime.
function p.search_by_subgroup_within_equave(subgroup, int_limit, equave, tenney_height)
	local subgroup = subgroup or { 2, 3, 7 }
	local int_limit = int_limit or 50
	local equave = equave or rat.new(2,1)			-- Defualt equave is 2/1.
	local tenney_height = tenney_height or 1/0		-- Defualt tenney height is infinity.
	
	-- Be absolutely sure the subgroup's members are sorted!
	table.sort(subgroup)
	
	-- Find all possible products given the factors in the subgroup.
	-- These will be used to find all possible ratios.
	local products = {{1}}
	local new_products_found = true
	while new_products_found do
		local new_products = {}
		for i = 1, #subgroup do
			for j = 1, #products[#products] do
				local new_product = products[#products][j] * subgroup[i]
				if new_product <= int_limit then
					local product_already_added = false
					for k = 1, #new_products do
						product_already_added = product_already_added or new_product == new_products[k]
						if product_already_added then break end
					end
					if not product_already_added then
						table.insert(new_products, new_product)
					end
				end
			end
		end
		if #new_products == 0 then
			new_products_found = false
		else
			table.insert(products, new_products)
		end
	end
	
	-- Consolidate and sort products
	local consolidated_products = {}
	for i = 1, #products do
		for j = 1, #products[i] do
			table.insert(consolidated_products, products[i][j])
		end
	end
	products = consolidated_products
	table.sort(products)

	-- Using the products produced earlier, combine them to make all possible
	-- ratios from 1/1 to the equave. Ratios with non-coprime numerator and
	-- denominator, or exceed the tenney height, are omitted.
	local ratios = {}
	local equave_as_float = rat.as_float(equave)
	for i = 1, #products do
		local denominator = products[i]
		for j = i, #products do
			local numerator = products[j]
			local gcd = utils._gcd(numerator, denominator)
			if gcd == 1 then
				local within_equave = numerator / denominator <= equave_as_float
				local within_tenney_height = math.log(numerator * denominator) / math.log(2) <= tenney_height
				if within_equave and within_tenney_height then
					table.insert(ratios, {numerator, denominator})
				else
					break
				end
			end
		end
	end

	-- Convert to ratios that Module:Rational can work with
	for i = 1, #ratios do
		ratios[i] = rat.new(ratios[i][1], ratios[i][2])
	end
	
	return ratios
end

--------------------------------------------------------------------------------
---------------------- PRIME-LIMIT-BASED SEARCH FUNCTION -----------------------
--------------------------------------------------------------------------------

-- Int-limit-based search; finds ratios between 1/1 and an equave, within a
-- prime limit. An int limit is passed in to limit the size of output, since
-- prime limits are inifinite sets. An optional tenney height can be passed in
-- to further limit output.
-- Like subgroup search, prime limit search can also allow for very high int
-- limits, as long as the prime is reasonably small.
function p.search_by_prime_limit_within_equave(prime_limit, int_limit, equave, tenney_height)
	local prime_limit = 3
	local int_limit = int_limit or 1000
	local equave = equave or rat.new(2,1)			-- Defualt equave is 2/1.
	local tenney_height = tenney_height or 1/0		-- Defualt tenney height is infinity.
	
	-- Find all primes up to the prime limit.
	local primes = {}
	for i = 2, prime_limit do
		local is_prime = true
		for j = 2, math.floor(math.sqrt(i)) do
			if i % j == 0 then
				is_prime = false
				break
			end
		end
		if is_prime then
			table.insert(primes, i)
		end
	end
	
	-- Perform subgroup search on the primes found, as subgroup-search code can
	-- be reused for prime-limit search.
	return p.search_by_subgroup_within_equave(primes, int_limit, equave, tenney_height)
end

--------------------------------------------------------------------------------
------------------------- PARAM-BASED SEARCH FUNCTIONS -------------------------
--------------------------------------------------------------------------------

-- Search for ratios based on params passed in. Each param is its own
-- function call. Params must be parsed first.
function p.search_by_params(params, max_cents)
	local max_cents = max_cents or 1200
	
	-- First get ratios up to an int limit. If no int limit was passed in, it
	-- will default to the hardcoded default value.
	local ratios = {}
	if params["Int Limit"] ~= nil then
		ratios = p.search_by_int_limit(params["Int Limit"], max_cents)
	end
	
	if params["Prime Limit"] ~= nil then
		ratios = p.filter_by_prime_limit(ratios, params["Prime Limit"])
	end
	
	if params["Tenney Height"] ~= nil then
		ratios = p.filter_by_tenney_height(ratios, params["Tenney Height"])
	end
	
	return ratios
end

-- Parse search params.
function p.parse_search_params(search_params)
	local parsed = tip.parse_kv_pairs(search_params)
	
	if parsed["Int Limit"] ~= nil then
		parsed["Int Limit"] = tonumber(parsed["Int Limit"])
	end
	
	if parsed["Tenney Height"] ~= nil then
		parsed["Tenney Height"] = tonumber(parsed["Tenney Height"])
	end
	
	if parsed["Prime Limit"] ~= nil then
		parsed["Prime Limit"] = tonumber(parsed["Prime Limit"])
	end
	
	return parsed
end

function p.search_param_footnotes(search_params)
	local result = "Not all notable ratios may be shown, and other interpretations are possible."
	
	if search_params["Prime Limit"] ~= nil then
		result = string.format("Ratios shown are within the [[%s-limit]]. %s", search_params["Prime Limit"], result)
	elseif search_params["Int Limit"] ~= nil then
		result = string.format("Ratios shown are %s-[[integer-limit|integer limit]]. %s", search_params["Int Limit"], result)
	end
	return result
end

--------------------------------------------------------------------------------
---------------------------- RATIO FILTER FUNCTIONS ----------------------------
--------------------------------------------------------------------------------

-- Filter ratios by Tenney height.
function p.filter_by_tenney_height(ratios, tenney_height)
	local tenney_height = tenney_height or 10
	local filtered_ratios = {}
	
	for i = 1, #ratios do
		local curr_tenney_height = rat.tenney_height(ratios[i])
		if curr_tenney_height <= tenney_height then
			table.insert(filtered_ratios, ratios[i])
		end
	end
	return filtered_ratios
end

-- Filter ratios by prime limit.
function p.filter_by_prime_limit(ratios, prime_limit)
	local prime_limit = prime_limit or 41
	local filtered_ratios = {}
	
	for i = 1, #ratios do
		local curr_max_prime = rat.max_prime(ratios[i])
		if curr_max_prime <= prime_limit then
			table.insert(filtered_ratios, ratios[i])
		end
	end
	return filtered_ratios
end

-- Filter ratios by (prime) subgroup. EG: 2.3.5.7
function p.filter_by_subgroup(ratios, subgroup)
	
end

-- Filter ratios by rational/nonprime subgroup. EG, 2.7/2.11/2, or 2.5.7.9
-- Does not support irrational subgroups.
function p.filter_by_nonprime_subgroup(ratios, subgroup)
	
end

--------------------------------------------------------------------------------
--------------------------- RATIO SORTING FUNCTIONS ----------------------------
--------------------------------------------------------------------------------

-- Sorts ratios by closeness to cent values.
function p.sort_by_closeness_to_cent_values(ratios, cent_values, tolerance)
	local tolerance = tolerance or 30
	
	local sorted_ratios = {}
	local curr_index = 1		-- Index of current_ratio
	for i = 1, #cent_values do
		local lower_bound = cent_values[i] - tolerance
		local upper_bound = cent_values[i] + tolerance
		local cents_within_range = true
		local curr_ratios = {}
		
		for j = curr_index, #ratios do
			local curr_ratio = ratios[j]
			local curr_cents = rat.cents(curr_ratio)
			
			if lower_bound < curr_cents and curr_cents < upper_bound then
				table.insert(curr_ratios, curr_ratio)
			--elseif curr_cents > upper_bound then
			--	curr_index = j
			--	break
			end
		end
		
		table.insert(sorted_ratios, curr_ratios)
	end
	
	return sorted_ratios
end

--------------------------------------------------------------------------------
------------------------ RATIO PARSING/INPUT FUNCTIONS -------------------------
--------------------------------------------------------------------------------

-- Parse a list of ratios from a string. String is formatted as follows:
-- "a/b; c/d; e/f; g/h"
function p.parse_ratios(unparsed)
	local parsed = tip.parse_numeric_pairs(unparsed)
	for i = 1, #parsed do
		parsed[i] = rat.new(parsed[i][1], parsed[i][2])
	end
	return parsed
end

--------------------------------------------------------------------------------
---------------------------- RATIO STRING FUNCTIONS ----------------------------
--------------------------------------------------------------------------------

-- Convert a table of ratios into a string, with options for links and delimiter
function p.ratios_as_text(ratios, add_links, delimiter)
	local add_links = add_links == true
	local delimiter = delimiter or ", "
	
	local text = ""
	if #ratios ~= 0 then
		text = add_links and string.format("[[%s]]", rat.as_ratio(ratios[1])) or rat.as_ratio(ratios[1])
		for i = 2, #ratios do
			text = text .. (add_links and string.format("%s[[%s]]", delimiter, rat.as_ratio(ratios[i])) or string.format("%s%s", delimiter, rat.as_ratio(ratios[i])))
		end
	end
	return text
end

-- Convert a table of ratios (tables, as defined by rational module) into a
-- line of text, with options for delimiters.
function p.ratios_as_texts(ratios, add_links, delimiter)
	local add_links = add_links == true
	local delimiter = delimiter or ", "
	
	local texts = {}
	for i = 1, #ratios do
		local text = p.ratios_as_text(ratios[i], add_links, delimiter)
		table.insert(texts, text)
	end
	return texts
end

function p.tester()

	local ratios = p.search_by_prime_limit_within_equave()
	
	return p.ratios_as_text(ratios)
end

return p