Module:Primes in edo fs

From Xenharmonic Wiki
Jump to navigation Jump to search

Documentation transcluded from /doc
Note: Do not invoke this module directly; use the corresponding template instead: Template:Primes in edo fs.
Icon-Todo.png Todo: Documentation

local p = {}

-- greatest common divisor of a and b
-- source: https://rosettacode.org/wiki/Greatest_common_divisor#Lua
local function gcd(a, b)
    while b ~= 0 do 
        a, b = b, a % b
    end
    return math.abs(a)
end

local function calc_step_span(edo, val)
    local multiplicity = gcd(edo, val)
    if multiplicity == 1 then
        test = 0
        for i=0, edo-1 do
            if test == 1 then
                return i, 1
            end
            test = test + val
            test = test % edo
        end
    end
    return nil, multiplicity
end

-- round number to decimals. halfway up for positive numbers
local function round(number, decimals)
  local scale = 10^(decimals or 0)
  local nt = math.floor(number * scale + 0.5)
  return nt / scale
end

local function log2(v)
  return math.log(v) / math.log(2)
end

local function fmt_sign(s)
  if type(s) == "number" then
    s = tostring(s)
  end
  return s:gsub("-", "−")
end

--[[ 
primes: list of primes
prec: number of decimals (for abs errors)
fse_arg: should fifthspans be included as well
]]
local function edoprox(edo, primes, title, prec, fse_arg)
  local f = 1/edo
  local tpri = {"! colspan=\"2\" | Prime number"}
  local tabs = {"! rowspan=\"2\" | Error\n! absolute ([[cent|¢]])"}
  local trel = {"! [[Relative error|relative]] (%)"}
  local tdeg = {"! rowspan=\"2\" | Mapping\n! [[patent val]] ''v''"}
  local tdegor = {"! ''v'' ([[octave-reduced|mod "..edo.."]])"}
  local fspans = {"! colspan=\"2\" | [[Fifthspan]]"}
  local fmt_abs = string.format(" %%+.%df", prec)
  local fmt_rel = " %+.0f"
  local fs_enable = fse_arg or false
  local function cat_row(tab, row)
    tab = tab + row
  end
  for _, p in pairs(primes) do
    s = log2(p)
    v = s*edo
    ev = round(v)
    evo = ev % edo
    table.insert(tpri, " " .. p)
    table.insert(tabs, ""..fmt_sign(string.format(fmt_abs, 1200 * (ev - v ) / edo)))
    table.insert(trel, ""..fmt_sign(string.format(fmt_rel, 100 * (ev - v))))
    table.insert(tdeg, " " .. ev)
    table.insert(tdegor, " " .. evo)
  end
  
  if fs_enable then
    local vf = round(log2(3)*edo) % edo
    local sss, multi = calc_step_span(edo, vf)
    if sss ~= nil then
      for _, p in pairs(primes) do
        s = log2(p)
        v = s*edo
        v = round(v) % edo
        local sp = tonumber(v)*sss % edo
        local rsp = sp
        if (rsp > edo/2) then
           rsp = rsp - edo
        end
        if rsp ~= 0 then
          rsp = string.format("%+.0f", rsp)
        end
        table.insert(fspans, " ".. fmt_sign(rsp))
      end
    else
      local reason = ""
      if (multi > 1) then
        reason = reason .. " (".. multi .."x [[" .. (edo / multi) .. "-EDO]])"
      end
      table.insert(fspans, " N/A" .. reason)
    end
  end

  local border_style = ""
  if fs_enable then
    border_style = " class=\"thick-border\" style=\"border-top: 2px solid #aaa;\""
  end
  
  local titleMarkup = ""
  if title then
	  titleMarkup = "|-\n|+ style=\"font-size: 105%;\" | " .. title .. "\n"
  end
  local data_rows = "|-"..border_style.."\n" .. table.concat(tabs, "\n|") .. "\n"
	.. "|-\n" .. table.concat(trel, "\n|") .. "\n"
	.. "|-"..border_style.."\n" .. table.concat(tdeg, "\n|") .. "\n"
	.. "|-\n" .. table.concat(tdegor, "\n|") .. "\n"
  
  if fs_enable then
    data_rows = data_rows .. "|-"..border_style.."\n" .. table.concat(fspans, "\n|") .. "\n"
  end
  
  return "{| class=\"wikitable center-all\"\n" .. titleMarkup ..
	  "|-\n" ..
	table.concat(tpri, "\n!") .. "\n" .. data_rows  .. "|}"
end

local primes = { 
  2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 
  43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
  101, 103, 107, 109, 113, 127
}

-- evaluate input on error use default
local function eval_num_arg(input, def_value)
  local result = input
  if type(input) ~= "number" then
    result = def_value
    if type(input) == "string" then
      input = input:match("^%s*(.-)%s*$")
      if string.len(input) > 0 then
        result = tonumber(input)
      end
    end
  end
  return result
end

-- calculate default precicion by EDO magnitude
local function prec_by_edo(edo)
  return math.floor(math.log(edo*1.9)/math.log(10))
end

function p.primes_in_edo (frame)
  -- optional EDO number, default: 12
  local edo = eval_num_arg(frame.args["edo"], 12)
  -- optional number of columns, default: 8
  local columns = eval_num_arg(frame.args["columns"], 8)
  -- optional start column, default: start with prime 2
  local start = eval_num_arg(frame.args["start"], 1)
  local title = frame.args["title"] or "Approximation of prime intervals in " .. edo .. " EDO"
  -- optional precision for abs error, default about 3 digits
  local prec = eval_num_arg(frame.args["prec"], prec_by_edo(edo))
  -- option to show fifthspan information, default: false
  local fs = (frame.args["fs"] == 1) or (frame.args["fs"] == "1") or false
  return edoprox( edo, {unpack(primes, start, start+columns-1)}, title, prec, fs)
end

return p;