Module:JI ratios: Difference between revisions

Ganaram inukshuk (talk | contribs)
merge some helper functions; adopt is_within_int_limit function; comments
Ganaram inukshuk (talk | contribs)
m comments
 
(18 intermediate revisions by 2 users not shown)
Line 1: Line 1:
-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
local getArgs = require("Module:Arguments").getArgs
local med = require("Module:Mediants")
local rat = require("Module:Rational")
local rat = require("Module:Rational")
local tip = require("Module:Template input parse")
local utils = require("Module:Utils")
local utils = require("Module:Utils")
local tip = require("Module:Template input parse")
local med = require("Module:Mediants")
local yesno = require("Module:Yesno")
local yesno = require("Module:Yesno")
local getArgs = require("Module:Arguments").getArgs
p = {}


-- TODO
local p = {}
-- - Move filtering functions to separate module?
-- - Transfer control over to new "main" function: p.ji_ratios()


-- 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
-- searching for JI ratios if automatic entry is desired.
-- searching for JI ratios if automatic entry is desired.
-- This is a successor/replacement for JI ratio finder.
-- This is a successor/replacement for JI ratio finder.
-- TODO: Refactor code such that:
-- - For int-limit search, int limit is the first arg, and equave and min/max
--  cents default to 2/1, 0c, and 1200c respectively.
--  (int_limit, equave)
--  (int_limit, min_cents, max_cents)
-- - For odd-limit search, odd limit is the first arg, int limit defaults to
--  twice the odd limit, and equave and min/max cents default to 2/1, 0c, and
--  1200c respectively.
--  (odd_limit, int_limit, equave)
--  (odd_limit, int_limit, min_cents, max_cents)
-- - For prime-limit search, prime-limit is the first arg, int limit defaults to
--  twice the largest prime, and equave and min/max cents default to 2/1, 0c,
--  and 1200c respectively.
--  (prime_limit, int_limit, equave)
--  (prime_limit, int_limit, min_cents, max_cents)
-- - For subgroup search, subgroup is the first arg, there's no default value
--  for int limit (due to complexity of subgroups), and equave and min/max
--  cents default to 2/1, 0c, and 1200c respectively.
--  (subgroup, int_limit, equave)
--  (subgroup, int_limit, min_cents, max_cents)
-- - Filter ratios function is split into two:
--  - Filter ratios by complement removes ratios from a table if its complement
--    is missing. Complements are octave-complements by default.
--  - Filter ratios by tenney height removes ratios from a table if its tenney
--    height exceeds a passed-in value.
-- TODO: write filter function for cent range


-- Module searches for ratios that are, at the minimum, up to an equave and are
-- Module searches for ratios that are, at the minimum, up to an equave and are
-- up to some integer limit. Search hierarchy is as follows:
-- up to some integer limit. Search hierarchy is as follows:
-- - Search by subgroup (includes non-integer and rational elements)
-- - Search by subgroup (subgroup elements may be nonprime or rational)
-- - Then search by prime limit
-- - Then search by prime limit
-- - Then search by odd limit (to be implemented)
-- - Then search by odd limit
-- - Then search by int limit
-- - Then search by int limit


Line 29: Line 55:
--  be omitted by Tenney height, or if no Tenney height is entered, omits
--  be omitted by Tenney height, or if no Tenney height is entered, omits
--  ratios whose complements are missing.
--  ratios whose complements are missing.
local DEFAULT_EQUAVE = rat.new(2)
local DEFAULT_INT_LIMIT = 30


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 37: Line 66:
-- Filters currently include:
-- Filters currently include:
-- - Removing ratios that exceed a max Tenney height.
-- - Removing ratios that exceed a max Tenney height.
-- - Removing ratios whose complement would exceed a max Tenney height.
-- - Removing ratios whose complement would exceed a max Tenney height or int limit
 
function p.filter_ratios(ratios, equave, int_limit, tenney_height, complements_only)
-- TODO: combine into one filter function
 
local filtered_ratios = {}
--[[
for i = 1, #ratios do
 
local complement = rat.mul(rat.inv(ratios[i]), equave)
-- Remove ratios whose complements exceed the int limit and Tenney height.
local ratio_th  = rat.tenney_height(ratios[i])
-- If filtering based on Tenney height is not needed, then Tenney height is set
local compl_th  = rat.tenney_height(complement)
-- to infinity instead, which should be done by the calling function.
function p.filter_ratios_by_complements(ratios, equave, fine_search_args)
-- Are the ratios within the Tenney height?
if fine_search_args["Complements Only"] then
-- Has no effect (defaults to TRUE) if Tenney height is infinity.
local filtered_ratios = {}
local ratio_within_th = ratio_th <= tenney_height
for i = 1, #ratios do
local compl_within_th = compl_th <= tenney_height
local complement = rat.mul(rat.inv(ratios[i]), equave)
if rat.int_limit(complement) <= fine_search_args["Int Limit"] and rat.tenney_height(complement) <= fine_search_args["Tenney Height"] then
-- Is the ratio's complement within the int limit?
local compl_within_int_limit = rat.is_within_int_limit(complement, int_limit)
if complements_only then
if ratio_within_th and compl_within_th and compl_within_int_limit then
table.insert(filtered_ratios, ratios[i])
end
else
if ratio_within_th then
table.insert(filtered_ratios, ratios[i])
table.insert(filtered_ratios, ratios[i])
end
end
end
end
return filtered_ratios
else
return ratios
end
end
return filtered_ratios
end
end


-- Remove ratios that exceed the Tenney height. This does nothing if the Tenney
-- Filters ratios from a table of ratios, returning an array of ratios within
-- height is infinity.
-- the cent range and preserving the original table. Meant for searching for
function p.filter_ratios_by_tenney_height(ratios, equave, fine_search_args)
-- multiple ranges. TODO: write
local filtered_ratios = {}
function p.filter_ratios_within_cent_range(ratios, min_cents, max_cents)
for i = 1, #ratios do
if rat.tenney_height(ratios[i]) <= (fine_search_args["Tenney Height"] or math.huge) then
table.insert(filtered_ratios, ratios[i])
end
end
return filtered_ratios
end
end
]]--


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 79: Line 108:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Int limit search finds ratios from 1/1 to an equave
-- Int limit search finds ratios from 1/1 to an equave, where each ratio's
-- numerator or denominator don't exceed the int limit.
function p.search_by_int_limit(equave, int_limit)
function p.search_by_int_limit(equave, int_limit)
local equave    = equave or rat.new(2,1) -- Defualt equave is 2/1.
return p.search_by_int_limit_within_cents(0, rat.cents(equave), int_limit)
local int_limit = int_limit or 50 -- Default is 50
end
 
-- Find all ratios from 1/1 to the equave by finding mediants between 1/1
-- Cent range search finds ratios within a cent range. Meant for searching for
-- and 1/0. Mediants module has a function for this built-in, but includes
-- ratios within a single interval range. If searching for ratios within many
-- ratios that exceed the equave (for example, if the equave is 2/1, then
-- interval ranges, then try a broad search first.
-- the mediants function will include 3/1 and 4/1), which must be removed
function p.search_by_int_limit_within_cents(min_cents, max_cents, int_limit)
-- afterwards.
local init_ratios = {{1,1}, {1,0}}
local init_ratios = {{1,1}, {1,0}}
local ratios = med.find_only_mediants_by_int_limit(init_ratios, int_limit)
local ratios = med.find_only_mediants(init_ratios, 2)
for i = 3, int_limit do
ratios = med.find_mediants_by_int_limit(ratios, i)
-- Purge ratios from the beginning.
-- If the first and second ratio are smaller than min_cents, and smaller
-- than max_cents, then remove the first ratio. Keeping the first ratio
-- would add mediants outside the cent range.
local cents_1 = utils.log2(ratios[1][1] / ratios[1][2]) * 1200
local cents_2 = utils.log2(ratios[2][1] / ratios[2][2]) * 1200
if cents_1 < min_cents and cents_2 <= min_cents and cents_1 < max_cents and cents_2 < max_cents then
table.remove(ratios, 1)
end
-- Purge ratios from the end.
-- If the 2nd-last ratio and last ratio are greater than max_cents, and
-- larger than min_cents, then remove the last ratio. Keeping the last
-- ratio would add mediants outside the cent range.
local cents_3 = utils.log2(ratios[#ratios-1][1] / ratios[#ratios-1][2]) * 1200
local cents_4 = utils.log2(ratios[#ratios  ][1] / ratios[#ratios  ][2]) * 1200
if cents_3 > max_cents and cents_4 >= max_cents and cents_3 > min_cents and cents_4 > min_cents then
table.remove(ratios, #ratios)
end
end
-- Convert to ratios that Module:Rational can work with
-- Convert to ratios that Module:Rational can work with
Line 97: Line 150:
end
end
-- Remove ratios that exceed the equave.
-- Remove any remaining ratios that fall outside the cent range.
-- Because the ratios are already sorted by cent value, remove ratios from
while rat.cents(ratios[1]) < min_cents do
-- the end of the table until there are no more ratios to remove.
table.remove(ratios, 1)
while rat.gt(ratios[#ratios], equave) do
end
while rat.cents(ratios[#ratios]) > max_cents do
table.remove(ratios, #ratios)
table.remove(ratios, #ratios)
end
end
Line 111: Line 165:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- to be implemented
-- Convert odd limit into equivalent subgroup.
-- EG, 11-odd-limit becomes 2.3.5.7.9.11
-- 2 is part of the subgroup by definition.
function p.odd_limit_to_subgroup(odd_limit)
local subgroup = { rat.new(2) }
for i = 3, odd_limit, 2 do
table.insert(subgroup, rat.new(i))
end
return subgroup
end
 
function p.search_by_odd_limit(equave, int_limit, odd_limit)
local subgroup = p.odd_limit_to_subgroup(odd_limit)
return p.search_by_subgroup_within_cents(0, rat.cents(equave), int_limit, subgroup)
end
 
function p.search_by_odd_limit_within_cents(min_cents, max_cents, odd_limit)
local subgroup = p.odd_limit_to_subgroup(odd_limit)
return p.search_by_subgroup_within_cents(min_cents, max_cents, int_limit, subgroup)
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 117: Line 190:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


function p.search_by_prime_limit(equave, int_limit, prime_limit)
-- Convert prime limit into equivalent subgroup.
local equave      = equave or rat.new(2,1) -- Defualt equave is 2/1.
-- EG, 11-prime-limit becomes 2.3.5.7.11
local int_limit  = int_limit or 50 -- Default is 50
function p.prime_limit_to_subgroup(prime_limit)
local prime_limit = prime_limit or 5 -- Default is 5-prime-limit
local subgroup = {}
 
for i = 3, prime_limit do
-- Convert prime limit into an equivalent subgroup (EG, 7-limit becomes
-- 2.3.5.7) so that it can be passed into the subgroup search function.
local primes = {}
for i = 2, prime_limit do
local is_prime = true
local is_prime = true
for j = 2, math.floor(math.sqrt(i)) do
for j = 2, math.floor(math.sqrt(i)) do
Line 134: Line 203:
end
end
if is_prime then
if is_prime then
table.insert(primes, rat.new(i))
table.insert(subgroup, rat.new(i))
end
end
end
end
return subgroup
-- Perform subgroup search.
end
return p.search_by_subgroup(equave, int_limit, primes)
 
-- Prime limit search finds ratios with prime factors that don't exceed some
-- prime limit.
-- Upper bounds for searching is the equave and int limit.
function p.search_by_prime_limit(equave, int_limit, prime_limit)
local subgroup = p.prime_limit_to_subgroup(prime_limit)
return p.search_by_subgroup_within_cents(0, rat.cents(equave), int_limit, subgroup)
end
 
-- Prime limit search finds ratios with prime factors that don't exceed some
-- prime limit. Searches within a cent range.
function p.search_by_prime_limit_within_cents(min_cents, max_cents, int_limit, prime_limit)
local subgroup = p.prime_limit_to_subgroup(prime_limit)
local ratios = p.search_by_subgroup_within_cents(min_cents, max_cents, int_limit, subgroup)
while rat.cents(ratios[1]) < min_cents do
table.remove(ratios, 1)
end
return ratios
end
end


Line 146: Line 232:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Subgroup search find ratios that are products of at least two non-unique
-- elements from the subgroup.
function p.search_by_subgroup(equave, int_limit, subgroup)
function p.search_by_subgroup(equave, int_limit, subgroup)
local equave    = equave or rat.new(2,1) -- Defualt equave is 2/1.
local ratios = p.search_by_subgroup_within_cents(0, rat.cents(equave), int_limit, subgroup)
local int_limit = int_limit or 50 -- Default is 50
return ratios
local subgroup  = subgroup or {rat.new(2), rat.new(3), rat.new(7)} -- Default is 2.3.7 subgroup
end
 
function p.search_by_subgroup_within_cents(min_cents, max_cents, int_limit, subgroup)
--local equave    = equave or rat.new(2,1) -- Defualt equave is 2/1.
--local int_limit = int_limit or 50 -- Default is 50
--local subgroup  = subgroup or {rat.new(2), rat.new(3), rat.new(7)} -- Default is 2.3.7 subgroup
-- Find all possible ways to multiply subgroup elements with one another
-- Find all possible ways to multiply subgroup elements with one another
Line 189: Line 282:
for j = i, #products do
for j = i, #products do
local new_ratio = rat.div(products[j], products[i])
local new_ratio = rat.div(products[j], products[i])
if rat.as_float(new_ratio) > rat.as_float(equave) then break end
if rat.cents(new_ratio) > max_cents then break end
if not p.find_ratio_in_table(new_ratios, new_ratio) and rat.is_within_int_limit(new_ratio, int_limit) then
if not p.find_ratio_in_table(new_ratios, new_ratio) and rat.is_within_int_limit(new_ratio, int_limit) then
Line 202: Line 295:
-- Sort
-- Sort
table.sort(ratios, rat.lt)
table.sort(ratios, rat.lt)
-- Remove ratios less than minimum
while rat.cents(ratios[1]) < min_cents do
table.remove(ratios, 1)
end
return ratios
return ratios
end
end
--------------------------------------------------------------------------------
------------------------------- HELPER FUNCTIONS -------------------------------
--------------------------------------------------------------------------------


-- Heleper function; merges elements from source table with destination table
-- Heleper function; merges elements from source table with destination table
Line 258: Line 360:
end
end
return texts
return texts
end
--------------------------------------------------------------------------------
---------------------------- ARG-PARSING FUNCTION ------------------------------
--------------------------------------------------------------------------------
-- Parse search args if entered as one string. Use is to be determined.
function p.parse_args(search_args)
local parsed = tip.parse_kv_pairs(search_args)
if parsed["Equave"] ~= nil then
parsed["Equave"] = rat.parse(parsed["Equave"])
end
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
if parsed["Subgroup"] ~= nil then
local subgroup_elements = tip.parse_numeric_pairs(parsed["Subgroup"], ".", "/", true)
for i = 1, #subgroup_elements do
subgroup_elements[i] = rat.new(subgroup_elements[i][1], subgroup_elements[i][2])
end
parsed["Subgroup"] = subgroup_elements
end
if parsed["Complements Only"] ~= nil then
parsed["Complements Only"] = yesno(parsed["Complements Only"])
end
return parsed
end
end


Line 265: Line 406:


-- Function callable by other modules
-- Function callable by other modules
-- Ratios are returned as a table, for use with other modules
-- Ratios are returned as a table, for use with other modules.
function p._ji_ratios(args)
function p._ji_ratios(args)
-- Args for ease of access
-- Args for ease of access
equave      = args["Equave"]
equave      = args["Equave"     ] or DEFAULT_EQUAVE
int_limit  = args["Int Limit"]
int_limit  = args["Int Limit" ] or DEFAULT_INT_LIMIT
odd_limit  = args["Odd Limit"]
odd_limit  = args["Odd Limit" ]
prime_limit = args["Prime Limit"]
prime_limit = args["Prime Limit"]
subgroup    = args["Subgroup"]
subgroup    = args["Subgroup"   ]
-- Filtering args
tenney_height    = args["Tenney Height"  ] or 1/0 -- Default Tenney height is infinity
complements_only = args["Complements Only"] or false -- Default is to include all ratios
local ratios = {}
local ratios = {}
Line 282: Line 427:
ratios = p.search_by_int_limit(equave, int_limit)
ratios = p.search_by_int_limit(equave, int_limit)
end
end
-- Filter ratios
ratios = p.filter_ratios(ratios, equave, int_limit, tenney_height, complements_only)
return ratios
return ratios
Line 287: Line 435:


-- Invokable function; for templates
-- Invokable function; for templates
-- Ratios are returned as a comma-delimited list
-- Ratios are returned as a comma-delimited list. For finer control, it's
-- necessary to call the "main" function, then further process the results.
function p.ji_ratios(frame)
function p.ji_ratios(frame)
args = getArgs(frame)
args = getArgs(frame)
Line 294: Line 443:
-- Ratios are searched from 1/1 to some equave (default 2/1), so an equave
-- Ratios are searched from 1/1 to some equave (default 2/1), so an equave
-- must be passed in.
-- must be passed in.
args["Equave"] = args["Equave"] ~= nil and rat.parse(args["Equave"]) or rat.new(2,1)
args["Equave"] = args["Equave"] ~= nil and rat.parse(args["Equave"])
-- Preprocess int limit
-- Preprocess int limit
-- Ratios are searched up to some int limit (default 50), so an int limit
-- Ratios are searched up to some int limit (default 50), so an int limit
-- must be passed in.
-- must be passed in.
args["Int Limit"] = args["Int Limit"] ~= nil and tonumber(args["Int Limit"]) or 50
args["Int Limit"] = args["Int Limit"] ~= nil and tonumber(args["Int Limit"])


-- Preprocess Tenney height
-- Preprocess Tenney height
Line 325: Line 474:
-- Find and return ratios
-- Find and return ratios
ratios = p._ji_ratios(args)
local result = p.ratios_as_string(p._ji_ratios(args))
return p.ratios_as_string(ratios)
local debugg = yesno(frame.args["debug"])
if debugg == true then
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
end
return frame:preprocess(result)
 
end
end


function p.tester()
function p.tester()
return p.ratios_as_string(p.search_by_subgroup())
--return p.ratios_as_string(p._ji_ratios(p.parse_args("Int Limit: 16; Equave: 3/1; Complements Only: 0")))
--return p.ratios_as_string(p.search_by_prime_limit_within_cents(372, 440, 17, 30))
return p.ratios_as_string(p.search_by_odd_limit(rat.new(2), 15, 15*2))
end
end