- 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
|
|
|
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 = {}
-- 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 int 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 ------------------------
--------------------------------------------------------------------------------
-- 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_within_equave(equave, int_limit, 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}, {1,0} }
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
-- Remove ratios that exceed the equave.
-- Note that mediant search results in sorted ratios, so remove them from
-- the end until there's no more to remove.
while rat.gt(ratios[#ratios], equave) do
table.remove(ratios, #ratios)
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 ------------------------
--------------------------------------------------------------------------------
-- Subgroup-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, equave, int_limit, 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, equave, int_limit, tenney_height)
local prime_limit = prime_limit or 5
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, equave, int_limit, 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, equave)
local equave = equave or rat.new(2,1)
-- 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_within_equave(equave, params["Int Limit"], 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
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["Int Limit"] ~= nil then
local tenney_height_text = string.format("Ratios shown are within the %s-integer limit", search_params["Int Limit"])
local int_limit_text = search_params["Tenney Height"] ~= nil and string.format(", capped at a Tenney height of %.1f.", search_params["Tenney Height"]) or "."
result = tenney_height_text .. ". " .. result
end
return result
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 primes = { 2, 3, 5, 7, 11, 13, 17, 19 }
local ratios = p.search_by_subgroup_within_equave(nil, primes, 4000, nil)
return p.ratios_as_text(ratios)
end
return p