Module:Rational: Difference between revisions
m is_square_superparticular() implemented |
ArrowHead294 (talk | contribs) mNo edit summary |
||
(46 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 51: | Line 58: | ||
local factor = 1 | local factor = 1 | ||
local a = { sign = 1 } | local a = { sign = 1 } | ||
for i in s:gmatch( | for i in s:gmatch("%S+") do | ||
local power = tonumber(i) | local power = tonumber(i) | ||
if power == nil then return nil end | if power == nil then | ||
return nil | |||
end | |||
-- find the next prime | -- find the next prime | ||
factor = factor + 1 | factor = factor + 1 | ||
while not | while not utils.is_prime(factor) do | ||
factor = factor + 1 | factor = factor + 1 | ||
end | end | ||
if power ~= 0 then | if power ~= 0 then | ||
a[factor] = power | a[factor] = power | ||
Line 78: | 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 92: | 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 118: | 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 152: | 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 166: | 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 182: | 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 199: | 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 227: | 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 238: | 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 248: | 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 275: | 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 310: | 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 319: | 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 388: | 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 424: | 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 434: | 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 | ||
end | end | ||
end | end | ||
end | |||
return true | |||
end | |||
-- determine whether a rational number lies within [1; equave) | |||
function p.is_reduced(a, equave, large) | |||
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 | end | ||
return true | return true | ||
Line 445: | Line 605: | ||
-- determine whether a rational number is an integer power of another rational number | -- determine whether a rational number is an integer power of another rational number | ||
function p.is_power(a) | function p.is_power(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 454: | Line 614: | ||
return false | return false | ||
end | end | ||
local total_power = nil | local total_power = nil | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
if total_power then | if total_power then | ||
total_power = | total_power = utils._gcd(total_power, math.abs(power)) | ||
else | else | ||
total_power = math.abs(power) | total_power = math.abs(power) | ||
Line 479: | Line 630: | ||
-- determine whether a rational number is superparticular | -- determine whether a rational number is superparticular | ||
function p.is_superparticular(a) | function p.is_superparticular(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
Line 491: | Line 642: | ||
-- determine whether a rational number is a square superparticular | -- determine whether a rational number is a square superparticular | ||
function p.is_square_superparticular(a) | function p.is_square_superparticular(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 then | if a.nan or a.inf or a.zero or a.sign < 0 then | ||
return false | return false | ||
end | end | ||
-- check the numerator | -- check the numerator | ||
local k = { 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 power > 0 and power % 2 ~= 0 then | if power > 0 and power % 2 ~= 0 then | ||
return false | 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 | end | ||
end | end | ||
return | return flag | ||
end | end | ||
-- check if an integer is highly composite | -- check if an integer is highly composite | ||
function p.is_highly_composite(a) | function p.is_highly_composite(a) | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | 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 | |||
-- negative numbers are not highly composite | |||
if 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 528: | 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 547: | 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 558: | 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 574: | 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 580: | 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 605: | Line 785: | ||
return false | return false | ||
end | end | ||
diagram = | diagram = utils.next_young_diagram(diagram) | ||
end | end | ||
return true | return true | ||
Line 612: | 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 624: | 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 630: | 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 644: | 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 657: | 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 668: | 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 684: | 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 690: | 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 710: | 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 722: | 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 736: | 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 746: | Line 953: | ||
end | end | ||
-- | -- convert a rational number to its size in octaves | ||
-- equal to log2 of the rational number | |||
function p.log(a, base) | function p.log(a, base) | ||
base = base or 2 | base = base or 2 | ||
if type(a) == | if type(a) == "number" then | ||
a = p.new(a) | a = p.new(a) | ||
end | end | ||
if a.inf and a.sign > 0 then | if a.inf and a.sign > 0 then | ||
return 1/0 | return 1 / 0 | ||
end | end | ||
if a.nan or a.inf then | if a.nan or a.inf then | ||
Line 759: | Line 967: | ||
end | end | ||
if a.zero then | if a.zero then | ||
return -1/0 | return -1 / 0 | ||
end | end | ||
if a.sign < 0 then | if a.sign < 0 then | ||
Line 766: | Line 974: | ||
local logarithm = 0 | local logarithm = 0 | ||
for factor, power in pairs(a) do | for factor, power in pairs(a) do | ||
if type(factor) == | if type(factor) == "number" then | ||
logarithm = logarithm + power * | logarithm = logarithm + power * utils._log(factor, base) | ||
end | end | ||
end | end | ||
return logarithm | 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 782: | Line 1,035: | ||
end | end | ||
local b = p.copy(a) | local b = p.copy(a) | ||
-- start with an approximation | -- start with an approximation | ||
local log2 = p.log(b) | local log2 = p.log(b) | ||
b = p.div(b, p.pow(2, math.floor(log2))) | 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 834: | Line 1,087: | ||
-- FJS representation of a rational number | -- FJS representation of a rational number | ||
-- might be a bit incorrect | -- 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 845: | 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 861: | Line 1,115: | ||
table.sort(otonal) | table.sort(otonal) | ||
table.sort(utonal) | table.sort(utonal) | ||
local fifths = b[3] or 0 | local fifths = b[3] or 0 | ||
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 | else | ||
num = num - 1 | |||
o = -o | |||
end | 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 = letter .. "A" | ||
elseif | o = o - 2 | ||
letter = | end | ||
else | |||
while o < 0 do | |||
while | letter = letter .. "d" | ||
o = o + 2 | |||
end | |||
end | end | ||
if #letter >= 5 then | |||
letter = | letter = #letter .. letter:sub(1, 1) | ||
letter = letter .. | |||
end | end | ||
end | end | ||
local FJS = | local FJS = letter .. num | ||
if #otonal > 0 then | if #otonal > 0 then | ||
FJS = FJS .. | FJS = FJS .. "^{" .. table.concat(otonal, ",") .. "}" | ||
end | end | ||
if #utonal > 0 then | if #utonal > 0 then | ||
FJS = FJS .. | FJS = FJS .. "_{" .. table.concat(utonal, ",") .. "}" | ||
end | end | ||
return FJS | return FJS | ||
Line 914: | Line 1,169: | ||
-- 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 920: | 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 932: | 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 945: | 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 958: | 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 967: | 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 976: | 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 985: | 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 1,027: | 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 1,038: | 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 p.eq(a, 1) then | if p.eq(a, 1) then | ||
return | return "1" | ||
end | end | ||
if a.nan or a.inf or a.zero or p.eq(a, -1) then | if a.nan or a.inf or a.zero or p.eq(a, -1) then | ||
return | 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 1,056: | 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 | end | ||
if a.sign < 0 then | if a.sign < 0 then | ||
s = | 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 1,086: | 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 1,100: | 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 1,116: | 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 template_arg_without_trailing_zeros = "" | ||
local zeros_n = 0 | 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 | if i > 2 then | ||
template_arg = template_arg .. | template_arg = template_arg .. " " | ||
end | 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 (a[i] or 0) ~= 0 then | ||
if zeros_n >= 4 then | if skip_many_zeros and zeros_n >= 4 then | ||
template_arg = template_arg_without_trailing_zeros | template_arg = template_arg_without_trailing_zeros | ||
if #template_arg > 0 then | if #template_arg > 0 then | ||
template_arg = template_arg .. | template_arg = template_arg .. " " | ||
end | end | ||
template_arg = template_arg .. | template_arg = template_arg .. "0<sup>" .. zeros_n .. "</sup> " .. (a[i] or 0) | ||
end | end | ||
zeros_n = 0 | zeros_n = 0 | ||
Line 1,171: | Line 1,470: | ||
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 1,181: | 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 | -- removing whitespaces | ||
unparsed = unparsed:gsub( | unparsed = unparsed:gsub("%s", "") | ||
-- removing <br> and <br/> tags | -- removing <br> and <br/> tags | ||
unparsed = unparsed:gsub( | 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,198: | 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,204: | 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,215: | 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 |