Module:Rational: Difference between revisions
m A bit more of FJS implemented |
ArrowHead294 (talk | contribs) mNo edit summary |
||
(60 intermediate revisions by 6 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
-- | local seq = require("Module:Sequence") | ||
local utils = require("Module:Utils") | |||
-- enter a numerator n and denominator m | |||
-- returns a table of prime factors | |||
-- similar to a monzo, but the indices are prime numbers. | |||
function p.new(n, m) | function p.new(n, m) | ||
m = m or 1 | m = m or 1 | ||
Line 8: | Line 12: | ||
return { nan = true } | return { nan = true } | ||
elseif n == 0 then | elseif n == 0 then | ||
return { zero = true, sign = | return { zero = true, sign = utils.signum(m) } | ||
elseif m == 0 then | elseif m == 0 then | ||
return { inf = true, sign = | return { inf = true, sign = utils.signum(n) } | ||
end | end | ||
local sign = | local sign = utils.signum(n) * utils.signum(m) | ||
n = n * | -- ensure n and m are positive | ||
m = m * | n = n * utils.signum(n) | ||
local n_factors = | m = m * utils.signum(m) | ||
local m_factors = | -- factorize n and m separately | ||
local n_factors = utils.prime_factorization_raw(n) | |||
local m_factors = utils.prime_factorization_raw(m) | |||
local factors = n_factors | local factors = n_factors | ||
factors.sign = sign | factors.sign = sign | ||
-- subtract the factors of m from the factors of n | |||
for factor, power in pairs(m_factors) do | for factor, power in pairs(m_factors) do | ||
factors[factor] = factors[factor] or 0 | factors[factor] = factors[factor] or 0 | ||
factors[factor] = factors[factor] - power | factors[factor] = factors[factor] - power | ||
if factors[factor] == 0 then | if factors[factor] == 0 then | ||
factors[factor] = nil | factors[factor] = nil -- clear the zeros | ||
end | end | ||
end | end | ||
Line 31: | Line 38: | ||
-- copy a rational number | -- copy a rational number | ||
function p.copy(a) | function p.copy(a) | ||
b = {} | local b = {} | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
b[factor] = power | b[factor] = power | ||
Line 45: | Line 52: | ||
end | end | ||
return val | return val | ||
end | |||
-- create a rational number from a string of whitespace-separated integers | |||
function p.from_ket(s) | |||
local factor = 1 | |||
local a = { sign = 1 } | |||
for i in s:gmatch("%S+") do | |||
local power = tonumber(i) | |||
if power == nil then | |||
return nil | |||
end | |||
-- find the next prime | |||
factor = factor + 1 | |||
while not utils.is_prime(factor) do | |||
factor = factor + 1 | |||
end | |||
if power ~= 0 then | |||
a[factor] = power | |||
end | |||
end | |||
return a | |||
end | end | ||
Line 57: | Line 87: | ||
table.insert(data, n) | table.insert(data, n) | ||
local frac = p.from_continued_fraction(data) | local frac = p.from_continued_fraction(data) | ||
if type(stop) == | if type(stop) == "function" and stop(frac) then | ||
break | break | ||
elseif type(stop) == | elseif type(stop) == "number" and i >= stop then | ||
break | break | ||
end | end | ||
table.insert(convergents, frac) | table.insert(convergents, frac) | ||
x = x - n | x = x - n | ||
if x == 0 then | |||
break | |||
end | |||
x = 1 / x | x = 1 / x | ||
i = i + 1 | i = i + 1 | ||
Line 71: | Line 104: | ||
-- determine whether a rational number is a convergent or a semiconvergent to `x` | -- determine whether a rational number is a convergent or a semiconvergent to `x` | ||
-- TODO: document how this works | |||
function p.converges(a, x) | function p.converges(a, x) | ||
local | local _, m_a = p.as_pair(a) | ||
local convergents = p.convergents( | local convergents = p.convergents(x, function(b) | ||
local _, m_b = p.as_pair(b) | |||
return m_b >= m_a * 10000 | |||
end) | |||
for _, b in ipairs(convergents) do | |||
for | |||
if p.eq(a, b) then | if p.eq(a, b) then | ||
return | return "convergent" | ||
end | end | ||
end | end | ||
for i = 2, #convergents - 1 do | for i = 2, #convergents - 1 do | ||
local n_delta, m_delta = p.as_pair(convergents[i]) | local n_delta, m_delta = p.as_pair(convergents[i]) | ||
Line 97: | Line 128: | ||
end | end | ||
if p.eq(a, c) then | if p.eq(a, c) then | ||
return | return "semiconvergent" | ||
end | end | ||
end | end | ||
end | end | ||
return false | return false | ||
end | |||
-- attempt to identify the ratio as a simple S-expression | |||
-- returns a table of matched expressions | |||
function p.find_S_expression(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero or a.sign < 0 then | |||
return {} | |||
end | |||
if p.eq(a, 1) then | |||
return {} | |||
end | |||
local max_prime = p.max_prime(a) | |||
if seq.square_superparticulars[max_prime] == nil then | |||
return {} | |||
end | |||
local expressions = {} | |||
local superparticular_indices = {} | |||
local superparticular_ratios = {} | |||
for _, k_array in pairs(seq.square_superparticulars) do | |||
for _, k in ipairs(k_array) do | |||
if k <= 1000 then | |||
table.insert(superparticular_indices, k) | |||
local Sk_num = p.pow(p.new(k), 2) | |||
local Sk_den = p.mul(k - 1, k + 1) | |||
local Sk = p.div(Sk_num, Sk_den) | |||
superparticular_ratios[k] = Sk | |||
end | |||
end | |||
end | |||
-- is it Sk? | |||
for _, k in ipairs(superparticular_indices) do | |||
if p.eq(a, superparticular_ratios[k]) then | |||
table.insert(expressions, "S" .. k) | |||
end | |||
end | |||
-- is it Sk*S(k+1) or Sk/S(k+1) or Sk^2*S(k+1) or Sk*S(k+1)^2? | |||
for _, k in ipairs(superparticular_indices) do | |||
local r1 = superparticular_ratios[k] | |||
local r2 = superparticular_ratios[k + 1] | |||
if r1 and r2 then | |||
if p.eq(a, p.mul(r1, r2)) then | |||
table.insert(expressions, "S" .. k .. " × S" .. (k + 1)) | |||
end | |||
if p.eq(a, p.div(r1, r2)) then | |||
table.insert(expressions, "S" .. k .. " / S" .. (k + 1)) | |||
end | |||
if p.eq(a, p.mul(p.pow(r1, 2), r2)) then | |||
table.insert(expressions, "S" .. k .. "<sup>2</sup> × S" .. (k + 1)) | |||
end | |||
if p.eq(a, p.mul(r1, p.pow(r2, 2))) then | |||
table.insert(expressions, "S" .. k .. " * S" .. (k + 1) .. "<sup>2</sup>") | |||
end | |||
end | |||
end | |||
-- is it Sk/S(k+2)? | |||
for _, k in ipairs(superparticular_indices) do | |||
local r1 = superparticular_ratios[k] | |||
local r2 = superparticular_ratios[k + 2] | |||
if r1 and r2 then | |||
if p.eq(a, p.div(r1, r2)) then | |||
table.insert(expressions, "S" .. k .. " / S" .. (k + 2)) | |||
end | |||
end | |||
end | |||
-- is it S(k-1)*Sk*S(k+1)? | |||
for _, k in ipairs(superparticular_indices) do | |||
local r1 = superparticular_ratios[k - 1] | |||
local r2 = superparticular_ratios[k] | |||
local r3 = superparticular_ratios[k + 1] | |||
if r1 and r2 and r3 then | |||
if p.eq(a, p.mul(r1, p.mul(r2, r3))) then | |||
table.insert(expressions, "S" .. (k - 1) .. " × S" .. k .. " × S" .. (k + 1)) | |||
end | |||
end | |||
end | |||
return expressions | |||
end | end | ||
-- multiply two rational numbers; integers are also allowed | -- multiply two rational numbers; integers are also allowed | ||
function p.mul(a, b) | function p.mul(a, b) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if type(b) == | if type(b) == "number" then | ||
b = p.new(b) | b = p.new(b) | ||
end | end | ||
Line 131: | Line 247: | ||
local c = p.copy(a) | local c = p.copy(a) | ||
for factor, power in pairs(b) do | for factor, power in pairs(b) do | ||
if type(factor) == | if type(factor) == "number" then | ||
c[factor] = c[factor] or 0 | c[factor] = c[factor] or 0 | ||
c[factor] = c[factor] + power | c[factor] = c[factor] + power | ||
Line 145: | Line 261: | ||
-- compute 1/a for a rational number a; integers are also allowed | -- compute 1/a for a rational number a; integers are also allowed | ||
function p.inv(a) | function p.inv(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 161: | Line 277: | ||
end | end | ||
-- regular case: not NaN, not infinity, not zero | -- regular case: not NaN, not infinity, not zero | ||
b = {} | local b = {} | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
b[factor] = -power | b[factor] = -power | ||
end | end | ||
Line 178: | Line 294: | ||
-- compute a^b; b must be an integer | -- compute a^b; b must be an integer | ||
function p.pow(a, b) | function p.pow(a, b) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if type(b) ~= | if type(b) ~= "number" then | ||
return nil | return nil | ||
end | end | ||
Line 206: | Line 322: | ||
end | end | ||
local c = p.new(1) | local c = p.new(1) | ||
for | for _ = 1, math.abs(b) do | ||
if b > 0 then | if b > 0 then | ||
c = p.mul(c, a) | c = p.mul(c, a) | ||
Line 217: | Line 333: | ||
-- compute a canonical representation of `a` modulo powers of `b` | -- compute a canonical representation of `a` modulo powers of `b` | ||
-- TODO: describe the exact behavior | |||
-- it seems bugged when the equave is a fraction | |||
function p.modulo_mul(a, b) | function p.modulo_mul(a, b) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if type(b) == | if type(b) == "number" then | ||
b = p.new(b) | b = p.new(b) | ||
end | end | ||
Line 227: | Line 345: | ||
return p.copy(a) | return p.copy(a) | ||
end | end | ||
local neg_power = - | local neg_power = -math.huge | ||
local pos_power = | local pos_power = math.huge | ||
for factor, power in pairs(b) do | for factor, power in pairs(b) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if (power > 0 and (a[factor] or 0) >= 0) or (power < 0 and (a[factor] or 0) <= 0) then | if (power > 0 and (a[factor] or 0) >= 0) or (power < 0 and (a[factor] or 0) <= 0) then | ||
pos_power = math.min(pos_power, | pos_power = math.min(pos_power, math.floor((a[factor] or 0) / power)) | ||
else | else | ||
neg_power = math.max(neg_power, | neg_power = math.max(neg_power, -math.ceil(math.abs(a[factor] or 0) / math.abs(power))) | ||
end | end | ||
end | end | ||
Line 254: | Line 368: | ||
-- add two rational numbers; integers are also allowed | -- add two rational numbers; integers are also allowed | ||
function p.add(a, b) | function p.add(a, b) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if type(b) == | if type(b) == "number" then | ||
b = p.new(b) | b = p.new(b) | ||
end | end | ||
-- special case: NaN | -- special case: NaN | ||
if a.nan or b.nan then | if a.nan or b.nan then | ||
Line 289: | Line 403: | ||
local gcd = { sign = 1 } | local gcd = { sign = 1 } | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if math.min(power, b[factor] or 0) > 0 then | if math.min(power, b[factor] or 0) > 0 then | ||
gcd[factor] = math.min(power, b[factor]) | gcd[factor] = math.min(power, b[factor]) | ||
Line 298: | Line 412: | ||
end | end | ||
end | end | ||
a = p.div(a, gcd) | |||
b = p.div(b, gcd) | |||
n_a, m_a = p.as_pair(a) | local n_a, m_a = p.as_pair(a) | ||
n_b, m_b = p.as_pair(b) | local n_b, m_b = p.as_pair(b) | ||
n = n_a * m_b + n_b * m_a | local n = n_a * m_b + n_b * m_a | ||
m = m_a * m_b | local m = m_a * m_b | ||
return p.mul(p.new(n, m), gcd) | return p.mul(p.new(n, m), gcd) | ||
end | end | ||
Line 367: | Line 481: | ||
-- determine whether a rational number is equal to another; integers are also allowed | -- determine whether a rational number is equal to another; integers are also allowed | ||
function p.eq(a, b) | function p.eq(a, b) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if type(b) == | if type(b) == "number" then | ||
b = p.new(b) | b = p.new(b) | ||
end | end | ||
Line 403: | Line 517: | ||
-- determine whether a rational number is integer | -- determine whether a rational number is integer | ||
function p.is_int(a) | function p.is_int(a) | ||
if type(a) == | if type(a) == "number" then | ||
return true | return true | ||
end | end | ||
Line 413: | Line 527: | ||
end | end | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if power < 0 then | if power < 0 then | ||
return false | return false | ||
Line 422: | Line 536: | ||
end | end | ||
-- check if an integer is | -- determine whether a rational number lies within [1; equave) | ||
function p. | function p.is_reduced(a, equave, large) | ||
if type(a) == | equave = equave or 2 | ||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero or a.sign < 0 then | |||
return false | |||
end | |||
if large then | |||
-- an approximation | |||
local cents = p.cents(a) | |||
local cents_max = p.cents(equave) | |||
return cents >= 0 and cents < cents_max | |||
else | |||
return p.geq(a, 1) and p.lt(a, equave) | |||
end | |||
end | |||
-- determine whether a rational number represents a harmonic. | |||
-- reduced: check for reduced harmonic instead. | |||
function p.is_harmonic(a, reduced, large) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero or a.sign < 0 then | |||
return false | |||
end | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
if factor == 2 and reduced then | |||
-- pass (ignore factors of 2 for reduced harmonic check) | |||
elseif power < 0 then | |||
return false | |||
end | |||
end | |||
end | |||
if reduced then | |||
return p.is_reduced(a, 2, large) | |||
end | |||
return true | |||
end | |||
-- determine whether a rational number represents a subharmonic. | |||
-- reduced: check for reduced subharmonic instead. | |||
function p.is_subharmonic(a, reduced, large) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero or a.sign < 0 then | |||
return false | |||
end | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
if factor == 2 and reduced then | |||
-- pass (ignore factors of 2 for reduced subharmonic check) | |||
elseif power > 0 then | |||
return false | |||
end | |||
end | |||
end | |||
if reduced then | |||
return p.is_reduced(a, 2, large) | |||
end | |||
return true | |||
end | |||
-- determine whether a rational number is an integer power of another rational number | |||
function p.is_power(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero then | |||
return false | |||
end | |||
if p.eq(a, 1) or p.eq(a, -1) then | |||
return false | |||
end | |||
local total_power = nil | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
if total_power then | |||
total_power = utils._gcd(total_power, math.abs(power)) | |||
else | |||
total_power = math.abs(power) | |||
end | |||
end | |||
end | |||
return total_power > 1 | |||
end | |||
-- determine whether a rational number is superparticular | |||
function p.is_superparticular(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 430: | Line 636: | ||
return false | return false | ||
end | end | ||
-- negative numbers are not highly composite | local n, m = p.as_pair(a) | ||
if a.sign == -1 then | return n - m == 1 | ||
end | |||
-- determine whether a rational number is a square superparticular | |||
function p.is_square_superparticular(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero or a.sign < 0 then | |||
return false | |||
end | |||
-- check the numerator | |||
local k = { sign = 1 } | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
if power > 0 and power % 2 ~= 0 then | |||
return false | |||
elseif power > 0 then | |||
k[factor] = math.floor(power / 2 + 0.5) | |||
end | |||
end | |||
end | |||
-- check the denominator | |||
local den = p.mul(p.add(k, 1), p.sub(k, 1)) | |||
return p.eq(a, p.div(p.pow(k, 2), den)) | |||
end | |||
-- check if an integer is prime | |||
function p.is_prime(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
-- nan, inf, zero, and negative numbers are not prime | |||
if a.nan or a.inf or a.zero or a.sign < 0 then | |||
return false | |||
end | |||
local flag = false -- flag for having exactly one prime factor | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" and power then | |||
if flag or power ~= 1 then | |||
return false | |||
else | |||
flag = true | |||
end | |||
end | |||
end | |||
return flag | |||
end | |||
-- check if an integer is highly composite | |||
function p.is_highly_composite(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
-- nan, inf, zero, and negative numbers are not highly composite | |||
if a.nan or a.inf or a.zero or a.sign == -1 then | |||
return false | return false | ||
end | end | ||
-- non-integers are not highly composite | -- non-integers are not highly composite | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if power < 0 then | if power < 0 then | ||
return false | return false | ||
Line 442: | Line 707: | ||
end | end | ||
end | end | ||
local last_power = 1/0 | |||
local last_power = 1 / 0 | |||
local max_prime = p.max_prime(a) | local max_prime = p.max_prime(a) | ||
for i = 2, max_prime do | for i = 2, max_prime do | ||
if | if utils.is_prime(i) then | ||
-- factors must be the first N primes | -- factors must be the first N primes | ||
if a[i] == nil then | if a[i] == nil then | ||
Line 461: | Line 727: | ||
return p.eq(a, 1) or p.eq(a, 4) or p.eq(a, 36) | return p.eq(a, 1) or p.eq(a, 4) or p.eq(a, 36) | ||
end | end | ||
-- now we actually check whether it is highly composite | -- now we actually check whether it is highly composite | ||
local n, | local n, _ = p.as_pair(a) | ||
-- precision is very important here | -- precision is very important here | ||
local log2_n = 0 | local log2_n = 0 | ||
Line 472: | Line 738: | ||
t = t * 2 | t = t * 2 | ||
end | end | ||
local divisors = p.divisors(a) | local divisors = p.divisors(a) | ||
local diagram_size = log2_n | local diagram_size = log2_n | ||
local diagram = {log2_n} | local diagram = { log2_n } | ||
local primes = {2} | local primes = { 2 } | ||
function eval_diagram(d) | local function eval_diagram(d) | ||
while #d > #primes do | while #d > #primes do | ||
local i = primes[#primes] + 1 | local i = primes[#primes] + 1 | ||
while not | while not utils.is_prime(i) do | ||
i = i + 1 | i = i + 1 | ||
end | end | ||
Line 488: | Line 754: | ||
local m = 1 | local m = 1 | ||
for i = 1, #d do | for i = 1, #d do | ||
for | for _ = 1, d[i] do | ||
m = m * primes[i] | m = m * primes[i] | ||
end | end | ||
Line 494: | Line 760: | ||
return m | return m | ||
end | end | ||
-- iterate factorisations of some composite integers <n | -- iterate factorisations of some composite integers <n | ||
while diagram do | while diagram do | ||
Line 519: | Line 785: | ||
return false | return false | ||
end | end | ||
diagram = | diagram = utils.next_young_diagram(diagram) | ||
end | end | ||
return true | return true | ||
Line 526: | Line 792: | ||
-- check if an integer is superabundant | -- check if an integer is superabundant | ||
function p.is_superabundant(a) | function p.is_superabundant(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 538: | Line 804: | ||
-- non-integers are not superabundant | -- non-integers are not superabundant | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if power < 0 then | if power < 0 then | ||
return false | return false | ||
Line 544: | Line 810: | ||
end | end | ||
end | end | ||
local last_power = 1/0 | local last_power = 1 / 0 | ||
local max_prime = p.max_prime(a) | local max_prime = p.max_prime(a) | ||
local divisor_sum = p.new(1) | local divisor_sum = p.new(1) | ||
for i = 2, max_prime do | for i = 2, max_prime do | ||
if | if utils.is_prime(i) then | ||
-- factors must be the first N primes | -- factors must be the first N primes | ||
if a[i] == nil then | if a[i] == nil then | ||
Line 558: | Line 824: | ||
end | end | ||
last_power = a[i] | last_power = a[i] | ||
divisor_sum = p.mul( | divisor_sum = p.mul(divisor_sum, p.div(p.sub(p.pow(i, a[i] + 1), 1), i - 1)) | ||
end | end | ||
end | end | ||
Line 571: | Line 831: | ||
return p.eq(a, 1) or p.eq(a, 4) or p.eq(a, 36) | return p.eq(a, 1) or p.eq(a, 4) or p.eq(a, 36) | ||
end | end | ||
-- now we actually check whether it is superabundant | -- now we actually check whether it is superabundant | ||
local n, | local n, _ = p.as_pair(a) | ||
-- precision is very important here | -- precision is very important here | ||
local log2_n = 0 | local log2_n = 0 | ||
Line 582: | Line 842: | ||
t = t * 2 | t = t * 2 | ||
end | end | ||
local SA_ratio = p.div(divisor_sum, a) | local SA_ratio = p.div(divisor_sum, a) | ||
local diagram_size = log2_n | local diagram_size = log2_n | ||
local diagram = {log2_n} | local diagram = { log2_n } | ||
local primes = {2} | local primes = { 2 } | ||
function eval_diagram(d) | local function eval_diagram(d) | ||
while #d > #primes do | while #d > #primes do | ||
local i = primes[#primes] + 1 | local i = primes[#primes] + 1 | ||
while not | while not utils.is_prime(i) do | ||
i = i + 1 | i = i + 1 | ||
end | end | ||
Line 598: | Line 858: | ||
local m = 1 | local m = 1 | ||
for i = 1, #d do | for i = 1, #d do | ||
for | for _ = 1, d[i] do | ||
m = m * primes[i] | m = m * primes[i] | ||
end | end | ||
Line 604: | Line 864: | ||
return m | return m | ||
end | end | ||
-- iterate factorisations of some composite integers <n | -- iterate factorisations of some composite integers <n | ||
while diagram do | while diagram do | ||
Line 624: | Line 884: | ||
local diagram_divisor_sum = 1 | local diagram_divisor_sum = 1 | ||
for i = 1, #diagram do | for i = 1, #diagram do | ||
diagram_divisor_sum = p.mul( | diagram_divisor_sum = | ||
p.mul(diagram_divisor_sum, p.div(p.sub(p.pow(primes[i], diagram[i] + 1), 1), primes[i] - 1)) | |||
end | end | ||
local diagram_SA_ratio = p.div(diagram_divisor_sum, eval_diagram(diagram)) | local diagram_SA_ratio = p.div(diagram_divisor_sum, eval_diagram(diagram)) | ||
Line 636: | Line 891: | ||
return false | return false | ||
end | end | ||
diagram = | diagram = utils.next_young_diagram(diagram) | ||
end | end | ||
return true | return true | ||
end | end | ||
-- find max prime in | -- Check if ratio is within an int limit; that is, neither its numerator nor | ||
-- denominator exceed that limit. | |||
function p.is_within_int_limit(a, lim) | |||
return p.int_limit(a) <= lim | |||
end | |||
-- Find integer limit of a ratio | |||
-- For a ratio p/q, this is simply max(p, q) | |||
function p.int_limit(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero then | |||
return nil | |||
end | |||
local a_copy = p.copy(a) | |||
local num, den = p.as_pair(a_copy) | |||
return math.max(num, den) | |||
end | |||
-- Find odd limit of a ratio | |||
-- For a ratio p/q, this is simply max(p, q) where powers of 2 are ignored | |||
function p.odd_limit(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero then | |||
return nil | |||
end | |||
local a_copy = p.copy(a) | |||
if a_copy[2] ~= nil then | |||
a_copy[2] = 0 | |||
end | |||
local num, den = p.as_pair(a_copy) | |||
return math.max(num, den) | |||
end | |||
-- find max prime involved in the factorisation | |||
-- (a.k.a. prime limit or harmonic class) of a rational number | |||
function p.max_prime(a) | function p.max_prime(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 650: | Line 943: | ||
end | end | ||
local max_factor = 0 | local max_factor = 0 | ||
for factor, | for factor, _ in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if factor > max_factor then | if factor > max_factor then | ||
max_factor = factor | max_factor = factor | ||
Line 658: | Line 951: | ||
end | end | ||
return max_factor | return max_factor | ||
end | |||
-- convert a rational number to its size in octaves | |||
-- equal to log2 of the rational number | |||
function p.log(a, base) | |||
base = base or 2 | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.inf and a.sign > 0 then | |||
return 1 / 0 | |||
end | |||
if a.nan or a.inf then | |||
return nil | |||
end | |||
if a.zero then | |||
return -1 / 0 | |||
end | |||
if a.sign < 0 then | |||
return nil | |||
end | |||
local logarithm = 0 | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
logarithm = logarithm + power * utils._log(factor, base) | |||
end | |||
end | |||
return logarithm | |||
end | |||
-- convert a rational number to its size in cents | |||
function p.cents(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.sign < 0 then | |||
return nil | |||
end | |||
if a.inf and a.sign > 0 then | |||
return 1 / 0 | |||
end | |||
if a.zero then | |||
return -1 / 0 | |||
end | |||
local c = 0 | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
c = c + power * utils.log2(factor) | |||
end | |||
end | |||
return c * 1200 | |||
end | |||
-- convert a rational number (interpreted as an interval) into Hz | |||
function p.hz(a, base) | |||
base = base or 440 | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.sign < 0 then | |||
return nil | |||
end | |||
if a.zero then | |||
return 0 | |||
end | |||
local log_hz = math.log(base) | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
log_hz = log_hz + power * math.log(factor) | |||
end | |||
end | |||
return math.exp(log_hz) | |||
end | end | ||
-- FJS: x = a * 2^n : x >= 1, x < 2 | -- FJS: x = a * 2^n : x >= 1, x < 2 | ||
local function red(a) | local function red(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 669: | Line 1,035: | ||
end | end | ||
local b = p.copy(a) | local b = p.copy(a) | ||
-- start with an approximation | |||
local log2 = p.log(b) | |||
b = p.div(b, p.pow(2, math.floor(log2))) | |||
while p.lt(b, 1) do | while p.lt(b, 1) do | ||
b = p.mul(b, 2) | b = p.mul(b, 2) | ||
Line 714: | Line 1,085: | ||
end | end | ||
-- FJS representation of a rational number | |||
-- might be a bit incorrect | |||
-- TODO: confirm correctness | |||
function p.as_FJS(a) | function p.as_FJS(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 725: | Line 1,099: | ||
local utonal = {} | local utonal = {} | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" and factor > 3 then | ||
local comma = formal_comma(factor) | local comma = formal_comma(factor) | ||
b = p.div(b, p.pow(comma, power)) | b = p.div(b, p.pow(comma, power)) | ||
if power > 0 then | if power > 0 then | ||
for | for _ = 1, power do | ||
table.insert(otonal, factor) | table.insert(otonal, factor) | ||
end | end | ||
else | else | ||
for | for _ = 1, -power do | ||
table.insert(utonal, factor) | table.insert(utonal, factor) | ||
end | end | ||
Line 739: | Line 1,113: | ||
end | end | ||
end | end | ||
table.sort(otonal) | |||
table.sort(utonal) | |||
local fifths = b[3] or 0 | local fifths = b[3] or 0 | ||
local | local o = math.floor((fifths * 2 + 3) / 7) | ||
local num = fifths * 11 + (b[2] or 0) * 7 | |||
if num >= 0 then | |||
num = num + 1 | |||
else | |||
num = num - 1 | |||
o = -o | |||
end | |||
local num_mod = (num - utils.signum(num)) % 7 | |||
local letter = "" | |||
if (num_mod == 0 or num_mod == 3 or num_mod == 4) and o == 0 then | |||
letter = "P" | |||
elseif o == 1 then | |||
letter = "M" | |||
elseif o == -1 then | |||
letter = "m" | |||
else | |||
if o >= 0 then | |||
o = o - 1 | |||
else | |||
o = o + 1 | |||
end | |||
if o > 0 then | |||
while o > 0 do | |||
letter = letter .. "A" | |||
o = o - 2 | |||
end | |||
else | |||
while o < 0 do | |||
letter = letter .. "d" | |||
o = o + 2 | |||
end | |||
end | |||
if #letter >= 5 then | |||
letter = #letter .. letter:sub(1, 1) | |||
end | |||
end | |||
local FJS = letter .. num | |||
if #otonal > 0 then | |||
FJS = FJS .. "^{" .. table.concat(otonal, ",") .. "}" | |||
end | |||
if #utonal > 0 then | |||
FJS = FJS .. "_{" .. table.concat(utonal, ",") .. "}" | |||
end | |||
return FJS | |||
end | end | ||
-- determine log2 product complexity | -- determine log2 product complexity | ||
function p.tenney_height(a) | function p.tenney_height(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 755: | Line 1,175: | ||
return nil | return nil | ||
end | end | ||
local | local h = 0 | ||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
h = h + math.abs(power) * utils.log2(factor) | |||
end | |||
end | |||
return h | |||
end | end | ||
-- determine max complexity | -- determine log2 max complexity | ||
function p.weil_height(a) | function p.weil_height(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 767: | Line 1,192: | ||
return nil | return nil | ||
end | end | ||
local | local h1 = p.tenney_height(a) | ||
return math. | local h2 = 0 | ||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
h2 = h2 + power * utils.log2(factor) | |||
end | |||
end | |||
h2 = math.abs(h2) | |||
return h1 + h2 | |||
end | |||
-- determine sopfr complexity | |||
function p.wilson_height(a) | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | |||
if a.nan or a.inf or a.zero then | |||
return nil | |||
end | |||
local h = 0 | |||
for factor, power in pairs(a) do | |||
if type(factor) == "number" then | |||
h = h + math.abs(power) * factor | |||
end | |||
end | |||
return h | |||
end | end | ||
-- determine product complexity | -- determine product complexity | ||
function p.benedetti_height(a) | function p.benedetti_height(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 780: | Line 1,229: | ||
end | end | ||
local n, m = p.as_pair(a) | local n, m = p.as_pair(a) | ||
return n * m | if (math.log(n) + math.log(m)) / math.log(10) <= 15 then | ||
return n * m | |||
else | |||
-- it is going to be an overflow | |||
return nil | |||
end | |||
end | end | ||
-- determine the number of rational divisors | -- determine the number of rational divisors | ||
function p.divisors(a) | function p.divisors(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 793: | Line 1,247: | ||
local d = 1 | local d = 1 | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
d = d * (math.abs(power) + 1) | d = d * (math.abs(power) + 1) | ||
end | end | ||
Line 802: | Line 1,256: | ||
-- determine whether the rational number is +- p/q, where p, q are primes OR 1 | -- determine whether the rational number is +- p/q, where p, q are primes OR 1 | ||
function p.is_prime_ratio(a) | function p.is_prime_ratio(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 811: | Line 1,265: | ||
local m_factors = 0 | local m_factors = 0 | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if power > 0 then | if power > 0 then | ||
n_factors = n_factors + 1 | n_factors = n_factors + 1 | ||
Line 820: | Line 1,274: | ||
end | end | ||
return n_factors <= 1 and m_factors <= 1 | return n_factors <= 1 and m_factors <= 1 | ||
end | end | ||
-- return prime factorisation of a rational number | -- return prime factorisation of a rational number | ||
function p.factorisation(a) | function p.factorisation(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if a.nan or a.inf or a.zero or p.eq(a, 1) or p.eq(a, -1) then | if a.nan or a.inf or a.zero or p.eq(a, 1) or p.eq(a, -1) then | ||
return | return "n/a" | ||
end | end | ||
local s = | local s = "" | ||
if a.sign < 0 then | if a.sign < 0 then | ||
s = s .. | s = s .. "-" | ||
end | end | ||
local factors = {} | local factors = {} | ||
for factor, | for factor, _ in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
table.insert(factors, factor) | table.insert(factors, factor) | ||
end | end | ||
Line 862: | Line 1,296: | ||
table.sort(factors) | table.sort(factors) | ||
for i, factor in ipairs(factors) do | for i, factor in ipairs(factors) do | ||
if i > 1 then s = s .. | if i > 1 then | ||
s = s .. " × " | |||
end | |||
s = s .. factor | s = s .. factor | ||
if a[factor] ~= 1 then | if a[factor] ~= 1 then | ||
s = s .. | s = s .. "<sup>" .. a[factor] .. "</sup>" | ||
end | end | ||
end | end | ||
Line 873: | Line 1,309: | ||
-- return the subgroup generated by primes from a rational number's prime factorisation | -- return the subgroup generated by primes from a rational number's prime factorisation | ||
function p.subgroup(a) | function p.subgroup(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if a.nan or a.inf or a.zero | if p.eq(a, 1) then | ||
return | return "1" | ||
end | |||
if a.nan or a.inf or a.zero or p.eq(a, -1) then | |||
return "n/a" | |||
end | end | ||
local s = | local s = "" | ||
local factors = {} | local factors = {} | ||
for factor, | for factor, _ in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
table.insert(factors, factor) | table.insert(factors, factor) | ||
end | end | ||
Line 888: | Line 1,327: | ||
table.sort(factors) | table.sort(factors) | ||
for i, factor in ipairs(factors) do | for i, factor in ipairs(factors) do | ||
if i > 1 then s = s .. | if i > 1 then | ||
s = s .. "." | |||
end | |||
s = s .. factor | s = s .. factor | ||
end | |||
if a.sign < 0 then | |||
s = "-1." .. s | |||
end | end | ||
return s | return s | ||
end | end | ||
-- return | -- unpack rational as two return values (n, m) | ||
function p.as_pair(a) | function p.as_pair(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 915: | Line 1,359: | ||
local m = 1 | local m = 1 | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if power > 0 then | if power > 0 then | ||
n = n * (factor ^ power) | n = n * (factor ^ power) | ||
else | else | ||
m = m * (factor ^ | m = m * (factor ^ -power) | ||
end | end | ||
end | end | ||
Line 929: | Line 1,373: | ||
-- return a string ratio representation | -- return a string ratio representation | ||
function p.as_ratio(a, separator) | function p.as_ratio(a, separator) | ||
separator = separator or | separator = separator or "/" | ||
local n, m = p.as_pair(a) | local n, m = p.as_pair(a) | ||
return n | return ("%d%s%d"):format(n, separator, m) | ||
end | end | ||
-- return the {n, m} pair as a Lua table | -- return the {n, m} pair as a Lua table | ||
function p.as_table(a) | function p.as_table(a) | ||
return {p.as_pair(a)} | return { p.as_pair(a) } | ||
end | end | ||
Line 945: | Line 1,389: | ||
end | end | ||
-- return a rational number in ket notation | -- return a rational number in subgroup ket notation | ||
function p.as_subgroup_ket(a, frame) | |||
function p. | if type(a) == "number" then | ||
if type(a) == | |||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if a.nan or a.inf or a.zero or a.sign < 0 then | |||
if a.nan then | return "n/a" | ||
return | |||
end | end | ||
local factors = {} | |||
for factor, _ in pairs(a) do | |||
if type(factor) == "number" then | |||
table.insert(factors, factor) | |||
end | end | ||
end | end | ||
-- | table.sort(factors) | ||
if a | local subgroup = "1" | ||
if not p.eq(a, 1) then | |||
subgroup = table.concat(factors, ".") | |||
end | |||
local powers = {} | |||
for _, factor in ipairs(factors) do | |||
table.insert(powers, a[factor]) | |||
end | |||
local template_arg = "0" | |||
if not p.eq(a, 1) then | |||
template_arg = table.concat(powers, " ") | |||
end | |||
return subgroup .. " " .. frame:expandTemplate({ | |||
title = "Monzo", | |||
args = { template_arg }, | |||
}) | |||
end | |||
-- return a string of a rational number in monzo notation | |||
-- calling Template: Monzo | |||
function p.as_ket(a, frame, skip_many_zeros, only_numbers) | |||
if skip_many_zeros == nil then | |||
skip_many_zeros = true | |||
end | |||
only_numbers = only_numbers or false | |||
if type(a) == "number" then | |||
a = p.new(a) | |||
end | end | ||
-- special cases | |||
if a.sign < 0 then | if a.nan or a.inf or a.zero or a.sign < 0 then | ||
return "n/a" | |||
end | end | ||
-- regular case: positive finite ratio | |||
local s = "" | |||
-- preparing the argument | -- preparing the argument | ||
local max_prime = p.max_prime(a) | local max_prime = p.max_prime(a) | ||
local template_arg = | local template_arg = "" | ||
local template_arg_without_trailing_zeros = "" | |||
local zeros_n = 0 | |||
for i = 2, max_prime do | for i = 2, max_prime do | ||
if | if utils.is_prime(i) then | ||
if i > 2 then template_arg = template_arg .. | if i > 2 then | ||
template_arg = template_arg .. " " | |||
end | |||
template_arg = template_arg .. (a[i] or 0) | template_arg = template_arg .. (a[i] or 0) | ||
if (a[i] or 0) ~= 0 then | |||
if skip_many_zeros and zeros_n >= 4 then | |||
template_arg = template_arg_without_trailing_zeros | |||
if #template_arg > 0 then | |||
template_arg = template_arg .. " " | |||
end | |||
template_arg = template_arg .. "0<sup>" .. zeros_n .. "</sup> " .. (a[i] or 0) | |||
end | |||
zeros_n = 0 | |||
template_arg_without_trailing_zeros = template_arg | |||
else | |||
zeros_n = zeros_n + 1 | |||
end | |||
end | end | ||
end | end | ||
s = s .. frame:expandTemplate{ | if #template_arg == 0 then | ||
template_arg = "0" | |||
end | |||
if only_numbers then | |||
s = s .. template_arg | |||
else | |||
s = s .. frame:expandTemplate({ | |||
title = "Monzo", | |||
args = { template_arg }, | |||
}) | |||
end | |||
return s | return s | ||
end | end | ||
Line 992: | Line 1,487: | ||
-- returns nil on failure | -- returns nil on failure | ||
function p.parse(unparsed) | function p.parse(unparsed) | ||
if type(unparsed) ~= | if type(unparsed) ~= "string" then | ||
return nil | return nil | ||
end | end | ||
-- removing whitespaces | |||
unparsed = unparsed:gsub("%s", "") | |||
-- removing <br> and <br/> tags | |||
unparsed = unparsed:gsub("<br/?>", "") | |||
-- length limit: very long strings are not converted into Lua numbers correctly | |||
local max_length = 15 | |||
-- rational form | -- rational form | ||
local sign, n, | local sign, n, _, m = unparsed:match("^%s*(%-?)%s*(%d+)%s*(/%s*(%d+))%s*$") | ||
if n == nil then | if n == nil then | ||
-- integer form | -- integer form | ||
sign, n = unparsed:match( | sign, n = unparsed:match("^%s*(%-?)%s*(%d+)%s*$") | ||
if n == nil then | if n == nil then | ||
-- parsing failure | -- parsing failure | ||
Line 1,005: | Line 1,508: | ||
else | else | ||
m = 1 | m = 1 | ||
if #n > max_length then | |||
return nil | |||
end | |||
n = tonumber(n) | n = tonumber(n) | ||
if #sign > 0 then | if #sign > 0 then | ||
Line 1,011: | Line 1,517: | ||
end | end | ||
else | else | ||
if #n > max_length then | |||
return nil | |||
end | |||
n = tonumber(n) | n = tonumber(n) | ||
if #m > max_length then | |||
return nil | |||
end | |||
m = tonumber(m) | m = tonumber(m) | ||
if #sign > 0 then | if #sign > 0 then | ||
Line 1,022: | Line 1,534: | ||
-- a version of as_ket() that can be {{#invoke:}}d | -- a version of as_ket() that can be {{#invoke:}}d | ||
function p.ket(frame) | function p.ket(frame) | ||
local unparsed = frame.args[1] or | local unparsed = frame.args[1] or "1" | ||
local result = "" | |||
local a = p.parse(unparsed) | local a = p.parse(unparsed) | ||
if a == nil then | if a == nil then | ||
result = '{{error|Invalid rational number: ' .. unparsed .. ".}}" | |||
else | |||
result = p.as_ket(a, frame) | |||
end | end | ||
return | |||
return frame:preprocess(result) | |||
end | end | ||
p.monzo = p.ket | p.monzo = p.ket | ||
return p | return p |