跳转到内容

模組:PythonScript/Runtime/Cmath

维基百科,自由的百科全书
-- Module:PythonScript/Runtime/Cmath
-- 整合核心:BigInt 大數引擎、Cmath 四元數/複數引擎、Matrix 廣義超複數泛函線性代數引擎、math 與 cmath 標準庫
local MathCore = {}

-- 延遲載入的例外拋出引擎 (避免與 Runtime 產生循環依賴)
local function raise_err(err_type, msg)
    require("Module:PythonScript/Runtime").raise(err_type, msg)
end

-- ==========================================
-- [1] 大數運算引擎 (BigInt Engine)
-- ==========================================
local BASE = 10000000
local SAFE_LIMIT = 9007199254740990

local BigInt = {}

local function strip_limbs(limbs)
    while #limbs > 1 and limbs[#limbs] == 0 do table.remove(limbs) end
    if #limbs > 10000 then
        raise_err("ValueError", "Integer exceeds maximum length limit (10000 limbs)")
    end
    return limbs
end

local function add_limbs(a, b)
    local res = {}; local carry = 0; local n = math.max(#a, #b)
    for i = 1, n do
        local sum = (a[i] or 0) + (b[i] or 0) + carry
        carry = math.floor(sum / BASE)
        table.insert(res, sum % BASE)
    end
    if carry > 0 then table.insert(res, carry) end
    return strip_limbs(res)
end

local function sub_limbs(a, b)
    local res = {}; local borrow = 0
    for i = 1, #a do
        local diff = a[i] - (b[i] or 0) - borrow
        if diff < 0 then diff = diff + BASE; borrow = 1 else borrow = 0 end
        table.insert(res, diff)
    end
    return strip_limbs(res)
end

local function cmp_limbs(a, b)
    if #a ~= #b then return #a < #b and -1 or 1 end
    for i = #a, 1, -1 do
        if a[i] ~= b[i] then return a[i] < b[i] and -1 or 1 end
    end
    return 0
end

local function mul_limbs(a, b)
    if math.min(#a, #b) <= 16 then
        local res = {}; for i = 1, #a + #b do res[i] = 0 end
        for i = 1, #a do
            local carry = 0
            for j = 1, #b do
                local sum = res[i+j-1] + a[i] * b[j] + carry
                carry = math.floor(sum / BASE); res[i+j-1] = sum % BASE
            end
            res[i+#b] = res[i+#b] + carry
        end
        return strip_limbs(res)
    end

    local n = math.max(#a, #b)
    local m = math.floor(n / 2)
    local low1, high1, low2, high2 = {}, {}, {}, {}
    for i = 1, m do low1[i] = a[i] or 0; low2[i] = b[i] or 0 end
    for i = m + 1, n do high1[i - m] = a[i] or 0; high2[i - m] = b[i] or 0 end
    low1, high1 = strip_limbs(low1), strip_limbs(high1)
    low2, high2 = strip_limbs(low2), strip_limbs(high2)
    local z0 = mul_limbs(low1, low2)
    local z2 = mul_limbs(high1, high2)
    local z1 = mul_limbs(add_limbs(low1, high1), add_limbs(low2, high2))
    z1 = sub_limbs(sub_limbs(z1, z2), z0)

    local res = {}; for i = 1, n * 2 do res[i] = 0 end
    for i = 1, #z0 do res[i] = z0[i] end
    for i = 1, #z1 do res[i+m] = res[i+m] + z1[i] end
    for i = 1, #z2 do res[i+m*2] = res[i+m*2] + z2[i] end

    local carry = 0
    for i = 1, #res do
        local sum = res[i] + carry
        carry = math.floor(sum / BASE); res[i] = sum % BASE
    end
    while carry > 0 do table.insert(res, carry % BASE); carry = math.floor(carry / BASE) end
    return strip_limbs(res)
end

local function divmod_limbs(a, b)
    if #b == 1 and b[1] == 0 then raise_err("ZeroDivisionError", "division by zero") end
    local cmp = cmp_limbs(a, b)
    if cmp < 0 then return {0}, { unpack(a) } end
    if cmp == 0 then return {1}, {0} end

    local q, r = {}, {}
    for i = 1, #a do r[i] = a[i] end

    if #b == 1 then
        local divisor = b[1]; local carry = 0
        for i = #r, 1, -1 do
            local cur = carry * BASE + r[i]
            q[i] = math.floor(cur / divisor); carry = cur % divisor
        end
        return strip_limbs(q), strip_limbs({carry})
    end

    -- Knuth Algorithm D 標準正規化:保證 #norm_b == #b 不溢位且最高位 d >= BASE / 2
    local scale = math.floor(BASE / (b[#b] + 1))
    local norm_b = (scale > 1) and mul_limbs(b, {scale}) or { unpack(b) }
    local norm_a = (scale > 1) and mul_limbs(r, {scale}) or r
    local n_b = #norm_b
    local m_len = #norm_a - n_b
    table.insert(norm_a, 0)

    local d = norm_b[n_b]
    local v_next = norm_b[n_b - 1] or 0

    for i = m_len + 1, 1, -1 do
        local u2 = norm_a[i + n_b] or 0
        local u1 = norm_a[i + n_b - 1] or 0
        local u0 = norm_a[i + n_b - 2] or 0
        local num_hi = u2 * BASE + u1
        local q_guess = math.floor(num_hi / d)
        if q_guess >= BASE then q_guess = BASE - 1 end
        local r_hat = num_hi - q_guess * d

        -- Knuth Step D3 次高位精確修正:消除絕大多數高估誤差
        while r_hat < BASE and (q_guess * v_next) > (r_hat * BASE + u0) do
            q_guess = q_guess - 1
            r_hat = r_hat + d
        end

        if q_guess > 0 then
            local carry = 0
            for j = 1, n_b do
                local sub_val = norm_b[j] * q_guess + carry
                carry = math.floor(sub_val / BASE)
                local diff = (norm_a[i+j-1] or 0) - (sub_val % BASE)
                if diff < 0 then diff = diff + BASE; carry = carry + 1 end
                norm_a[i+j-1] = diff
            end
            local diff_last = (norm_a[i+n_b] or 0) - carry
            while diff_last < 0 do
                q_guess = q_guess - 1; carry = 0
                for j = 1, n_b do
                    local sum = (norm_a[i+j-1] or 0) + norm_b[j] + carry
                    carry = math.floor(sum / BASE); norm_a[i+j-1] = sum % BASE
                end
                diff_last = diff_last + carry
            end
            norm_a[i+n_b] = diff_last
        end
        q[i] = q_guess
    end
    local rem = (scale > 1) and divmod_limbs(strip_limbs(norm_a), {scale}) or strip_limbs(norm_a)
    return strip_limbs(q), rem
end

function BigInt.to_limbs(val)
    if type(val) == "table" and val.__type == "int" and val.is_big then
        return val.limbs
    elseif type(val) == "number" then
        if val == 0 then return {0} end; local abs_val = math.abs(val); local limbs = {}
        while abs_val > 0 do table.insert(limbs, abs_val % BASE); abs_val = math.floor(abs_val / BASE) end
        return limbs
    elseif type(val) == "string" then
        local s = val:gsub("^-", ""); if s == "" or s == "0" then return {0} end
        if #s > 70000 then
            raise_err("ValueError", "Integer string exceeds maximum length limit (70000 digits)")
        end
        local limbs = {}
        for i = #s, 1, -7 do local start_idx = math.max(1, i - 6); table.insert(limbs, tonumber(s:sub(start_idx, i))) end
        return strip_limbs(limbs)
    end; return {0}
end

function BigInt.to_string(sign, limbs)
    if #limbs == 1 and limbs[1] == 0 then return "0" end
    local res = {}
    table.insert(res, tostring(limbs[#limbs]))
    for i = #limbs - 1, 1, -1 do table.insert(res, string.format("%07d", limbs[i])) end
    return (sign < 0 and "-" or "") .. table.concat(res)
end

local function extract_int_sign_limbs(val)
    if type(val) == "boolean" then val = val and 1 or 0 end
    if type(val) == "table" and val.__type == "int" then
        local l = strip_limbs({ unpack(val.limbs or {0}) })
        local s = (#l == 1 and l[1] == 0) and 1 or (val.sign or 1)
        return s, l
    elseif type(val) == "number" then
        if val ~= val or val == math.huge or val == -math.huge or math.floor(val) ~= val then
            raise_err("TypeError", "pow() 3rd argument not allowed unless all arguments are integers")
        end
        if val == 0 then return 1, {0} end
        local s = (val < 0) and -1 or 1
        local av = math.abs(val)
        if av <= SAFE_LIMIT then
            return s, BigInt.to_limbs(av)
        end
        local m, e = math.frexp(av)
        local b_limbs = BigInt.to_limbs(math.ldexp(m, 53))
        local shift = e - 53
        local two_pow = {2}
        while shift > 0 do
            if shift % 2 == 1 then b_limbs = mul_limbs(b_limbs, two_pow) end
            shift = math.floor(shift * 0.5)
            if shift > 0 then two_pow = mul_limbs(two_pow, two_pow) end
        end
        return s, strip_limbs(b_limbs)
    end
    raise_err("TypeError", "pow() 3rd argument not allowed unless all arguments are integers")
end

-- 大數快速模冪演算法 (Binary Exponentiation with Modulo + 負指數擴展歐幾里得模反元素)
function BigInt.powmod(base, exp, mod)
    local b_sign, b_limbs = extract_int_sign_limbs(base)
    local e_sign, e_limbs = extract_int_sign_limbs(exp)
    local m_sign, m_limbs = extract_int_sign_limbs(mod)

    if #m_limbs == 1 and m_limbs[1] == 0 then
        raise_err("ValueError", "pow() 3rd argument cannot be 0")
    end
    if #m_limbs == 1 and m_limbs[1] == 1 then
        return 0
    end

    -- 極速通道:當 |mod| <= 94906265 (平方不超過 2^53-1) 且指數為非負小整數時,直接以原生 53-bit 整數運算
    if e_sign > 0 and #m_limbs <= 2 and #e_limbs <= 2 then
        local m_val = (m_limbs[2] or 0) * BASE + m_limbs[1]
        if m_val <= 94906265 then
            local _, r_limbs = divmod_limbs(b_limbs, m_limbs)
            local b_val = (r_limbs[2] or 0) * BASE + r_limbs[1]
            if b_sign < 0 and b_val ~= 0 then b_val = m_val - b_val end
            local e_val = (e_limbs[2] or 0) * BASE + e_limbs[1]
            local res = 1
            while e_val > 0 do
                if e_val % 2 == 1 then res = (res * b_val) % m_val end
                e_val = math.floor(e_val * 0.5)
                if e_val > 0 then b_val = (b_val * b_val) % m_val end
            end
            if m_sign < 0 and res > 0 then res = res - m_val end
            return res
        end
    end

    -- 1. 先將底數正規化至 [0, |mod|) 區間
    local _, rem = divmod_limbs(b_limbs, m_limbs)
    if b_sign < 0 and not (#rem == 1 and rem[1] == 0) then
        b_limbs = sub_limbs(m_limbs, rem)
    else
        b_limbs = rem
    end

    -- 2. 若指數為負數 (Python 3.8+),透過擴展歐幾里得演算法求模反元素 base^-1 (mod |mod|)
    if e_sign < 0 then
        if #b_limbs == 1 and b_limbs[1] == 0 then
            raise_err("ValueError", "base is not invertible for the given modulus")
        end
        local old_r, r = m_limbs, b_limbs
        local old_t_s, old_t_l = 1, {0}
        local t_s, t_l = 1, {1}
        while not (#r == 1 and r[1] == 0) do
            local q, rem_r = divmod_limbs(old_r, r)
            old_r, r = r, rem_r
            local qt_l = mul_limbs(q, t_l)
            local qt_s = (#qt_l == 1 and qt_l[1] == 0) and 1 or -t_s
            local nt_s, nt_l
            if old_t_s == qt_s then
                nt_s = old_t_s
                nt_l = add_limbs(old_t_l, qt_l)
            else
                local cmp = cmp_limbs(old_t_l, qt_l)
                if cmp == 0 then nt_s, nt_l = 1, {0}
                elseif cmp > 0 then nt_s, nt_l = old_t_s, sub_limbs(old_t_l, qt_l)
                else nt_s, nt_l = qt_s, sub_limbs(qt_l, old_t_l) end
            end
            old_t_s, old_t_l = t_s, t_l
            t_s, t_l = nt_s, nt_l
        end
        if not (#old_r == 1 and old_r[1] == 1) then
            raise_err("ValueError", "base is not invertible for the given modulus")
        end
        if old_t_s < 0 and not (#old_t_l == 1 and old_t_l[1] == 0) then
            b_limbs = sub_limbs(m_limbs, old_t_l)
        else
            b_limbs = old_t_l
        end
    end

    -- 3. 大數二進位平方取模迴圈 (支援任意千位數以上之超大指數)
    local res_limbs = {1}
    local cur_base = b_limbs
    local cur_exp = e_limbs
    while not (#cur_exp == 1 and cur_exp[1] == 0) do
        if cur_exp[1] % 2 == 1 then
            local prod = mul_limbs(res_limbs, cur_base)
            local _, r_mod = divmod_limbs(prod, m_limbs)
            res_limbs = r_mod
        end
        local next_exp = {}
        local carry = 0
        for i = #cur_exp, 1, -1 do
            local cur = carry * BASE + cur_exp[i]
            next_exp[i] = math.floor(cur * 0.5)
            carry = cur % 2
        end
        cur_exp = strip_limbs(next_exp)
        if not (#cur_exp == 1 and cur_exp[1] == 0) then
            local sq = mul_limbs(cur_base, cur_base)
            local _, r_sq = divmod_limbs(sq, m_limbs)
            cur_base = r_sq
        end
    end

    -- 4. 對齊 Python 負模數餘數正負號規範
    local res_sign = 1
    if m_sign < 0 and not (#res_limbs == 1 and res_limbs[1] == 0) then
        res_limbs = sub_limbs(m_limbs, res_limbs)
        res_sign = -1
    end
    if #res_limbs == 1 and res_limbs[1] == 0 then return 0 end
    local num_str = BigInt.to_string(res_sign, res_limbs)
    local num = tonumber(num_str)
    if num and math.abs(num) <= SAFE_LIMIT then return num end
    return { __type = "int", is_big = true, value = num, sign = res_sign, limbs = res_limbs }
end

MathCore.BigInt = {
    BASE = BASE,
    SAFE_LIMIT = SAFE_LIMIT,
    add_limbs = add_limbs, sub_limbs = sub_limbs,
    mul_limbs = mul_limbs, divmod_limbs = divmod_limbs, cmp_limbs = cmp_limbs,
    to_limbs = BigInt.to_limbs, to_string = BigInt.to_string,
    powmod = BigInt.powmod
}

-- ==========================================
-- [2] Python `int` 的專屬擴充方法
-- ==========================================
MathCore.IntMethods = {
    bit_length = { __type = "builtin_method", __call = function(args)
        local self = args[1]
        local v = self.__dict.value
        if not self.__dict.is_big and v then
            if v == 0 then return 0 end
            return math.floor(math.log(math.abs(v)) / math.log(2)) + 1
        end
        local s = BigInt.to_string(1, self.__dict.limbs)
        return math.floor(#s * 3.321928094887362)
    end},
    bit_count = { __type = "builtin_method", __call = function(args)
        local self = args[1]; local v = self.__dict.value
        if not self.__dict.is_big and v then
            local count = 0; v = math.abs(v)
            while v > 0 do count = count + (v % 2); v = math.floor(v / 2) end
            return count
        end
        raise_err("NotImplementedError", "bit_count for BigInt requires full base2 conversion")
    end},
    to_bytes = { __type = "builtin_method", __call = function(args, kwargs)
        local self, length = args[1], args[2] or 1
        local byteorder = args[3] or (kwargs and kwargs.byteorder) or "big"
        return "<bytes: " .. length .. " bytes, " .. byteorder .. ">"
    end},
    from_bytes = { __type = "classmethod", __func = { __type = "builtin_method", __call = function()
        raise_err("NotImplementedError", "from_bytes requires raw byte parsing")
    end}},
    as_integer_ratio = { __type = "builtin_method", __call = function(args)
        return { __type = "tuple", elements = {args[1], { __type = "int", is_big = false, value = 1, sign = 1 }} }
    end},
    is_integer = { __type = "builtin_method", __call = function() return true end }
}

local function is_decimal_obj(x)
    return type(x) == "table" and x.__type == "instance" and x.__dict and x.__dict._is_decimal == true
end

local function is_fraction_obj(x)
    return type(x) == "table" and x.__type == "instance" and x.__dict and x.__dict._is_fraction == true
end

local function is_matrix_obj(x)
    return type(x) == "table" and (x.__type == "list" or x.__type == "tuple") and (
        x.is_matrix == true or (#x.elements > 0 and type(x.elements[1]) == "table" and (x.elements[1].__type == "list" or x.elements[1].__type == "tuple"))
    )
end

-- ==========================================
-- 輔助:安全數值解包 (實數)
-- ==========================================
local function unwrap_num(x)
    if type(x) == "number" then return x end
    if type(x) == "boolean" then return x and 1 or 0 end
    if type(x) == "table" then
        if x.__type == "int" then
            return x.value or tonumber(MathCore.BigInt.to_string(x.sign, x.limbs))
        elseif x.__type == "complex" then
            raise_err("TypeError", "must be real number, not " .. (((x.k and x.k ~= 0) or (x.jk and x.jk ~= 0)) and "quaternion" or "complex"))
        elseif is_decimal_obj(x) then
            return require("Module:PythonScript/Librarys/Decimal").to_number(x)
        elseif x.__type == "instance" then
            local PyClass = require("Module:PythonScript/Class")
            local ok, flt_m = pcall(PyClass.get_attr, x, "__float__")
            if ok and flt_m and flt_m.__type == "bound_method" then
                return require("Module:PythonScript/Evaluator").invoke(flt_m.__func, {}, {}, flt_m.__self)
            end
            if x.__builtin_value ~= nil then return unwrap_num(x.__builtin_value) end
        end
        raise_err("TypeError", "must be real number, not " .. (x.__class and x.__class.__name or x.__type or type(x)))
    end
    if type(x) == "string" then return tonumber(x) end
    raise_err("TypeError", "must be real number")
end

-- ==========================================
-- [3] 超複數 / 四元數引擎 (Cmath & Quaternion Engine)
-- Python 表示法:p = a + bj + ck + djk  (對應數學 i -> 1j, j -> 1k, k -> 1jk)
-- 內部極輕量結構:q = {a, b, c, d} (4元素陣列,0 雜湊表開銷)
-- ==========================================
local Cmath = {}
local Matrix = {}

function Cmath.make_complex(real, imag, k, jk)
    return {
        __type = "complex",
        real = real or 0,
        imag = imag or 0,
        k = k or 0,
        jk = jk or 0
    }
end
Cmath.make_quat = Cmath.make_complex

function Cmath.is_quaternion(x)
    return type(x) == "table" and x.__type == "complex" and ((x.k and x.k ~= 0) or (x.jk and x.jk ~= 0))
end

local function get_parts(val)
    if type(val) == "table" and val.__type == "complex" then
        return val.real or 0, val.imag or 0, val.k or 0, val.jk or 0
    end
    local n = unwrap_num(val)
    if type(n) == "number" then return n, 0, 0, 0 end
    raise_err("TypeError", "unsupported operand type(s) for complex/quaternion math")
end
Cmath.get_parts = get_parts

local function to_q4(val, forbid_hyper)
    if type(val) == "number" then return { val, 0, 0, 0 } end
    if type(val) == "boolean" then return { val and 1 or 0, 0, 0, 0 } end
    if type(val) == "table" and val.__type == "complex" then
        if forbid_hyper then
            raise_err("TypeError", "must be real number, not " .. (Cmath.is_quaternion(val) and "quaternion" or "complex"))
        end
        return { val.real or 0, val.imag or 0, val.k or 0, val.jk or 0 }
    end
    return { unwrap_num(val), 0, 0, 0 }
end

local function clean_zero(v)
    if type(v) ~= "number" then return v end
    if v == 0 or math.abs(v) < 1e-14 then return 0 end
    local rounded = math.floor(v + 0.5)
    if math.abs(v - rounded) < 1e-13 then return rounded end
    return v
end

local function from_q4(q, force_complex)
    local a = clean_zero(q[1])
    local b = (math.abs(q[2]) < 1e-12) and 0 or clean_zero(q[2])
    local c = (math.abs(q[3]) < 1e-12) and 0 or clean_zero(q[3])
    local d = (math.abs(q[4]) < 1e-12) and 0 or clean_zero(q[4])
    if not force_complex and b == 0 and c == 0 and d == 0 then
        return a
    end
    return Cmath.make_complex(a, b, c, d)
end

-- 極輕量 4D 四元數底層算子 {a, b, c, d}
local function q_add(p, q) return { p[1]+q[1], p[2]+q[2], p[3]+q[3], p[4]+q[4] } end
local function q_sub(p, q) return { p[1]-q[1], p[2]-q[2], p[3]-q[3], p[4]-q[4] } end
local function q_neg(p)    return { -p[1], -p[2], -p[3], -p[4] } end
local function q_conj(p)   return { p[1], -p[2], -p[3], -p[4] } end
local function q_scale(p, s) return { p[1]*s, p[2]*s, p[3]*s, p[4]*s } end
local function q_dot(p, q) return p[1]*q[1] + p[2]*q[2] + p[3]*q[3] + p[4]*q[4] end
local function q_abs_sq(p) return p[1]*p[1] + p[2]*p[2] + p[3]*p[3] + p[4]*p[4] end
local function q_abs(p)    return math.sqrt(q_abs_sq(p)) end
local function q_vec_norm(p) return math.sqrt(p[2]*p[2] + p[3]*p[3] + p[4]*p[4]) end

-- 漢密爾頓積 (Hamilton Product): p * q
-- (a*t-b*x-c*y-d*z) + (a*x+b*t+c*z-d*y)j + (a*y-b*z+c*t+d*x)k + (a*z+b*y-c*x+d*t)jk
local function q_mul(p, q)
    local a, b, c, d = p[1], p[2], p[3], p[4]
    local t, x, y, z = q[1], q[2], q[3], q[4]
    return {
        a*t - b*x - c*y - d*z,
        a*x + b*t + c*z - d*y,
        a*y - b*z + c*t + d*x,
        a*z + b*y - c*x + d*t
    }
end

-- 四元數外積: p @ q
-- (t*b-a*x+c*z-d*y)j + (t*c-a*y+d*x-b*z)k + (t*d-a*z+b*y-x*c)jk
local function q_cross(p, q)
    local a, b, c, d = p[1], p[2], p[3], p[4]
    local t, x, y, z = q[1], q[2], q[3], q[4]
    return {
        0,
        t*b - a*x + c*z - d*y,
        t*c - a*y + d*x - b*z,
        t*d - a*z + b*y - x*c
    }
end

-- 乘法逆元: p^-1 = conj(p) / dot(p, p)
local function q_inv(p)
    local n2 = q_abs_sq(p)
    if n2 == 0 then raise_err("ZeroDivisionError", "complex/quaternion division by zero") end
    return { p[1]/n2, -p[2]/n2, -p[3]/n2, -p[4]/n2 }
end

-- 右除 p / q = p * q^-1 與 左除 p \ q = p^-1 * q
local function q_rdiv(p, q) return q_mul(p, q_inv(q)) end
local function q_ldiv(p, q) return q_mul(q_inv(p), q) end

-- 單位號函數 sgn(p) = p / |p|
local function q_sgn(p)
    local m = q_abs(p)
    if m == 0 then return { 0, 0, 0, 0 } end
    return { p[1]/m, p[2]/m, p[3]/m, p[4]/m }
end

-- 向量部單位純虛四元數 u = sgn(vec(p)),若 |vec(p)| == 0 則回傳 u (預設可選 0 或 1j)
local function q_vec_unit(p, default_j)
    local vn = q_vec_norm(p)
    if vn == 0 then
        return default_j and { 0, 1, 0, 0 } or { 0, 0, 0, 0 }, 0
    end
    return { 0, p[2]/vn, p[3]/vn, p[4]/vn }, vn
end

-- 幅角 arg(p) = arccos(scalar(p) / abs(p)) = atan2(|v|, a)
local function q_arg(p)
    local vn = q_vec_norm(p)
    if p[1] == 0 and vn == 0 then return 0.0 end
    return math.atan2(vn, p[1])
end

-- exp(p) = exp(a) * (cos(|v|) + sgn(v)*sin(|v|))
local function q_exp(p)
    local ea = math.exp(p[1])
    local u, vn = q_vec_unit(p, false)
    if vn == 0 then return { ea, 0, 0, 0 } end
    local cv = ea * math.cos(vn)
    local sv = ea * math.sin(vn)
    return { cv, u[2]*sv, u[3]*sv, u[4]*sv }
end

-- ln(p) = ln(|p|) + sgn(v)*arg(p)
local function q_log(p)
    local mag = q_abs(p)
    if mag == 0 then raise_err("ValueError", "math domain error") end
    local u, vn = q_vec_unit(p, true)
    local theta = math.atan2(vn, p[1])
    return { math.log(mag), u[2]*theta, u[3]*theta, u[4]*theta }
end

-- sqrt(p) = sqrt(|p|) * (cos(arg(p)/2) + sgn(v)*sin(arg(p)/2))
local function q_sqrt(p)
    local mag = q_abs(p)
    if mag == 0 then return { 0, 0, 0, 0 } end
    local u, vn = q_vec_unit(p, true)
    if vn == 0 and p[1] >= 0 then
        return { math.sqrt(p[1]), 0, 0, 0 }
    end
    local wr = math.sqrt(math.max(0, (mag + p[1]) * 0.5))
    local wv = math.sqrt(math.max(0, (mag - p[1]) * 0.5))
    return { wr, u[2]*wv, u[3]*wv, u[4]*wv }
end

-- p ** q = exp(q * ln(p)) (由左至右約定;整數次方支援精確快速冪)
local function q_pow(p, q)
    if q_abs_sq(p) == 0 then
        if q_abs_sq(q) == 0 then return { 1, 0, 0, 0 }
        elseif q[1] > 0 and q[2] == 0 and q[3] == 0 and q[4] == 0 then return { 0, 0, 0, 0 }
        else raise_err("ZeroDivisionError", "0.0 cannot be raised to a negative or complex power") end
    end
    if q[2] == 0 and q[3] == 0 and q[4] == 0 then
        local exp_r = q[1]
        if exp_r == -1 then return q_inv(p) end
        if exp_r == 0 then return { 1, 0, 0, 0 } end
        if math.floor(exp_r) == exp_r and math.abs(exp_r) <= 1024 then
            local k = math.floor(math.abs(exp_r))
            local base = (exp_r < 0) and q_inv(p) or { p[1], p[2], p[3], p[4] }
            local res = { 1, 0, 0, 0 }
            while k > 0 do
                if k % 2 == 1 then res = q_mul(res, base) end
                k = math.floor(k / 2)
                if k > 0 then base = q_mul(base, base) end
            end
            return res
        end
    end
    return q_exp(q_mul(q, q_log(p)))
end

local function r_sinh(x)
    if x == 0 then return 0.0 end
    local e = math.exp(x)
    return (e - 1.0 / e) * 0.5
end

local function r_cosh(x)
    if x == 0 then return 1.0 end
    local e = math.exp(x)
    return (e + 1.0 / e) * 0.5
end

-- sin(p) = sin(a)cosh(|v|) + cos(a)sgn(v)sinh(|v|)
local function q_sin(p)
    local a = p[1]
    local u, vn = q_vec_unit(p, false)
    local sc = math.sin(a) * r_cosh(vn)
    local cs = math.cos(a) * r_sinh(vn)
    return { sc, u[2]*cs, u[3]*cs, u[4]*cs }
end

-- cos(p) = cos(a)cosh(|v|) - sin(a)sgn(v)sinh(|v|)
local function q_cos(p)
    local a = p[1]
    local u, vn = q_vec_unit(p, false)
    local cc = math.cos(a) * r_cosh(vn)
    local ss = -math.sin(a) * r_sinh(vn)
    return { cc, u[2]*ss, u[3]*ss, u[4]*ss }
end

local function q_tan(p) return q_rdiv(q_sin(p), q_cos(p)) end

-- sinh(p) = sinh(a)cos(|v|) + cosh(a)sgn(v)sin(|v|)
local function q_sinh(p)
    local a = p[1]
    local u, vn = q_vec_unit(p, false)
    local sc = r_sinh(a) * math.cos(vn)
    local cs = r_cosh(a) * math.sin(vn)
    return { sc, u[2]*cs, u[3]*cs, u[4]*cs }
end

-- cosh(p) = cosh(a)cos(|v|) + sinh(a)sgn(v)sin(|v|)
local function q_cosh(p)
    local a = p[1]
    local u, vn = q_vec_unit(p, false)
    local cc = r_cosh(a) * math.cos(vn)
    local ss = r_sinh(a) * math.sin(vn)
    return { cc, u[2]*ss, u[3]*ss, u[4]*ss }
end

local function q_tanh(p) return q_rdiv(q_sinh(p), q_cosh(p)) end

-- asinh(p) = ln(p + sqrt(p^2 + 1))
local function q_asinh(p)
    local p2 = q_mul(p, p)
    local p2_plus_1 = { p2[1] + 1, p2[2], p2[3], p2[4] }
    return q_log(q_add(p, q_sqrt(p2_plus_1)))
end

-- acosh(p) = ln(p + sqrt(p + 1)*sqrt(p - 1))  (消除負實軸分支切割誤差,等價於 ln(p + sqrt(p^2 - 1)))
local function q_acosh(p)
    local sp1 = q_sqrt({ p[1] + 1, p[2], p[3], p[4] })
    local sm1 = q_sqrt({ p[1] - 1, p[2], p[3], p[4] })
    return q_log(q_add(p, q_mul(sp1, sm1)))
end

-- atanh(p) = (ln(1 + p) - ln(1 - p)) / 2
local function q_atanh(p)
    local one_plus_p  = { 1 + p[1],  p[2],  p[3],  p[4] }
    local one_minus_p = { 1 - p[1], -p[2], -p[3], -p[4] }
    return q_scale(q_sub(q_log(one_plus_p), q_log(one_minus_p)), 0.5)
end

-- asin(p) = -sgn(v) * asinh(p * sgn(v))
local function q_asin(p)
    local u = q_vec_unit(p, true)
    return q_mul(q_neg(u), q_asinh(q_mul(p, u)))
end

-- acos(p) = -sgn(v) * acosh(p)
local function q_acos(p)
    local u = q_vec_unit(p, true)
    return q_mul(q_neg(u), q_acosh(p))
end

-- atan(p) = -sgn(v) * atanh(p * sgn(v))
local function q_atan(p)
    local u = q_vec_unit(p, true)
    return q_mul(q_neg(u), q_atanh(q_mul(p, u)))
end

-- =========================================================================
-- 高精度複變與實數 Gamma / Lgamma 引擎 (Godfrey Lanczos g = 607/128, N = 15)
-- =========================================================================
local LANCZOS_G = 4.7421875 -- 607 / 128
local LANCZOS_P = {
	0.99999999999999709182,
	57.156235665862923517,
	-59.597960355475491248,
	14.136097974741747174,
	-0.49191381609762019978,
	0.33994649984811888699e-4,
	0.46523628927048575665e-4,
	-0.98374475304879564677e-4,
	0.15808870322491248884e-3,
	-0.21026444172410488319e-3,
	0.21743961811521264320e-3,
	-0.16431810653676389022e-3,
	0.84418223983852743293e-4,
	-0.26190838401581408670e-4,
	0.36899182659531622704e-5
}
local SQRT_2PI = 2.506628274631000502415765284811
local LN_SQRT_2PI = 0.918938533204672741780329736406
local function is_nan(x) return x ~= x end
-- 🛡️ 四元數防護網:確保輸入僅為實數或標準複數 (a + bj)
local function extract_complex_2d(val, func_name)
	local r, i, k, jk = get_parts(val)
	if (k and k ~= 0) or (jk and jk ~= 0) then
		raise_err("NotImplementedError", func_name .. "() does not support Quaternions; input must be real or complex")
	end
	return r, i
end

-- 複數二維基礎算子 (專供 Lanczos 高速內迴圈使用)
local function c2_add(ar, ai, br, bi) return ar + br, ai + bi end
local function c2_sub(ar, ai, br, bi) return ar - br, ai - bi end
local function c2_mul(ar, ai, br, bi) return ar*br - ai*bi, ar*bi + ai*br end
local function c2_div(ar, ai, br, bi)
	local denom = br*br + bi*bi
	if denom == 0 then raise_err("ZeroDivisionError", "complex division by zero in gamma") end
	return (ar*br + ai*bi) / denom, (ai*br - ar*bi) / denom
end
local function c2_exp(ar, ai)
	local ea = math.exp(ar)
	return ea * math.cos(ai), ea * math.sin(ai)
end
local function c2_log(ar, ai)
	return 0.5 * math.log(ar*ar + ai*ai), math.atan2(ai, ar)
end
local function c2_pow(ar, ai, br, bi)
	local lr, li = c2_log(ar, ai)
	local pr, pi = c2_mul(br, bi, lr, li)
	return c2_exp(pr, pi)
end

-- 高精度計算 sin(pi * z),先對實部做模數歸約以防止大數精度流失
local function c2_sin_pi(zr, zi)
	local n = math.floor(zr + 0.5)
	local dr = zr - n
	local sign = (n % 2 == 0) and 1.0 or -1.0
	local pr, pi = math.pi * dr, math.pi * zi
	return sign * math.sin(pr) * r_cosh(pi), sign * math.cos(pr) * r_sinh(pi)
end

-- 計算 Lanczos 級數和 A_g(z - 1)
local function lanczos_sum(zr, zi)
	local xm1_r, xm1_i = zr - 1.0, zi
	local sr, si = LANCZOS_P[1], 0.0
	for idx = 2, #LANCZOS_P do
		local dr, di = xm1_r + (idx - 1), xm1_i
		local tr, ti = c2_div(LANCZOS_P[idx], 0.0, dr, di)
		sr, si = sr + tr, si + ti
	end
	return sr, si
end

-- 複變 Gamma 核心:回傳 (real, imag)
local function c2_gamma(zr, zi)
	if is_nan(zr) or is_nan(zi) then return 0/0, 0/0 end
	-- 極點檢查:非正整數 0, -1, -2, ...
	if zi == 0 and zr <= 0 and zr == math.floor(zr) then
		raise_err("ValueError", "math domain error")
	end
	-- 正整數快速通道 (1 <= z <= 23 完全無浮點捨入誤差)
	if zi == 0 and zr >= 1 and zr <= 23 and zr == math.floor(zr) then
		local prod = 1.0
		for k = 2, zr - 1 do prod = prod * k end
		return prod, 0.0
	end
	-- 左半平面 (Re(z) < 0.5):套用歐拉反射公式 Gamma(z) = pi / (sin(pi * z) * Gamma(1 - z))
	if zr < 0.5 then
		local g1mr, g1mi = c2_gamma(1.0 - zr, -zi)
		local spr, spi = c2_sin_pi(zr, zi)
		local den_r, den_i = c2_mul(spr, spi, g1mr, g1mi)
		return c2_div(math.pi, 0.0, den_r, den_i)
	end

	local tr, ti = zr + LANCZOS_G - 0.5, zi
	local sr, si = lanczos_sum(zr, zi)
	sr, si = c2_mul(SQRT_2PI, 0.0, sr, si)

	-- 半指數拆分防溢位:t^((z-0.5)/2) * exp(-t) * t^((z-0.5)/2) * (sqrt(2*pi) * A_g(z))
	local half_zr, half_zi = (zr - 0.5) * 0.5, zi * 0.5
	local p_half_r, p_half_i = c2_pow(tr, ti, half_zr, half_zi)
	local exp_mt_r, exp_mt_i = c2_exp(-tr, -ti)

	local res_r, res_i = c2_mul(p_half_r, p_half_i, exp_mt_r, exp_mt_i)
	res_r, res_i = c2_mul(res_r, res_i, sr, si)
	res_r, res_i = c2_mul(res_r, res_i, p_half_r, p_half_i)
	return res_r, res_i
end

-- 複變 Log-Gamma 核心:回傳 (real, imag)
local function c2_lgamma(zr, zi)
	if is_nan(zr) or is_nan(zi) then return 0/0, 0/0 end
	if zi == 0 and zr <= 0 and zr == math.floor(zr) then
		raise_err("ValueError", "math domain error")
	end
	if zi == 0 and (zr == 1.0 or zr == 2.0) then return 0.0, 0.0 end

	if zr < 0.5 then
		local lg1mr, lg1mi = c2_lgamma(1.0 - zr, -zi)
		local spr, spi = c2_sin_pi(zr, zi)
		local lsin_r, lsin_i = c2_log(spr, spi)
		return math.log(math.pi) - lsin_r - lg1mr, -lsin_i - lg1mi
	end

	local tr, ti = zr + LANCZOS_G - 0.5, zi
	local lntr, lnti = c2_log(tr, ti)
	local term1_r, term1_i = c2_mul(zr - 0.5, zi, lntr, lnti)
	local sr, si = lanczos_sum(zr, zi)
	local lnsr, lnsi = c2_log(sr, si)

	return term1_r - tr + LN_SQRT_2PI + lnsr, term1_i - ti + lnsi
end

-- 實數專用 lgamma(x) = ln(|Gamma(x)|),完全對齊 CPython math.lgamma
local function r_lgamma(x)
	if is_nan(x) then return 0/0 end
	if x == math.huge or x == -math.huge then
		if x < 0 then raise_err("ValueError", "math domain error") end
		return math.huge
	end
	if x <= 0 and x == math.floor(x) then raise_err("ValueError", "math domain error") end
	if x == 1.0 or x == 2.0 then return 0.0 end
	if x < 0.5 then
		local rem = x - math.floor(x)
		local sin_p = math.abs(math.sin(math.pi * rem))
		if sin_p == 0 then raise_err("ValueError", "math domain error") end
		return math.log(math.pi) - math.log(sin_p) - r_lgamma(1.0 - x)
	end
	local lgr, _ = c2_lgamma(x, 0.0)
	return lgr
end

-- ==========================================
-- Cmath 對外運算介面 (自動路由純量與矩陣)
-- ==========================================
Cmath.clean_zero = clean_zero
Cmath.c2_gamma = c2_gamma
Cmath.c2_lgamma = c2_lgamma
Cmath.r_lgamma = r_lgamma
Cmath.extract_complex_2d = extract_complex_2d
function Cmath.add(c1, c2)
    if is_matrix_obj(c1) or is_matrix_obj(c2) then return Matrix.elementwise_op(c1, c2, "+") end
    return from_q4(q_add(to_q4(c1, false), to_q4(c2, false)), true)
end

function Cmath.sub(c1, c2)
    if is_matrix_obj(c1) or is_matrix_obj(c2) then return Matrix.elementwise_op(c1, c2, "-") end
    return from_q4(q_sub(to_q4(c1, false), to_q4(c2, false)), true)
end

function Cmath.mul(c1, c2)
    if is_matrix_obj(c1) or is_matrix_obj(c2) then return Matrix.elementwise_op(c1, c2, "*") end
    return from_q4(q_mul(to_q4(c1, false), to_q4(c2, false)), true)
end

function Cmath.div(c1, c2)
    if is_matrix_obj(c1) or is_matrix_obj(c2) then return Matrix.rdiv(c1, c2) end
    return from_q4(q_rdiv(to_q4(c1, false), to_q4(c2, false)), true)
end

function Cmath.ldiv(c1, c2)
    if is_matrix_obj(c1) or is_matrix_obj(c2) then return Matrix.ldiv(c1, c2) end
    return from_q4(q_ldiv(to_q4(c1, false), to_q4(c2, false)), true)
end

function Cmath.pow(c1, c2)
    if is_matrix_obj(c1) or is_matrix_obj(c2) then return Matrix.pow(c1, c2) end
    return from_q4(q_pow(to_q4(c1, false), to_q4(c2, false)), true)
end

-- 四元數外積 p @ q
function Cmath.cross(c1, c2)
    return from_q4(q_cross(to_q4(c1, false), to_q4(c2, false)), true)
end
Cmath.matmul = Cmath.cross

function Cmath.dot(c1, c2)
    return clean_zero(q_dot(to_q4(c1, false), to_q4(c2, false)))
end

function Cmath.conj(c)
    return from_q4(q_conj(to_q4(c, false)), true)
end

function Cmath.scalar(c)
    local a = get_parts(c)
    return clean_zero(a)
end

-- 雙模態 vector:作為屬性讀取時是四元數 0+bj+ck+djk;作為方法呼叫 x.vector() 時傳回 3D 向量 [b, c, d]
function Cmath.vector(c)
    local _, b, k_val, jk_val = get_parts(c)
    local vec_quat = Cmath.make_complex(0, b, k_val, jk_val)
    vec_quat.__call = function()
        return { __type = "list", elements = { clean_zero(b), clean_zero(k_val), clean_zero(jk_val) }, is_matrix = true, shape = {3} }
    end
    return vec_quat
end

function Cmath.sgn(c)
    return from_q4(q_sgn(to_q4(c, false)), true)
end

function Cmath.arg(c)
    return clean_zero(q_arg(to_q4(c, false)))
end

function Cmath.neg(c) return from_q4(q_neg(to_q4(c, false)), true) end
function Cmath.pos(c) return from_q4(to_q4(c, false), true) end
function Cmath.abs(c) return clean_zero(q_abs(to_q4(c, false))) end

function Cmath.eq(c1, c2)
    local s1, a1, b1, k1, d1 = pcall(get_parts, c1)
    local s2, a2, b2, k2, d2 = pcall(get_parts, c2)
    if not s1 or not s2 then return false end
    return a1 == a2 and b1 == b2 and k1 == k2 and d1 == d2
end

function Cmath.to_str(c)
    local a, b, k_val, d_val = get_parts(c)
    local function fmt(n)
        local s = tostring(n)
        if s:match("%.0$") then return s:sub(1, -3) end
        return s
    end
    local function signed_fmt(n, unit)
        return (n >= 0 and "+" or "") .. fmt(n) .. unit
    end
    -- 若 k 與 jk 為 0,維持標準 Python complex 字串格式
    if k_val == 0 and d_val == 0 then
        if a == 0 then return fmt(b) .. "j" end
        return "(" .. fmt(a) .. signed_fmt(b, "j") .. ")"
    end
    -- 單一純虛基元簡寫 (如 2k 或 3jk)
    if a == 0 and b == 0 and d_val == 0 then return fmt(k_val) .. "k" end
    if a == 0 and b == 0 and k_val == 0 then return fmt(d_val) .. "jk" end
    if a == 0 then
        return "(" .. fmt(b) .. "j" .. signed_fmt(k_val, "k") .. signed_fmt(d_val, "jk") .. ")"
    end
    return "(" .. fmt(a) .. signed_fmt(b, "j") .. signed_fmt(k_val, "k") .. signed_fmt(d_val, "jk") .. ")"
end

-- 供 Evaluator / Class 直接查詢 complex / quaternion 的屬性與方法
function Cmath.get_attr(c, attr)
    local a, b, k_val, d_val = get_parts(c)
    if attr == "real" or attr == "scalar" then return clean_zero(a)
    elseif attr == "imag" then return clean_zero(b)
    elseif attr == "k" then return clean_zero(k_val)
    elseif attr == "jk" then return clean_zero(d_val)
    elseif attr == "vector" then return Cmath.vector(c)
    elseif attr == "conjugate" then
        return { __type = "builtin_method", __name = "conjugate", __call = function() return Cmath.conj(c) end }
    elseif attr == "sgn" then
        return { __type = "builtin_method", __name = "sgn", __call = function() return Cmath.sgn(c) end }
    elseif attr == "arg" then
        return { __type = "builtin_method", __name = "arg", __call = function() return Cmath.arg(c) end }
    end
    return nil
end

MathCore.Cmath = Cmath

-- ==========================================
-- [4] 線性代數與廣義超複數矩陣泛函引擎 (Matrix Functional Calculus)
-- ==========================================
local eval_expr_cached = nil
local dummy_scope_cached = nil

local function py_exact_binop(op, a, b)
    if type(a) == "number" and type(b) == "number" then
        if op == "+" then return a + b
        elseif op == "-" then return a - b
        elseif op == "*" then return a * b
        elseif op == "/" then
            if b == 0 then raise_err("ZeroDivisionError", "division by zero") end
            return a / b
        elseif op == "//" then
            if b == 0 then raise_err("ZeroDivisionError", "integer division or modulo by zero") end
            return math.floor(a / b)
        elseif op == "%" then
            if b == 0 then raise_err("ZeroDivisionError", "integer division or modulo by zero") end
            return a % b
        elseif op == "**" then return a ^ b end
    end
    if not eval_expr_cached then
        eval_expr_cached = require("Module:PythonScript/Evaluator/Core").getCoreFunction()
        dummy_scope_cached = require("Module:PythonScript/Evaluator/Utils").create_scope(nil)
    end
    return eval_expr_cached({
        type = "BinaryOp", op = op,
        left = { type = "DirectValue", value = a },
        right = { type = "DirectValue", value = b }
    }, dummy_scope_cached)
end

local function has_exact_algebraic_elements(obj)
    if type(obj) ~= "table" then return false end
    if obj.__type == "list" or obj.__type == "tuple" then
        for _, el in ipairs(obj.elements) do
            if has_exact_algebraic_elements(el) then return true end
        end
        return false
    end
    if obj.__type == "complex" then return false end
    return true -- int (BigInt), Decimal, Fraction, etc.
end

function Matrix.get_shape(obj)
    if type(obj) ~= "table" or (obj.__type ~= "list" and obj.__type ~= "tuple") then return {} end
    if #obj.elements == 0 then return {0} end
    local sub_shape = Matrix.get_shape(obj.elements[1])
    if not sub_shape then return nil end
    for i = 2, #obj.elements do
        local s = Matrix.get_shape(obj.elements[i])
        if not s or #s ~= #sub_shape then return nil end
        for j = 1, #s do if s[j] ~= sub_shape[j] then return nil end end
    end
    local shape = {#obj.elements}
    for i = 1, #sub_shape do table.insert(shape, sub_shape[i]) end
    return shape
end

function Matrix.to_lua_2d(obj)
    local shape = Matrix.get_shape(obj)
    if not shape or #shape ~= 2 or shape[1] == 0 or shape[2] == 0 then
        raise_err("ValueError", "expected a non-empty 2D matrix")
    end
    local m, n = shape[1], shape[2]
    local mat = {}
    for i = 1, m do
        mat[i] = {}
        local row = obj.elements[i].elements
        for j = 1, n do mat[i][j] = unwrap_num(row[j]) end
    end
    return mat, m, n
end

function Matrix.from_lua_2d(mat, m, n)
    local rows = {}
    for i = 1, m do
        local r = {}
        for j = 1, n do r[j] = clean_zero(mat[i][j]) end
        rows[i] = { __type = "list", elements = r, is_matrix = true, shape = {n} }
    end
    return { __type = "list", elements = rows, is_matrix = true, shape = {m, n} }
end

-- 轉為廣義超複數 2D 表 q2d (每個元素為 {a, b, c, d})
function Matrix.to_q2d(obj, forbid_hyper)
    local shape = Matrix.get_shape(obj)
    if not shape or #shape ~= 2 or shape[1] == 0 or shape[2] == 0 then
        raise_err("ValueError", "expected a non-empty 2D matrix")
    end
    local m, n = shape[1], shape[2]
    local mat = {}
    for i = 1, m do
        mat[i] = {}
        local row = obj.elements[i].elements
        for j = 1, n do mat[i][j] = to_q4(row[j], forbid_hyper) end
    end
    return mat, m, n
end

function Matrix.from_q2d(mat, m, n)
    local rows = {}
    for i = 1, m do
        local r = {}
        for j = 1, n do
            r[j] = from_q4(mat[i][j], false)
        end
        rows[i] = { __type = "list", elements = r, is_matrix = true, shape = {n} }
    end
    return { __type = "list", elements = rows, is_matrix = true, shape = {m, n} }
end

function Matrix.as_matrix(obj)
    local shape = Matrix.get_shape(obj)
    if not shape or #shape == 0 then raise_err("ValueError", "invalid nested list for matrix") end
    return Matrix.unary_op(obj, function(x) return x end)
end

function Matrix.elementwise_op(a, b, op)
    local is_list_a = (type(a) == "table" and (a.__type == "list" or a.__type == "tuple"))
    local is_list_b = (type(b) == "table" and (b.__type == "list" or b.__type == "tuple"))

    if is_list_a and is_list_b then
        local shapeA = Matrix.get_shape(a)
        local shapeB = Matrix.get_shape(b)
        if not shapeA or not shapeB then raise_err("ValueError", "invalid shapes for matrix operation") end

        if #shapeA == #shapeB then
            for i = 1, #shapeA do
                if shapeA[i] ~= shapeB[i] then
                    raise_err("ValueError", "operands could not be broadcast together with shapes")
                end
            end
            local res = { __type = "list", elements = {}, is_matrix = true, shape = shapeA }
            for i = 1, #a.elements do
                table.insert(res.elements, Matrix.elementwise_op(a.elements[i], b.elements[i], op))
            end
            return res
        elseif #shapeA == 2 and #shapeB == 1 and shapeA[2] == shapeB[1] then
            local res = { __type = "list", elements = {}, is_matrix = true, shape = shapeA }
            for i = 1, shapeA[1] do
                table.insert(res.elements, Matrix.elementwise_op(a.elements[i], b, op))
            end
            return res
        elseif #shapeA == 1 and #shapeB == 2 and shapeA[1] == shapeB[2] then
            local res = { __type = "list", elements = {}, is_matrix = true, shape = shapeB }
            for i = 1, shapeB[1] do
                table.insert(res.elements, Matrix.elementwise_op(a, b.elements[i], op))
            end
            return res
        else
            raise_err("ValueError", "operands could not be broadcast together with shapes")
        end
    elseif is_list_a then
        local shapeA = Matrix.get_shape(a)
        local res = { __type = "list", elements = {}, is_matrix = true, shape = shapeA }
        for i = 1, #a.elements do table.insert(res.elements, Matrix.elementwise_op(a.elements[i], b, op)) end
        return res
    elseif is_list_b then
        local shapeB = Matrix.get_shape(b)
        local res = { __type = "list", elements = {}, is_matrix = true, shape = shapeB }
        for i = 1, #b.elements do table.insert(res.elements, Matrix.elementwise_op(a, b.elements[i], op)) end
        return res
    else
        return py_exact_binop(op, a, b)
    end
end

function Matrix.unary_op(a, fn)
    if type(a) == "table" and (a.__type == "list" or a.__type == "tuple") then
        local shapeA = Matrix.get_shape(a)
        local res = { __type = "list", elements = {}, is_matrix = true, shape = shapeA }
        for i = 1, #a.elements do
            table.insert(res.elements, Matrix.unary_op(a.elements[i], fn))
        end
        return res
    end
    return fn(a)
end

-- ==========================================
-- 內部超複數 2D 矩陣底層算子 (q2d: 元素皆為 {a, b, c, d})
-- ==========================================
local function q2d_eye(n)
    local I = {}
    for i = 1, n do
        I[i] = {}
        for j = 1, n do I[i][j] = { (i == j) and 1 or 0, 0, 0, 0 } end
    end
    return I
end

local function q2d_copy(A, m, n)
    local B = {}
    for i = 1, m do
        B[i] = {}
        for j = 1, n do
            local cell = A[i][j]
            B[i][j] = { cell[1], cell[2], cell[3], cell[4] }
        end
    end
    return B
end

local function q2d_add(A, B, m, n, sign_b)
    sign_b = sign_b or 1
    local C = {}
    for i = 1, m do
        C[i] = {}
        for j = 1, n do
            local a, b = A[i][j], B[i][j]
            C[i][j] = { a[1] + sign_b*b[1], a[2] + sign_b*b[2], a[3] + sign_b*b[3], a[4] + sign_b*b[4] }
        end
    end
    return C
end

-- 左乘純量 s * A 與 右乘純量 A * s (在四元數中左右乘順序不同)
local function q2d_scale_left(s_q4, A, m, n)
    local C = {}
    for i = 1, m do
        C[i] = {}
        for j = 1, n do C[i][j] = q_mul(s_q4, A[i][j]) end
    end
    return C
end

local function q2d_scale_right(A, m, n, s_q4)
    local C = {}
    for i = 1, m do
        C[i] = {}
        for j = 1, n do C[i][j] = q_mul(A[i][j], s_q4) end
    end
    return C
end

local function q2d_matmul(A, B, m, k, n)
    local C = {}
    for i = 1, m do
        C[i] = {}
        for j = 1, n do
            local s1, s2, s3, s4 = 0, 0, 0, 0
            for p = 1, k do
                local prod = q_mul(A[i][p], B[p][j])
                s1 = s1 + prod[1]; s2 = s2 + prod[2]; s3 = s3 + prod[3]; s4 = s4 + prod[4]
            end
            C[i][j] = { s1, s2, s3, s4 }
        end
    end
    return C
end

local function q2d_frobenius_norm(A, m, n)
    local s = 0
    for i = 1, m do
        for j = 1, n do s = s + q_abs_sq(A[i][j]) end
    end
    return math.sqrt(s)
end

local function q2d_is_diagonal(A, n)
    for i = 1, n do
        for j = 1, n do
            if i ~= j and q_abs_sq(A[i][j]) > 1e-26 then return false end
        end
    end
    return true
end

local function q2d_has_quaternions(A, m, n)
    for i = 1, m do
        for j = 1, n do
            if math.abs(A[i][j][3]) > 1e-15 or math.abs(A[i][j][4]) > 1e-15 then
                return true
            end
        end
    end
    return false
end

-- 非交換除環左消去高斯-若爾當法 (Left-Pivoted Gauss-Jordan over Quaternions H)
-- 嚴格使用左乘列運算 E_k,求得精確雙邊反矩陣 A^-1 與行列式 (非交換矩陣回傳 Study/Dieudonne 範數模長)
local function q2d_gauss_jordan(mat, n)
    local aug = {}
    for i = 1, n do
        aug[i] = {}
        for j = 1, n do aug[i][j] = { mat[i][j][1], mat[i][j][2], mat[i][j][3], mat[i][j][4] } end
        for j = 1, n do aug[i][n + j] = { (i == j) and 1 or 0, 0, 0, 0 } end
    end
    local det = { 1, 0, 0, 0 }
    for col = 1, n do
        local pivot_row = col
        local max_val = q_abs(aug[col][col])
        for r = col + 1, n do
            local v = q_abs(aug[r][col])
            if v > max_val then max_val = v; pivot_row = r end
        end
        if max_val <= 1e-11 then
            return nil, { 0, 0, 0, 0 }
        end
        if pivot_row ~= col then
            aug[col], aug[pivot_row] = aug[pivot_row], aug[col]
            det = q_neg(det)
        end
        local piv = aug[col][col]
        det = q_mul(det, piv)
        local inv_piv = q_inv(piv)
        -- 嚴格左乘 inv_piv
        for j = col, 2 * n do aug[col][j] = q_mul(inv_piv, aug[col][j]) end
        for r = 1, n do
            if r ~= col then
                local factor = aug[r][col]
                if q_abs_sq(factor) > 0 then
                    for j = col, 2 * n do
                        -- 嚴格左乘 factor: Row_r = Row_r - factor * Row_col
                        aug[r][j] = q_sub(aug[r][j], q_mul(factor, aug[col][j]))
                    end
                end
            end
        end
    end
    local inv = {}
    for i = 1, n do
        inv[i] = {}
        for j = 1, n do inv[i][j] = aug[i][n + j] end
    end
    return inv, det
end

local function raw_transpose(A, m, n)
    local T = {}
    for j = 1, n do
        T[j] = {}
        for i = 1, m do T[j][i] = A[i][j] end
    end
    return T
end

local function raw_eye(n)
    local I = {}
    for i = 1, n do
        I[i] = {}
        for j = 1, n do I[i][j] = (i == j) and 1 or 0 end
    end
    return I
end

-- Hestenes 單邊雅可比奇異值分解 (One-Sided Jacobi SVD) 與 Moore-Penrose 偽反矩陣
local function raw_svd_and_pinv(mat, m, n)
    if m < n then
        local matT = raw_transpose(mat, m, n)
        local pinvT, U_t, S_t, V_t, rank = raw_svd_and_pinv(matT, n, m)
        return raw_transpose(pinvT, m, n), V_t, S_t, U_t, rank
    end

    local W = {}
    for i = 1, m do
        W[i] = {}
        for j = 1, n do W[i][j] = mat[i][j] end
    end
    local V = raw_eye(n)

    for _ = 1, 40 do
        local converged = true
        for p = 1, n - 1 do
            for q = p + 1, n do
                local alpha, beta, gamma = 0, 0, 0
                for i = 1, m do
                    local wp, wq = W[i][p], W[i][q]
                    alpha = alpha + wp * wp
                    beta = beta + wq * wq
                    gamma = gamma + wp * wq
                end
                if alpha < beta then
                    for i = 1, m do W[i][p], W[i][q] = W[i][q], W[i][p] end
                    for i = 1, n do V[i][p], V[i][q] = V[i][q], V[i][p] end
                    alpha, beta = beta, alpha
                end
                if math.abs(gamma) > 1e-14 * math.sqrt(alpha * beta) and alpha > 1e-24 then
                    converged = false
                    local tau = (beta - alpha) / (2 * gamma)
                    local sign_tau = (tau >= 0) and 1 or -1
                    local t = sign_tau / (math.abs(tau) + math.sqrt(1 + tau * tau))
                    local c = 1 / math.sqrt(1 + t * t)
                    local s = c * t
                    for i = 1, m do
                        local wp, wq = W[i][p], W[i][q]
                        W[i][p] = c * wp - s * wq
                        W[i][q] = s * wp + c * wq
                    end
                    for i = 1, n do
                        local vp, vq = V[i][p], V[i][q]
                        V[i][p] = c * vp - s * vq
                        V[i][q] = s * vp + c * vq
                    end
                end
            end
        end
        if converged then break end
    end

    local S = {}
    local max_s = 0
    for j = 1, n do
        local norm_sq = 0
        for i = 1, m do norm_sq = norm_sq + W[i][j] * W[i][j] end
        S[j] = math.sqrt(norm_sq)
        if S[j] > max_s then max_s = S[j] end
    end

    local tol = math.max(m, n) * max_s * 1e-11
    local U = {}
    for i = 1, m do U[i] = {} end
    local inv_S = {}
    local rank = 0

    for j = 1, n do
        if S[j] > tol then
            rank = rank + 1
            inv_S[j] = 1 / S[j]
            for i = 1, m do U[i][j] = W[i][j] / S[j] end
        else
            inv_S[j] = 0
            for i = 1, m do U[i][j] = 0 end
        end
    end

    local pinv = {}
    for i = 1, n do
        pinv[i] = {}
        for j = 1, m do
            local sum = 0
            for k = 1, n do
                if inv_S[k] ~= 0 then
                    sum = sum + V[i][k] * inv_S[k] * U[j][k]
                end
            end
            pinv[i][j] = sum
        end
    end

    return pinv, U, S, V, rank
end

local function q2d_inv_or_pinv(mat, m, n)
    if m == n then
        local inv = q2d_gauss_jordan(mat, n)
        if inv then return inv end
    end
    if q2d_has_quaternions(mat, m, n) then
        raise_err("NotImplementedError", "Moore-Penrose pseudo-inverse via SVD is not supported for non-square or singular quaternionic matrices")
    end
    local rmat = {}
    for i = 1, m do
        rmat[i] = {}
        for j = 1, n do rmat[i][j] = mat[i][j][1] end
    end
    local rpinv = raw_svd_and_pinv(rmat, m, n)
    local qpinv = {}
    for i = 1, n do
        qpinv[i] = {}
        for j = 1, m do qpinv[i][j] = { rpinv[i][j], 0, 0, 0 } end
    end
    return qpinv
end

-- Denman-Beavers 縮放迭代法求方陣平方根 A^(1/2)
local function q2d_sqrtm(mat, n)
    if n == 1 then return { { q_sqrt(mat[1][1]) } } end
    if q2d_is_diagonal(mat, n) then
        local D = q2d_eye(n)
        for i = 1, n do D[i][i] = q_sqrt(mat[i][i]) end
        return D
    end

    local Y = q2d_copy(mat, n, n)
    local Z = q2d_eye(n)

    for _ = 1, 35 do
        local invY, detY = q2d_gauss_jordan(Y, n)
        local invZ, detZ = q2d_gauss_jordan(Z, n)
        if not invY or not invZ then
            invY = q2d_inv_or_pinv(Y, n, n)
            invZ = q2d_inv_or_pinv(Z, n, n)
            detY, detZ = { 1, 0, 0, 0 }, { 1, 0, 0, 0 }
        end
        local prod_det = q_abs(q_mul(detY, detZ))
        local gamma = 1
        if prod_det > 1e-20 then
            gamma = prod_det ^ (-1 / (2 * n))
            if gamma < 0.1 then gamma = 0.1 elseif gamma > 10 then gamma = 10 end
        end
        local inv_gamma = 1 / gamma
        local diff_sq, norm_sq = 0, 0
        for i = 1, n do
            for j = 1, n do
                local yij, zij = Y[i][j], Z[i][j]
                local iz, iy = invZ[i][j], invY[i][j]
                local ny = {
                    0.5 * (gamma * yij[1] + inv_gamma * iz[1]),
                    0.5 * (gamma * yij[2] + inv_gamma * iz[2]),
                    0.5 * (gamma * yij[3] + inv_gamma * iz[3]),
                    0.5 * (gamma * yij[4] + inv_gamma * iz[4])
                }
                local nz = {
                    0.5 * (gamma * zij[1] + inv_gamma * iy[1]),
                    0.5 * (gamma * zij[2] + inv_gamma * iy[2]),
                    0.5 * (gamma * zij[3] + inv_gamma * iy[3]),
                    0.5 * (gamma * zij[4] + inv_gamma * iy[4])
                }
                diff_sq = diff_sq + q_abs_sq(q_sub(ny, yij))
                norm_sq = norm_sq + q_abs_sq(ny)
                Y[i][j] = ny
                Z[i][j] = nz
            end
        end
        if math.sqrt(diff_sq) <= 1e-12 * math.max(1, math.sqrt(norm_sq)) then break end
    end
    return Y
end

-- 逆縮放平方算法求矩陣主對數 logm(A)
local function q2d_logm(mat, n)
    if n == 1 then return { { q_log(mat[1][1]) } } end
    if q2d_is_diagonal(mat, n) then
        local D = q2d_eye(n)
        for i = 1, n do D[i][i] = q_log(mat[i][i]) end
        return D
    end

    local _, det = q2d_gauss_jordan(mat, n)
    if q_abs(det) <= 1e-12 then
        raise_err("ValueError", "math domain error: matrix logarithm is undefined for singular matrix")
    end

    local A = q2d_copy(mat, n, n)
    local s = 0
    local function dist_from_I(M)
        local d = 0
        for i = 1, n do
            for j = 1, n do
                local cell = M[i][j]
                local dr = cell[1] - ((i == j) and 1 or 0)
                d = d + dr*dr + cell[2]*cell[2] + cell[3]*cell[3] + cell[4]*cell[4]
            end
        end
        return math.sqrt(d)
    end

    while dist_from_I(A) > 0.18 and s < 8 do
        A = q2d_sqrtm(A, n)
        s = s + 1
    end

    local X, term, L = {}, {}, {}
    for i = 1, n do
        X[i], term[i], L[i] = {}, {}, {}
        for j = 1, n do
            local cell = A[i][j]
            local xij = { cell[1] - ((i == j) and 1 or 0), cell[2], cell[3], cell[4] }
            X[i][j] = xij
            term[i][j] = { xij[1], xij[2], xij[3], xij[4] }
            L[i][j] = { xij[1], xij[2], xij[3], xij[4] }
        end
    end

    for k = 2, 20 do
        term = q2d_matmul(term, X, n, n, n)
        local coeff = ((k % 2 == 1) and 1 or -1) / k
        local max_add = 0
        for i = 1, n do
            for j = 1, n do
                local add_q = q_scale(term[i][j], coeff)
                L[i][j] = q_add(L[i][j], add_q)
                local mag = q_abs(add_q)
                if mag > max_add then max_add = mag end
            end
        end
        if max_add < 1e-14 then break end
    end

    local scale = 2 ^ s
    for i = 1, n do
        for j = 1, n do L[i][j] = q_scale(L[i][j], scale) end
    end
    return L
end

-- 縮放平方算法求矩陣指數 expm(A)
local function q2d_expm(mat, n)
    if n == 1 then return { { q_exp(mat[1][1]) } } end
    if q2d_is_diagonal(mat, n) then
        local D = q2d_eye(n)
        for i = 1, n do D[i][i] = q_exp(mat[i][i]) end
        return D
    end

    local norm = q2d_frobenius_norm(mat, n, n)
    local k = 0
    while norm / (2 ^ k) > 0.25 do k = k + 1 end
    local scale = 1 / (2 ^ k)

    local B = {}
    for i = 1, n do
        B[i] = {}
        for j = 1, n do B[i][j] = q_scale(mat[i][j], scale) end
    end

    local E = q2d_eye(n)
    local term = q2d_eye(n)
    for step = 1, 20 do
        term = q2d_matmul(term, B, n, n, n)
        local max_add = 0
        for i = 1, n do
            for j = 1, n do
                term[i][j] = q_scale(term[i][j], 1 / step)
                E[i][j] = q_add(E[i][j], term[i][j])
                local mag = q_abs(term[i][j])
                if mag > max_add then max_add = mag end
            end
        end
        if max_add < 1e-14 then break end
    end

    for _ = 1, k do
        E = q2d_matmul(E, E, n, n, n)
    end
    return E
end

-- ==========================================
-- 泛函微積分:三角函數、雙曲函數與反函數 (支援實數、複數與四元數矩陣)
-- ==========================================
local function ensure_square_q2d(obj, func_name, forbid_hyper)
    local mA, m, n = Matrix.to_q2d(obj, forbid_hyper)
    if m ~= n then
        raise_err("ValueError", func_name .. "() requires a square 2D matrix")
    end
    return mA, n
end

-- 對含有 k, jk 的非交換四元數矩陣使用實係數冪級數計算 sinm 與 cosm (避免 1j 與 1k 不交換問題)
local function q2d_sinm_cosm_series(mA, n)
    local norm = q2d_frobenius_norm(mA, n, n)
    local k = 0
    while norm / (2 ^ k) > 0.5 do k = k + 1 end
    local X = q2d_scale_left({ 1 / (2 ^ k), 0, 0, 0 }, mA, n, n)
    local X2 = q2d_matmul(X, X, n, n, n)

    local S = q2d_copy(X, n, n)
    local C = q2d_eye(n)
    local termS = q2d_copy(X, n, n)
    local termC = q2d_eye(n)

    for m = 1, 12 do
        termC = q2d_matmul(termC, X2, n, n, n)
        termC = q2d_scale_left({ -1 / ((2*m - 1) * (2*m)), 0, 0, 0 }, termC, n, n)
        C = q2d_add(C, termC, n, n, 1)

        termS = q2d_matmul(termS, X2, n, n, n)
        termS = q2d_scale_left({ -1 / ((2*m) * (2*m + 1)), 0, 0, 0 }, termS, n, n)
        S = q2d_add(S, termS, n, n, 1)
    end

    for _ = 1, k do
        -- sin(2X) = 2 sin(X) cos(X), cos(2X) = cos^2(X) - sin^2(X)
        local nextS = q2d_scale_left({ 2, 0, 0, 0 }, q2d_matmul(S, C, n, n, n), n, n)
        local nextC = q2d_add(q2d_matmul(C, C, n, n, n), q2d_matmul(S, S, n, n, n), n, n, -1)
        S, C = nextS, nextC
    end
    return S, C
end

-- sin(M) = (e^(jM) - e^(-jM)) / 2j (對角或四元數矩陣自動派發)
local function q2d_sinm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_sin(mA[i][i]) end; return D
    end
    if q2d_has_quaternions(mA, n, n) then
        local S, _ = q2d_sinm_cosm_series(mA, n); return S
    end
    local Ep = q2d_expm(q2d_scale_left({ 0, 1, 0, 0 }, mA, n, n), n)
    local Em = q2d_expm(q2d_scale_left({ 0, -1, 0, 0 }, mA, n, n), n)
    return q2d_scale_right(q2d_add(Ep, Em, n, n, -1), n, n, { 0, -0.5, 0, 0 })
end

-- cos(M) = (e^(jM) + e^(-jM)) / 2
local function q2d_cosm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_cos(mA[i][i]) end; return D
    end
    if q2d_has_quaternions(mA, n, n) then
        local _, C = q2d_sinm_cosm_series(mA, n); return C
    end
    local Ep = q2d_expm(q2d_scale_left({ 0, 1, 0, 0 }, mA, n, n), n)
    local Em = q2d_expm(q2d_scale_left({ 0, -1, 0, 0 }, mA, n, n), n)
    return q2d_scale_right(q2d_add(Ep, Em, n, n, 1), n, n, { 0.5, 0, 0, 0 })
end

-- tan(M) = sin(M) * inv(cos(M))
local function q2d_tanm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_tan(mA[i][i]) end; return D
    end
    local S = q2d_sinm(mA, n)
    local C = q2d_cosm(mA, n)
    return q2d_matmul(S, q2d_inv_or_pinv(C, n, n), n, n, n)
end

-- sinh(M) = (e^M - e^(-M)) / 2
local function q2d_sinhm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_sinh(mA[i][i]) end; return D
    end
    local Ep = q2d_expm(mA, n)
    local Em = q2d_expm(q2d_scale_left({ -1, 0, 0, 0 }, mA, n, n), n)
    return q2d_scale_right(q2d_add(Ep, Em, n, n, -1), n, n, { 0.5, 0, 0, 0 })
end

-- cosh(M) = (e^M + e^(-M)) / 2
local function q2d_coshm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_cosh(mA[i][i]) end; return D
    end
    local Ep = q2d_expm(mA, n)
    local Em = q2d_expm(q2d_scale_left({ -1, 0, 0, 0 }, mA, n, n), n)
    return q2d_scale_right(q2d_add(Ep, Em, n, n, 1), n, n, { 0.5, 0, 0, 0 })
end

-- tanh(M) = sinh(M) * inv(cosh(M))
local function q2d_tanhm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_tanh(mA[i][i]) end; return D
    end
    local S = q2d_sinhm(mA, n)
    local C = q2d_coshm(mA, n)
    return q2d_matmul(S, q2d_inv_or_pinv(C, n, n), n, n, n)
end

-- asinh(M) = ln(M + sqrt(M^2 + I))
local function q2d_asinhm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_asinh(mA[i][i]) end; return D
    end
    local I = q2d_eye(n)
    local M2 = q2d_matmul(mA, mA, n, n, n)
    local sq = q2d_sqrtm(q2d_add(M2, I, n, n, 1), n)
    return q2d_logm(q2d_add(mA, sq, n, n, 1), n)
end

-- acosh(M) = ln(M + sqrt(M^2 - I))
local function q2d_acoshm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_acosh(mA[i][i]) end; return D
    end
    local I = q2d_eye(n)
    local M2 = q2d_matmul(mA, mA, n, n, n)
    local sq = q2d_sqrtm(q2d_add(M2, I, n, n, -1), n)
    return q2d_logm(q2d_add(mA, sq, n, n, 1), n)
end

-- atanh(M) = 0.5 * (ln(I + M) - ln(I - M))
local function q2d_atanhm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_atanh(mA[i][i]) end; return D
    end
    local I = q2d_eye(n)
    local num = q2d_add(I, mA, n, n, 1)
    local den = q2d_add(I, mA, n, n, -1)
    local q = q2d_matmul(num, q2d_inv_or_pinv(den, n, n), n, n, n)
    return q2d_scale_left({ 0.5, 0, 0, 0 }, q2d_logm(q, n), n, n)
end

-- asin(M) = -j * ln(jM + sqrt(I - M^2))
local function q2d_asinm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_asin(mA[i][i]) end; return D
    end
    local I = q2d_eye(n)
    local M2 = q2d_matmul(mA, mA, n, n, n)
    local sq = q2d_sqrtm(q2d_add(I, M2, n, n, -1), n)
    local jM = q2d_scale_left({ 0, 1, 0, 0 }, mA, n, n)
    local L = q2d_logm(q2d_add(jM, sq, n, n, 1), n)
    return q2d_scale_left({ 0, -1, 0, 0 }, L, n, n)
end

-- acos(M) = (pi/2)*I - asin(M)
local function q2d_acosm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_acos(mA[i][i]) end; return D
    end
    local pi_half_I = q2d_scale_left({ math.pi / 2, 0, 0, 0 }, q2d_eye(n), n, n)
    return q2d_add(pi_half_I, q2d_asinm(mA, n), n, n, -1)
end

-- atan(M) = (j/2) * ln((I - jM) * inv(I + jM))
local function q2d_atanm(mA, n)
    if q2d_is_diagonal(mA, n) then
        local D = q2d_eye(n); for i = 1, n do D[i][i] = q_atan(mA[i][i]) end; return D
    end
    local I = q2d_eye(n)
    local jM = q2d_scale_left({ 0, 1, 0, 0 }, mA, n, n)
    local num = q2d_add(I, jM, n, n, -1)
    local den = q2d_add(I, jM, n, n, 1)
    local q = q2d_matmul(num, q2d_inv_or_pinv(den, n, n), n, n, n)
    return q2d_scale_left({ 0, 0.5, 0, 0 }, q2d_logm(q, n), n, n)
end

-- ==========================================
-- Matrix 對外核心方法 (支援雙軌制:精確代數 vs 超複數泛函)
-- ==========================================
function Matrix.matmul(A, B)
    -- 若 A 與 B 皆為純量超複數/四元數,轉發至四元數外積 p @ q
    if not is_matrix_obj(A) and not is_matrix_obj(B) and (type(A) ~= "table" or (A.__type ~= "list" and A.__type ~= "tuple")) and (type(B) ~= "table" or (B.__type ~= "list" and B.__type ~= "tuple")) then
        return Cmath.cross(A, B)
    end

    local shapeA = Matrix.get_shape(A)
    local shapeB = Matrix.get_shape(B)
    if not shapeA or not shapeB then raise_err("ValueError", "invalid matrix shape for @") end

    local exact = has_exact_algebraic_elements(A) or has_exact_algebraic_elements(B)

    if #shapeA == 2 and #shapeB == 2 then
        if shapeA[2] ~= shapeB[1] then raise_err("ValueError", "shapes not aligned for matmul") end
        local m, k, n = shapeA[1], shapeA[2], shapeB[2]
        if exact then
            local rows = {}
            for i = 1, m do
                local r = {}
                local rowA = A.elements[i].elements
                for j = 1, n do
                    local sum = py_exact_binop("*", rowA[1], B.elements[1].elements[j])
                    for p = 2, k do
                        sum = py_exact_binop("+", sum, py_exact_binop("*", rowA[p], B.elements[p].elements[j]))
                    end
                    r[j] = clean_zero(sum)
                end
                rows[i] = { __type = "list", elements = r, is_matrix = true, shape = {n} }
            end
            return { __type = "list", elements = rows, is_matrix = true, shape = {m, n} }
        else
            local mA = Matrix.to_q2d(A, false)
            local mB = Matrix.to_q2d(B, false)
            return Matrix.from_q2d(q2d_matmul(mA, mB, m, k, n), m, n)
        end
    elseif #shapeA == 1 and #shapeB == 2 then
        if shapeA[1] ~= shapeB[1] then raise_err("ValueError", "shapes not aligned") end
        local res = { __type = "list", elements = {}, is_matrix = true, shape = {shapeB[2]} }
        for j = 1, shapeB[2] do
            local sum = py_exact_binop("*", A.elements[1], B.elements[1].elements[j])
            for k = 2, shapeA[1] do
                sum = py_exact_binop("+", sum, py_exact_binop("*", A.elements[k], B.elements[k].elements[j]))
            end
            table.insert(res.elements, clean_zero(sum))
        end
        return res
    elseif #shapeA == 2 and #shapeB == 1 then
        if shapeA[2] ~= shapeB[1] then raise_err("ValueError", "shapes not aligned") end
        local res = { __type = "list", elements = {}, is_matrix = true, shape = {shapeA[1]} }
        for i = 1, shapeA[1] do
            local sum = py_exact_binop("*", A.elements[i].elements[1], B.elements[1])
            for k = 2, shapeA[2] do
                sum = py_exact_binop("+", sum, py_exact_binop("*", A.elements[i].elements[k], B.elements[k]))
            end
            table.insert(res.elements, clean_zero(sum))
        end
        return res
    elseif #shapeA == 1 and #shapeB == 1 then
        if shapeA[1] ~= shapeB[1] then raise_err("ValueError", "shapes not aligned") end
        local sum = py_exact_binop("*", A.elements[1], B.elements[1])
        for i = 2, shapeA[1] do
            sum = py_exact_binop("+", sum, py_exact_binop("*", A.elements[i], B.elements[i]))
        end
        return clean_zero(sum)
    else
        raise_err("NotImplementedError", "matmul for >2D or 0D arrays is not supported")
    end
end

function Matrix.ldiv(A, B)
    local is_list_a = (type(A) == "table" and (A.__type == "list" or A.__type == "tuple"))
    local is_list_b = (type(B) == "table" and (B.__type == "list" or B.__type == "tuple"))

    if not is_list_a and is_list_b then
        return Matrix.elementwise_op(B, A, "/")
    elseif is_list_a and not is_list_b then
        local shapeA = Matrix.get_shape(A)
        if shapeA and #shapeA == 2 then
            local mA, m, n = Matrix.to_q2d(A, false)
            local invA = Matrix.from_q2d(q2d_inv_or_pinv(mA, m, n), n, m)
            if B == 1 then return invA end
            return Matrix.elementwise_op(invA, B, "*")
        end
        return Matrix.elementwise_op(B, A, "/")
    elseif is_list_a and is_list_b then
        local shapeA = Matrix.get_shape(A)
        local shapeB = Matrix.get_shape(B)
        if shapeA and #shapeA == 2 then
            if not shapeB or (#shapeB ~= 1 and #shapeB ~= 2) then
                raise_err("TypeError", "left division (\\) requires a 1D vector or 2D matrix on the right")
            end
            if shapeA[1] ~= shapeB[1] then
                raise_err("ValueError", "matrix dimensions not aligned for left division (\\)")
            end
            local mA, m, n = Matrix.to_q2d(A, false)
            local pinvA = Matrix.from_q2d(q2d_inv_or_pinv(mA, m, n), n, m)
            return Matrix.matmul(pinvA, B)
        end
        return Matrix.elementwise_op(B, A, "/")
    end
    return py_exact_binop("/", B, A)
end

function Matrix.rdiv(A, B)
    local is_list_a = (type(A) == "table" and (A.__type == "list" or A.__type == "tuple"))
    local is_list_b = (type(B) == "table" and (B.__type == "list" or B.__type == "tuple"))

    if is_list_a and not is_list_b then
        return Matrix.elementwise_op(A, B, "/")
    elseif not is_list_a and is_list_b then
        local shapeB = Matrix.get_shape(B)
        if shapeB and #shapeB == 2 then
            local mB, m, n = Matrix.to_q2d(B, false)
            local invB = Matrix.from_q2d(q2d_inv_or_pinv(mB, m, n), n, m)
            if A == 1 then return invB end
            return Matrix.elementwise_op(A, invB, "*")
        end
        return Matrix.elementwise_op(A, B, "/")
    elseif is_list_a and is_list_b then
        local shapeA = Matrix.get_shape(A)
        local shapeB = Matrix.get_shape(B)
        if shapeB and #shapeB == 2 then
            local colsA = (#shapeA == 2) and shapeA[2] or shapeA[1]
            if colsA ~= shapeB[2] then
                raise_err("ValueError", "matrix dimensions not aligned for right division (/)")
            end
            local mB, m, n = Matrix.to_q2d(B, false)
            local pinvB = Matrix.from_q2d(q2d_inv_or_pinv(mB, m, n), n, m)
            return Matrix.matmul(A, pinvB)
        end
        return Matrix.elementwise_op(A, B, "/")
    end
    return py_exact_binop("/", A, B)
end

function Matrix.pow(A, B)
    local shapeA = Matrix.get_shape(A)
    local shapeB = Matrix.get_shape(B)
    local dimA = shapeA and #shapeA or -1
    local dimB = shapeB and #shapeB or -1

    -- [1] 2D 矩陣/陣列 ** 2D 矩陣/陣列 -> A^B = expm(B @ logm(A))
    if dimA == 2 and dimB == 2 then
        local mA, m1, n1 = Matrix.to_q2d(A, false)
        local mB, m2, n2 = Matrix.to_q2d(B, false)
        if m1 ~= n1 then
            raise_err("ValueError", "base matrix in M ** N must be a square 2D matrix for logm()")
        end
        if n2 ~= m1 then
            raise_err("ValueError", string.format("shapes (%d,%d) and (%d,%d) not aligned for M ** N = exp(N @ ln(M))", m2, n2, m1, n1))
        end
        if m2 ~= n1 then
            raise_err("ValueError", "exponent matrix N in M ** N must produce a square matrix for expm()")
        end
        local logA = q2d_logm(mA, n1)
        local prod = q2d_matmul(mB, logA, n1, n1, n1)
        return Matrix.from_q2d(q2d_expm(prod, n1), n1, n1)

    -- [2] 純量 c ** 2D 矩陣 M -> c^M = expm(M * ln(c))
    elseif dimA == 0 and dimB == 2 then
        local mB, m, n = Matrix.to_q2d(B, false)
        if m ~= n then
            raise_err("ValueError", "matrix exponent in c ** M requires a square 2D matrix")
        end
        local c_q4 = to_q4(A, false)
        local ln_c = q_log(c_q4)
        local scaled = q2d_scale_right(mB, n, n, ln_c)
        return Matrix.from_q2d(q2d_expm(scaled, n), n, n)

    -- [3] 2D 矩陣 M ** 純量 p
    elseif dimA == 2 and dimB == 0 then
        local p_q4 = to_q4(B, false)
        local is_real_p = (p_q4[2] == 0 and p_q4[3] == 0 and p_q4[4] == 0)
        local exp_r = p_q4[1]

        local m, n = shapeA[1], shapeA[2]
        if m ~= n then
            if is_real_p and exp_r == -1 then
                local mA = Matrix.to_q2d(A, false)
                return Matrix.from_q2d(q2d_inv_or_pinv(mA, m, n), n, m)
            end
            raise_err("ValueError", "matrix power (other than -1 for pseudo-inverse) requires a square 2D matrix")
        end

        if is_real_p and exp_r == 0 then
            return Matrix.from_lua_2d(raw_eye(n), n, n)
        end

        -- 軌道一:正整數次方且含精確型別 (Fraction / Decimal / BigInt) 時,使用精確快速冪
        if is_real_p and exp_r >= 1 and math.floor(exp_r) == exp_r and has_exact_algebraic_elements(A) then
            local k = math.floor(exp_r)
            local res = Matrix.from_lua_2d(raw_eye(n), n, n)
            local base = Matrix.as_matrix(A)
            while k > 0 do
                if k % 2 == 1 then res = Matrix.matmul(res, base) end
                k = math.floor(k / 2)
                if k > 0 then base = Matrix.matmul(base, base) end
            end
            return res
        end

        local mA = Matrix.to_q2d(A, false)
        if is_real_p and math.floor(exp_r) == exp_r then
            local k = math.floor(exp_r)
            if k < 0 then
                mA = q2d_inv_or_pinv(mA, n, n)
                k = -k
            end
            local res = q2d_eye(n)
            local base = mA
            while k > 0 do
                if k % 2 == 1 then res = q2d_matmul(res, base, n, n, n) end
                k = math.floor(k / 2)
                if k > 0 then base = q2d_matmul(base, base, n, n, n) end
            end
            return Matrix.from_q2d(res, n, n)
        end

        if is_real_p and math.abs(exp_r - 0.5) < 1e-12 then
            return Matrix.from_q2d(q2d_sqrtm(mA, n), n, n)
        end

        -- 一般超複數/小數次方:A^p = expm(p * logm(A))
        local L = q2d_logm(mA, n)
        return Matrix.from_q2d(q2d_expm(q2d_scale_left(p_q4, L, n, n), n), n, n)

    else
        return Matrix.elementwise_op(A, B, "**")
    end
end

function Matrix.transpose(A)
    local shapeA = Matrix.get_shape(A)
    if not shapeA or #shapeA == 0 then return A end
    if #shapeA == 1 then
        return { __type = "list", elements = A.elements, is_matrix = true, shape = shapeA }
    elseif #shapeA == 2 then
        local m, n = shapeA[1], shapeA[2]
        local rows = {}
        for j = 1, n do
            local r = {}
            for i = 1, m do r[i] = A.elements[i].elements[j] end
            rows[j] = { __type = "list", elements = r, is_matrix = true, shape = {m} }
        end
        return { __type = "list", elements = rows, is_matrix = true, shape = {n, m} }
    end
    raise_err("NotImplementedError", "transpose is only supported for 1D/2D matrices")
end

function Matrix.inv(A)
    local mA, m, n = Matrix.to_q2d(A, false)
    if m ~= n then raise_err("ValueError", "inv() requires a square matrix; use pinv() or .I for non-square matrices") end
    local inv = q2d_gauss_jordan(mA, n)
    if not inv then raise_err("ZeroDivisionError", "Singular matrix") end
    return Matrix.from_q2d(inv, n, n)
end

function Matrix.pinv(A)
    local mA, m, n = Matrix.to_q2d(A, false)
    return Matrix.from_q2d(q2d_inv_or_pinv(mA, m, n), n, m)
end

function Matrix.det(A)
    local mA, m, n = Matrix.to_q2d(A, false)
    if m ~= n then raise_err("ValueError", "det() requires a square 2D matrix") end
    local _, d = q2d_gauss_jordan(mA, n)
    return from_q4(d, false)
end

function Matrix.trace(A)
    local shapeA = Matrix.get_shape(A)
    if not shapeA or #shapeA ~= 2 then raise_err("ValueError", "trace() requires a 2D matrix") end
    local k = math.min(shapeA[1], shapeA[2])
    if k == 0 then return 0 end
    local s = A.elements[1].elements[1]
    for i = 2, k do
        s = py_exact_binop("+", s, A.elements[i].elements[i])
    end
    return clean_zero(s)
end

function Matrix.rank(A)
    local mA, m, n = Matrix.to_lua_2d(A)
    local _, _, _, _, r = raw_svd_and_pinv(mA, m, n)
    return r
end

function Matrix.svd(A)
    local mA, m, n = Matrix.to_lua_2d(A)
    local _, U, S, V = raw_svd_and_pinv(mA, m, n)
    local k = math.min(m, n)
    local s_elts = {}
    for i = 1, k do s_elts[i] = clean_zero(S[i]) end
    return {
        __type = "tuple",
        elements = {
            Matrix.from_lua_2d(U, m, k),
            { __type = "list", elements = s_elts, is_matrix = true, shape = {k} },
            Matrix.from_lua_2d(raw_transpose(V, n, k), k, n)
        }
    }
end

function Matrix.frobenius_norm(A, forbid_hyper)
    local sum_sq = 0
    local function accum(node)
        if type(node) == "table" and (node.__type == "list" or node.__type == "tuple") then
            for _, el in ipairs(node.elements) do accum(el) end
        else
            sum_sq = sum_sq + q_abs_sq(to_q4(node, forbid_hyper))
        end
    end
    accum(A)
    return clean_zero(math.sqrt(sum_sq))
end

function Matrix.expm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "exp", forbid_hyper)
    return Matrix.from_q2d(q2d_expm(mA, n), n, n)
end

function Matrix.expm1(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "expm1", forbid_hyper)
    local E = q2d_expm(mA, n)
    return Matrix.from_q2d(q2d_add(E, q2d_eye(n), n, n, -1), n, n)
end

function Matrix.logm(A, base, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "log", forbid_hyper)
    local LA = Matrix.from_q2d(q2d_logm(mA, n), n, n)
    if base == nil then return LA end
    if type(base) == "table" and (base.__type == "list" or base.__type == "tuple") then
        local mB, nb = ensure_square_q2d(base, "log", forbid_hyper)
        local LB = Matrix.from_q2d(q2d_logm(mB, nb), nb, nb)
        return Matrix.rdiv(LA, LB)
    else
        local bp = to_q4(base, forbid_hyper)
        local lnb = from_q4(q_log(bp), false)
        return Matrix.rdiv(LA, lnb)
    end
end

function Matrix.log1p(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "log1p", forbid_hyper)
    local IpA = q2d_add(q2d_eye(n), mA, n, n, 1)
    return Matrix.from_q2d(q2d_logm(IpA, n), n, n)
end

function Matrix.sqrtm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "sqrt", forbid_hyper)
    return Matrix.from_q2d(q2d_sqrtm(mA, n), n, n)
end

function Matrix.cbrtm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "cbrt", forbid_hyper)
    local L = q2d_logm(mA, n)
    return Matrix.from_q2d(q2d_expm(q2d_scale_left({ 1/3, 0, 0, 0 }, L, n, n), n), n, n)
end

function Matrix.sinm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "sin", forbid_hyper)
    return Matrix.from_q2d(q2d_sinm(mA, n), n, n)
end

function Matrix.cosm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "cos", forbid_hyper)
    return Matrix.from_q2d(q2d_cosm(mA, n), n, n)
end

function Matrix.tanm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "tan", forbid_hyper)
    return Matrix.from_q2d(q2d_tanm(mA, n), n, n)
end

function Matrix.sinhm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "sinh", forbid_hyper)
    return Matrix.from_q2d(q2d_sinhm(mA, n), n, n)
end

function Matrix.coshm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "cosh", forbid_hyper)
    return Matrix.from_q2d(q2d_coshm(mA, n), n, n)
end

function Matrix.tanhm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "tanh", forbid_hyper)
    return Matrix.from_q2d(q2d_tanhm(mA, n), n, n)
end

function Matrix.asinm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "asin", forbid_hyper)
    return Matrix.from_q2d(q2d_asinm(mA, n), n, n)
end

function Matrix.acosm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "acos", forbid_hyper)
    return Matrix.from_q2d(q2d_acosm(mA, n), n, n)
end

function Matrix.atanm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "atan", forbid_hyper)
    return Matrix.from_q2d(q2d_atanm(mA, n), n, n)
end

function Matrix.asinhm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "asinh", forbid_hyper)
    return Matrix.from_q2d(q2d_asinhm(mA, n), n, n)
end

function Matrix.acoshm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "acosh", forbid_hyper)
    return Matrix.from_q2d(q2d_acoshm(mA, n), n, n)
end

function Matrix.atanhm(A, forbid_hyper)
    local mA, n = ensure_square_q2d(A, "atanh", forbid_hyper)
    return Matrix.from_q2d(q2d_atanhm(mA, n), n, n)
end

MathCore.Matrix = Matrix

MathCore.linalg = {
    matrix    = function(args) return Matrix.as_matrix(args[1]) end,
    inv       = function(args) return Matrix.inv(args[1]) end,
    pinv      = function(args) return Matrix.pinv(args[1]) end,
    det       = function(args) return Matrix.det(args[1]) end,
    trace     = function(args) return Matrix.trace(args[1]) end,
    rank      = function(args) return Matrix.rank(args[1]) end,
    svd       = function(args) return Matrix.svd(args[1]) end,
    norm      = function(args) return Matrix.frobenius_norm(args[1], false) end,
    transpose = function(args) return Matrix.transpose(args[1]) end,
    solve     = function(args) return Matrix.ldiv(args[1], args[2]) end,
    mpower    = function(args) return Matrix.pow(args[1], args[2]) end,
    sqrtm     = function(args) return Matrix.sqrtm(args[1], false) end,
    expm      = function(args) return Matrix.expm(args[1], false) end,
    logm      = function(args) return Matrix.logm(args[1], args[2], false) end,
    sinm      = function(args) return Matrix.sinm(args[1], false) end,
    cosm      = function(args) return Matrix.cosm(args[1], false) end,
    tanm      = function(args) return Matrix.tanm(args[1], false) end,
    sinhm     = function(args) return Matrix.sinhm(args[1], false) end,
    coshm     = function(args) return Matrix.coshm(args[1], false) end,
    tanhm     = function(args) return Matrix.tanhm(args[1], false) end,
    asinm     = function(args) return Matrix.asinm(args[1], false) end,
    acosm     = function(args) return Matrix.acosm(args[1], false) end,
    atanm     = function(args) return Matrix.atanm(args[1], false) end,
    asinhm    = function(args) return Matrix.asinhm(args[1], false) end,
    acoshm    = function(args) return Matrix.acoshm(args[1], false) end,
    atanhm    = function(args) return Matrix.atanhm(args[1], false) end,
    dot       = function(args) return Cmath.dot(args[1], args[2]) end,
    cross     = function(args) return Cmath.cross(args[1], args[2]) end,
    eye       = function(args)
        local dim = require("Module:PythonScript/Runtime/Utils").unwrap_num(args[1])
        return Matrix.from_lua_2d(raw_eye(dim), dim, dim)
    end
}

function MathCore.get_math_module()
    return require("Module:PythonScript/Librarys/Cmath").MathLib.init()
end

function MathCore.get_cmath_module()
    return require("Module:PythonScript/Librarys/Cmath").CmathLib.init()
end

return MathCore