Module:JI ratios: Difference between revisions

Ganaram inukshuk (talk | contribs)
m m.member_name -> med.member_name; use standalone int-limit search function
Ganaram inukshuk (talk | contribs)
m comments
 
(82 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 yesno = require("Module:Yesno")
local med = require("Module:Mediants")
p = {}


-- TODO:
local p = {}
-- Adopt mediants module by using custom mediant search function
-- 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 14: Line 13:
-- This is a successor/replacement for JI ratio finder.
-- This is a successor/replacement for JI ratio finder.


-- JI ratios are searched by the following params in a hierarchy:
-- TODO: Refactor code such that:
-- - The absolute minimum for ratio search int limit, which limits the maximum
-- - For int-limit search, int limit is the first arg, and equave and min/max
--  size of the numerator and denominator.
--  cents default to 2/1, 0c, and 1200c respectively.
-- - If subgroup is present, ratios are searched by subgroup within an int
--  (int_limit, equave)
--  limit. Subgroup takes precedence over prime limit, as subgroup is
--  (int_limit, min_cents, max_cents)
--  (typically) a subset of prime limit, so prime limit is ignored. (Nonprime
-- - For odd-limit search, odd limit is the first arg, int limit defaults to
--  subgroups take precedence over prime subgroups.)
--  twice the odd limit, and equave and min/max cents default to 2/1, 0c, and
-- - If prime limit is present, ratios are searched by prime limit within an int
--  1200c respectively.
--  limit.
--  (odd_limit, int_limit, equave)
-- NOTES:
--  (odd_limit, int_limit, min_cents, max_cents)
-- - Prime limits are infinite sets, so int limit is used to restrain the set
-- - For prime-limit search, prime-limit is the first arg, int limit defaults to
--  to a finite size. The same is true for subgroup.
--  twice the largest prime, and equave and min/max cents default to 2/1, 0c,
-- - Tenney height is used for further filtering of ratios, and is considered
--  and 1200c respectively.
--  optional.
--  (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
-- up to some integer limit. Search hierarchy is as follows:
-- - Search by subgroup (subgroup elements may be nonprime or rational)
-- - Then search by prime limit
-- - Then search by odd limit
-- - Then search by int limit
 
-- Optional args omit ratios that don't meet certain conditions, and are used
-- to further limit the number of ratios found. Current options include:
-- - Tenney Height: omits ratios that exceed some max Tenney height. Has no
--  effect if no Tenney height is passed in.
-- - Complements Only: omits ratios and their equave complements if either would
--  be omitted by Tenney height, or if no Tenney height is entered, omits
--  ratios whose complements are missing.


-- INT_LIMIT_MAX is hardcoded to limit the size of output.
local DEFAULT_EQUAVE = rat.new(2)
-- 400 -> ~24000 ratios
local DEFAULT_INT_LIMIT = 30
-- 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 ------------------------
------------------------------- FILTER FUNCTIONS -------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Find JI ratios up to an integer limit within the octave by finding mediants.
-- Filter function removes certain ratios that don't meet some requirement.
-- A cent value can be passed in to either exclude ratios that are above an
-- Filters currently include:
-- interval below the octave or include ratios above the octave.
-- - Removing ratios that exceed a max Tenney height.
function p.search_by_int_limit(integer_limit, max_cents)
-- - Removing ratios whose complement would exceed a max Tenney height or int limit
local max_cents = max_cents or 1200
function p.filter_ratios(ratios, equave, int_limit, tenney_height, complements_only)
local integer_limit = integer_limit or DEFAULT_INT_LIMIT
local filtered_ratios = {}
for i = 1, #ratios do
local complement = rat.mul(rat.inv(ratios[i]), equave)
local ratio_th  = rat.tenney_height(ratios[i])
local compl_th  = rat.tenney_height(complement)
-- Are the ratios within the Tenney height?
-- Has no effect (defaults to TRUE) if Tenney height is infinity.
local ratio_within_th = ratio_th <= tenney_height
local compl_within_th = compl_th <= tenney_height
-- 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])
end
end
end
integer_limit = math.max(0, math.min(INT_LIMIT_MAX, integer_limit))
return filtered_ratios
end
 
-- Filters ratios from a table of ratios, returning an array of ratios within
-- the cent range and preserving the original table. Meant for searching for
-- multiple ranges. TODO: write
function p.filter_ratios_within_cent_range(ratios, min_cents, max_cents)
local init_ratios = {{1,1}, {2,1}}
end
local ratios = med.find_only_mediants_by_int_limit(init_ratios, integer_limit)
 
--------------------------------------------------------------------------------
-------------------------- INT-LIMIT SEARCH FUNCTION ---------------------------
--------------------------------------------------------------------------------
 
-- 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)
return p.search_by_int_limit_within_cents(0, rat.cents(equave), int_limit)
end
 
-- Cent range search finds ratios within a cent range. Meant for searching for
-- ratios within a single interval range. If searching for ratios within many
-- interval ranges, then try a broad search first.
function p.search_by_int_limit_within_cents(min_cents, max_cents, int_limit)
-- If the max cents is greater than the octave, duplicate all existing
local init_ratios = {{1,1}, {1,0}}
-- ratios and raise them by the required number of octaves.
local ratios = med.find_only_mediants(init_ratios, 2)
if max_cents > 1200 then
for i = 3, int_limit do
local new_ratios = {}
ratios = med.find_mediants_by_int_limit(ratios, i)
local num_octaves_up = math.ceil(max_cents / 1200)
for j = 1, num_octaves_up do
-- Purge ratios from the beginning.
for i = 2, #ratios do
-- If the first and second ratio are smaller than min_cents, and smaller
local num = ratios[i][1] * math.pow(2, j)
-- than max_cents, then remove the first ratio. Keeping the first ratio
local den = ratios[i][2]
-- would add mediants outside the cent range.
local cents_1 = utils.log2(ratios[1][1] / ratios[1][2]) * 1200
local gcd = utils._gcd(num, den)
local cents_2 = utils.log2(ratios[2][1] / ratios[2][2]) * 1200
num = num / gcd
if cents_1 < min_cents and cents_2 <= min_cents and cents_1 < max_cents and cents_2 < max_cents then
den = den / gcd
table.remove(ratios, 1)
if math.max(num, den) <= integer_limit then
table.insert(new_ratios, {num, den})
end
end
end
end
for i = 1, #new_ratios do
-- Purge ratios from the end.
table.insert(ratios, new_ratios[i])
-- 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
end
end
-- Remove any ratios that exceed the max cents
-- Convert to ratios that Module:Rational can work with
-- Convert to ratios that Module:Rational can work with
for i = 1, #ratios do
for i = 1, #ratios do
ratios[i] = rat.new(ratios[i][1], ratios[i][2])
ratios[i] = rat.new(ratios[i][1], ratios[i][2])
end
-- Remove any remaining ratios that fall outside the cent range.
while rat.cents(ratios[1]) < min_cents do
table.remove(ratios, 1)
end
while rat.cents(ratios[#ratios]) > max_cents do
table.remove(ratios, #ratios)
end
end
Line 93: Line 162:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------------------------ SUBGROUP-BASED SEARCH FUNCTION ------------------------
-------------------------- ODD-LIMIT SEARCH FUNCTION ---------------------------
--------------------------------------------------------------------------------
 
-- 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
 
--------------------------------------------------------------------------------
------------------------- PRIME-LIMIT SEARCH FUNCTION --------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Subgroup-based search
-- Convert prime limit into equivalent subgroup.
-- Can support higher int limits than int-limit search can, provided the sub-
-- EG, 11-prime-limit becomes 2.3.5.7.11
-- group is sufficiently small (about 10 members)
function p.prime_limit_to_subgroup(prime_limit)
function p.search_by_subgroup(subgroup, int_limit, equave)
local subgroup = {}
local subgroup = subgroup or { 2, 3, 7, 11 }
for i = 3, prime_limit do
local int_limit = int_limit or 50
local is_prime = true
local equave = equave or {2,1}
for j = 2, math.floor(math.sqrt(i)) do
if i % j == 0 then
local possible_values = p.find_products(subgroup, int_limit)
is_prime = false
local ratios = p.find_ratios_using_values(possible_values, equave)
break
end
-- Convert to ratios that Module:Rational can work with
end
for i = 1, #ratios do
if is_prime then
ratios[i] = rat.new(ratios[i][1], ratios[i][2])
table.insert(subgroup, rat.new(i))
end
end
return subgroup
end
 
-- 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
end
return ratios
return ratios
end
end


-- Helper function
--------------------------------------------------------------------------------
-- Finds all eligible values for the numerator and denominator
---------------------------- SUBGROUP SEARCH FUNCTION --------------------------
function p.find_products(factors, max_product)
--------------------------------------------------------------------------------
local factors = factors or { 2, 3, 7, 11 }
 
local max_product = max_product or 50
-- 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)
local ratios = p.search_by_subgroup_within_cents(0, rat.cents(equave), int_limit, subgroup)
return ratios
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
-- Perform a breadth-first-search.
-- Find all possible ways to multiply subgroup elements with one another
-- Starting with the number 1 at the root node of a (simulated) search tree,
-- using breadth-first-search. Products found this way should not exceed the
-- explore the possible products (child nodes) of multiplying that number
-- int limit, and if a subgroup element is rational, neither its numerator
-- with exactly one each of the given factors. Any products that are less
-- nor denominator should exceed the int limit.
-- than the max product are added to the search tree, and the search
local products = { rat.new(1) }
-- recurses for each child node by finding its children produced by multi-
local i = 1
-- plying by one of each factor. The search on any one branch stops if the
while i <= #products do
-- resulting products exceed that of the max product.
-- Multiply each subgroup element by the current ratio. The table of
-- Products are stored as a jagged array, where the index of each inner
-- product ratios created this way is merged with the running table of
-- array is the search depth. Duplicate products are excluded.
-- ratios. This is the Cartesian product of the single ratio as a set,
-- NOTE: the search starts with the number 1 for this operation to work. To
-- with the subgroup elements as a set, or {p/q} X subgroup.
-- make sense of this, this operation can be thought of a BFS for powers
-- pi raising factors fi (f1^p1 * f2^p2 * ... * fn^pn), so 1 is where each
-- factor fi is raised by zero, thus BFS increases the exponents by 1.
local products = {{1}}
local new_products_found = true
while new_products_found do
local new_products = {}
local new_products = {}
for i = 1, #factors do
for j = 1, #subgroup do
for j = 1, #products[#products] do
local new_ratio = rat.mul(products[i], subgroup[j])
local new_product = products[#products][j] * factors[i]
if rat.is_within_int_limit(new_ratio, int_limit) and not p.find_ratio_in_table(new_products, new_ratio) then
if new_product <= max_product then
table.insert(new_products, new_ratio)
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
end
end
if #new_products == 0 then
new_products_found = false
-- Merge new products with the table of products, omitting duplicates.
else
p.merge_tables(products, new_products)
table.insert(products, new_products)
i = i + 1
end
end
end
-- Consolidate and sort products
-- Sort for next step
local consolidated_products = {}
table.sort(products, rat.lt)
-- Use the products found to find all ratios between 1 and the equave.
-- For each ratio in the table of products, create a set of new ratios by
-- having that ratio be the numerator and all successive ratios be possible
-- denominators. Store these new ratios in a table, and repeat with all
-- successive products, omitting duplicats. From earlier testing, this is
-- faster than performing BFS on each ratio, and yields the same results.
local ratios = {}
for i = 1, #products do
for i = 1, #products do
for j = 1, #products[i] do
local new_ratios = {}
table.insert(consolidated_products, products[i][j])
for j = i, #products do
local new_ratio = rat.div(products[j], products[i])
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
table.insert(new_ratios, new_ratio)
end
end
end
-- Merge new ratios with the table of ratios, omitting duplicates.
p.merge_tables(ratios, new_ratios)
end
end
products = consolidated_products
table.sort(products)
return products
-- Sort
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
end
end


-- Finds all potential ratios whose numerator and denominator is from the list
--------------------------------------------------------------------------------
-- of given values, and whose value, as a float, is between 1 and a given
------------------------------- HELPER FUNCTIONS -------------------------------
-- equave.
--------------------------------------------------------------------------------
function p.find_ratios_using_values(values, equave)
 
local values = values or p.find_products()
-- Heleper function; merges elements from source table with destination table
local equave = equave or { 2, 1 }
-- while disallowing duplicates.
function p.merge_tables(dest_table, source_table)
local equave_as_float = equave[1]/equave[2]
for i = 1, #source_table do
if not p.find_ratio_in_table(dest_table, source_table[i]) then
local ratios = {}
table.insert(dest_table, source_table[i])
for i = 1, #values do
local denominator = values[i]
for j = i, #values do
local numerator = values[j]
local gcd = utils._gcd(numerator, denominator)
if gcd == 1 then
local within_equave = numerator / denominator <= equave_as_float
if within_equave then
table.insert(ratios, {numerator, denominator})
else
break
end
end
end
end
end
end
return ratios
end
end


-- Helper function for merge function.
function p.find_ratio_in_table(table_, ratio)
local found = false
for i = 1, #table_ do
if rat.as_float(table_[i]) == rat.as_float(ratio) then
found = true
break
end
end
return found
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------------------------- PARAM-BASED SEARCH FUNCTIONS -------------------------
---------------------------- RATIO STRING FUNCTIONS ----------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Search for ratios based on params passed in. Each param is its own
-- Convert a table of ratios into a string, with options for links and delimiter
-- function call. Params must be parsed first.
function p.ratios_as_string(ratios, add_links, delimiter)
function p.search_by_params(params, max_cents)
local add_links = add_links == true
local max_cents = max_cents or 1200
local delimiter = delimiter or ", "
-- First get ratios up to an int limit. If no int limit was passed in, it
local text = ""
-- will default to the hardcoded default value.
if #ratios ~= 0 then
local ratios = {}
text = add_links and string.format("[[%s]]", rat.as_ratio(ratios[1])) or rat.as_ratio(ratios[1])
if params["Int Limit"] ~= nil then
for i = 2, #ratios do
ratios = p.search_by_int_limit(params["Int Limit"], max_cents)
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
end
return text
end
-- Convert a jagged array of ratios into an array of strings
function p.ratios_as_strings(ratios, add_links, delimiter)
local add_links = add_links == true
local delimiter = delimiter or ", "
if params["Prime Limit"] ~= nil then
local texts = {}
ratios = p.filter_by_prime_limit(ratios, params["Prime Limit"])
for i = 1, #ratios do
local text = p.ratios_as_string(ratios[i], add_links, delimiter)
table.insert(texts, text)
end
end
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 params["Tenney Height"] ~= nil then
if parsed["Equave"] ~= nil then
ratios = p.filter_by_tenney_height(ratios, params["Tenney Height"])
parsed["Equave"] = rat.parse(parsed["Equave"])
end
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
if parsed["Int Limit"] ~= nil then
Line 247: Line 386:
end
end
return parsed
if parsed["Subgroup"] ~= nil then
end
local subgroup_elements = tip.parse_numeric_pairs(parsed["Subgroup"], ".", "/", true)
 
for i = 1, #subgroup_elements do
function p.search_param_footnotes(search_params)
subgroup_elements[i] = rat.new(subgroup_elements[i][1], subgroup_elements[i][2])
local result = "Not all notable ratios may be shown, and other interpretations are possible."
end
parsed["Subgroup"] = subgroup_elements
end
if search_params["Prime Limit"] ~= nil then
if parsed["Complements Only"] ~= nil then
result = string.format("Ratios shown are within the [[%s-limit]]. %s", search_params["Prime Limit"], result)
parsed["Complements Only"] = yesno(parsed["Complements Only"])
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
end
return result
return parsed
end
end


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


-- Filter ratios by Tenney height.
-- Function callable by other modules
function p.filter_by_tenney_height(ratios, tenney_height)
-- Ratios are returned as a table, for use with other modules.
local tenney_height = tenney_height or 10
function p._ji_ratios(args)
local filtered_ratios = {}
-- Args for ease of access
equave      = args["Equave"    ] or DEFAULT_EQUAVE
int_limit  = args["Int Limit"  ] or DEFAULT_INT_LIMIT
odd_limit  = args["Odd Limit"  ]
prime_limit = args["Prime Limit"]
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
for i = 1, #ratios do
local ratios = {}
local curr_tenney_height = rat.tenney_height(ratios[i])
if subgroup ~= nil then
if curr_tenney_height <= tenney_height then
ratios = p.search_by_subgroup(equave, int_limit, subgroup)
table.insert(filtered_ratios, ratios[i])
elseif prime_limit ~= nil then
end
ratios = p.search_by_prime_limit(equave, int_limit, prime_limit)
elseif int_limit ~= nil then
ratios = p.search_by_int_limit(equave, int_limit)
end
end
return filtered_ratios
-- Filter ratios
ratios = p.filter_ratios(ratios, equave, int_limit, tenney_height, complements_only)
return ratios
end
end


-- Filter ratios by prime limit.
-- Invokable function; for templates
function p.filter_by_prime_limit(ratios, prime_limit)
-- Ratios are returned as a comma-delimited list. For finer control, it's
local prime_limit = prime_limit or 41
-- necessary to call the "main" function, then further process the results.
local filtered_ratios = {}
function p.ji_ratios(frame)
args = getArgs(frame)
-- Preprocess equave
-- Ratios are searched from 1/1 to some equave (default 2/1), so an equave
-- must be passed in.
args["Equave"] = args["Equave"] ~= nil and rat.parse(args["Equave"])
-- Preprocess int limit
-- Ratios are searched up to some int limit (default 50), so an int limit
-- must be passed in.
args["Int Limit"] = args["Int Limit"] ~= nil and tonumber(args["Int Limit"])
 
-- Preprocess Tenney height
if args["Tenney Height"] ~= nil then
args["Tenney Height"] = tonumber(args["Tenney Height"])
end
-- Preprocess prime limit
if args["Prime Limit"] ~= nil then
args["Prime Limit"] = tonumber(args["Prime Limit"])
end
for i = 1, #ratios do
-- Preprocess subgroup
local curr_max_prime = rat.max_prime(ratios[i])
if args["Subgroup"] ~= nil then
if curr_max_prime <= prime_limit then
local subgroup_elements = tip.parse_numeric_pairs(args["Subgroup"], ".", "/", true)
table.insert(filtered_ratios, ratios[i])
for i = 1, #subgroup_elements do
subgroup_elements[i] = rat.new(subgroup_elements[i][1], subgroup_elements[i][2])
end
end
args["Subgroup"] = subgroup_elements
end
if args["Complements Only"] ~= nil then
args["Complements Only"] = yesno(args["Complements Only"], false)
end
-- Find and return ratios
local result = p.ratios_as_string(p._ji_ratios(args))
local debugg = yesno(frame.args["debug"])
if debugg == true then
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
end
end
return filtered_ratios
end
return frame:preprocess(result)


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


-- Filter ratios by rational/nonprime subgroup. EG, 2.7/2.11/2, or 2.5.7.9
function p.tester()
-- Does not support irrational subgroups.
--return p.ratios_as_string(p._ji_ratios(p.parse_args("Int Limit: 16; Equave: 3/1; Complements Only: 0")))
function p.filter_by_nonprime_subgroup(ratios, subgroup)
--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


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------- RATIO SORTING FUNCTIONS ----------------------------
---------------------------- FUNCTIONS TO BE MOVED -----------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Sorts ratios by closeness to cent values.
-- 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
 
-- Sorts ratios by closeness to cent values. Move to new module?
function p.sort_by_closeness_to_cent_values(ratios, cent_values, tolerance)
function p.sort_by_closeness_to_cent_values(ratios, cent_values, tolerance)
local tolerance = tolerance or 30
local tolerance = tolerance or 30
Line 336: Line 533:
return sorted_ratios
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 tables into a table of text
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
-- Int limit search function, with an equave cutoff.
-- 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 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)
end
end


return p
return p