%D \module
%D   [       file=m-matrix,
%D        version=2014.11.04, % already a year older
%D          title=\CONTEXT\ Extra Modules,
%D       subtitle=Matrices,
%D         author={Jeong Dalyoung \& Hans Hagen},
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D This code is based on a post by Dalyoung on the context list. After that
%D we turned it into a module and improved the code a bit. Feel free to ask
%D us for more. Once we're satisfied, a more general helper l-matrix could
%D be made. Dalyoung does the clever bits, and Hans only cleanes up and
%D optimizes a bit.

% \registerctxluafile{l-matrix}{1.001} % not yet

\startmodule[matrix]

\startluacode

local settings_to_hash = utilities.parsers.settings_to_hash
local formatters       = string.formatters
local copy             = table.copy
local insert           = table.insert
local remove           = table.remove

local matrix      = { }
moduledata.matrix = matrix

local f_matrix_slot = formatters["%s_{%s%s}"]

function matrix.symbolic(sym, x, y, nx ,ny) -- symMatrix("a", "m", "n")
    local nx = nx or 2
    local ny = ny or nx
    local function filled(i,y)
        local mrow = { }
        for j=1,nx do
            mrow[#mrow+1] = f_matrix_slot(sym,i,j)
        end
        mrow[#mrow+1] = "\\cdots"
        mrow[#mrow+1] = f_matrix_slot(sym,i,y)
        return mrow
    end
    local function dummy()
        local mrow = { }
        for j=1,nx do
            mrow[#mrow+1] = "\\vdots"
        end
        mrow[#mrow+1] = "\\ddots"
        mrow[#mrow+1] = "\\vdots"
        return mrow
    end
    --
    local mm = { }
    for i=1,ny do
        mm[i] = filled(i,y)
    end
    mm[#mm+1] = dummy()
    mm[#mm+1] = filled(x,y)
    return mm
end

-- todo: define a matrix at the tex end so that we have more control

local fences_p = {
    left  = "\\left(\\,",
    right = "\\,\\right)",
}

local fences_b = {
    left  = "\\left[\\,",
    right = "\\,\\right]",
}

function matrix.typeset(m,options)
    local options = settings_to_hash(options or "")
    context.startmatrix(options.determinant and fences_b or fences_p)
        for i=1, #m do
            local mi = m[i]
            for j=1,#mi do
                context.NC(mi[j])
            end
            context.NR()
        end
    context.stopmatrix()
end

-- interchange two rows (i-th, j-th)

function matrix.swap(t,i,j)
    t[i], t[j] = t[j], t[i]
end

-- replace i-th row with factor * (i-th row)

function matrix.multiply(m,i,factor)
    local mi = m[i]
    for k=1,#mi do
        mi[k] = factor * mi[k]
    end
    return m
end

-- scalar product "factor * m"

function matrix.scalar(m, factor)
    for i=1,#m do
        local mi = m[i]
        for j=1,#mi do
            mi[j] = factor * mi[j]
        end
    end
    return m
end

-- replace i-th row with i-th row + factor * (j-th row)

function matrix.sumrow(m,i,j,factor)
    local mi = m[i]
    local mj = m[j]
    for k=1,#mi do
        mi[k] = mi[k] + factor * mj[k]
    end
end

-- transpose of a matrix

function matrix.transpose(m)
    local t = { }
    for j=1,#m[1] do
        local r = { }
        for i=1,#m do
            r[i] = m[i][j]
        end
        t[j] = r
    end
    return t
end

-- inner product of two vectors

function matrix.inner(u,v)
    local nu = #u
    if nu == 0 then
        return 0
    end
    local nv = #v
    if nv ~= nu then
        return 0
    end
    local result = 0
    for i=1,nu do
        result = result + u[i] * v[i]
    end
    return result
end

-- product of two matrices

function matrix.product(m1,m2)
    local product = { }
    if #m1[1] == #m2 then
        for i=1,#m1 do
            local m1i  = m1[i]
            local mrow = { }
            for j=1,#m2[1] do
                local temp = 0
                for k=1,#m1[1] do
                    temp = temp + m1i[k] * m2[k][j]
                end
                mrow[j] = temp
            end
            product[i] = mrow
        end
    end
    return product
end

local function uppertri(m,sign)
    local temp = copy(m)
    for i=1,#temp-1 do
        local pivot = temp[i][i]
        if pivot == 0 then
            local pRow = i +1
            while temp[pRow][i] == 0 do
                pRow = pRow + 1
                if pRow > #temp then -- if there is no nonzero number
                    return temp
                end
            end
            temp[i], temp[pRow] = temp[pRow], temp[i]
            if sign then
                sign = -sign
            end
        end
        local mi = temp[i]
        for k=i+1, #temp do
            local factor = -temp[k][i]/mi[i]
            local mk = temp[k]
            for l=i,#mk do
                mk[l] = mk[l] + factor * mi[l]
            end
        end
    end
    if sign then
        return temp, sign
    else
        return temp
    end
end

matrix.uppertri = uppertri

function matrix.determinant(m)
    if #m == #m[1] then
        local d = 1
        local t, s = uppertri(m,1)
        for i=1,#t do
            d = d * t[i][i]
        end
        return s*d
    else
        return 0
    end
end

local function rowechelon(m,r)
    local temp = copy(m)
    local pRow = 1
    local pCol = 1
    while pRow <=  #temp  do
        local pivot = temp[pRow][pCol]
        if  pivot == 0 then
            local i = pRow
            local n = #temp
            while temp[i][pCol] == 0 do
                i = i + 1
                if i > n then
                    -- no nonzero number in a column
                    pCol = pCol + 1
                    if pCol > #temp[pRow] then
                        -- there is no nonzero number in a row
                        return temp
                    end
                    i = pRow
                end
            end
            temp[pRow], temp[i] = temp[i], temp[pRow]
        end
        local row = temp[pRow]
        pivot = row[pCol]
        for l=pCol,#row do
            row[l] = row[l]/pivot
        end

        if r == 1 then
            -- make the "reduced row echelon form"
            local row = temp[pRow]
            for k=1,pRow-1 do
                local current = temp[k]
                local factor  = -current[pCol]
                local mk      = current
                for l=pCol,#mk do
                    mk[l] = mk[l] + factor * row[l]
                end
            end
        end
        -- just make the row echelon form
        local row = temp[pRow]
        for k=pRow+1, #temp do
            local current = temp[k]
            local factor  = -current[pCol]
            local mk      = current
             for l=pCol,#mk do
                mk[l] = mk[l] + factor * row[l]
            end
        end
        pRow = pRow + 1
        pCol = pCol + 1

        if pRow > #temp or pCol > #temp[1] then
            pRow = #temp + 1
        end
    end
    return temp
end

matrix.rowechelon = rowechelon
matrix.rowEchelon = rowechelon

--  solve the linear equation m X = c

local function solve(m,c) 
    local n = #m
    if n ~= #c then
        return copy(m)
    end
    local newm = copy(m)
    local temp = copy(c)
    for i=1,n do
        insert(newm[i],temp[i])
    end
    return rowechelon(newm,1)
end

matrix.solve = solve

-- find the inverse matrix of m

local function inverse(m)
    local n = #m
    local temp = copy(m)
    if n ~= #m[1] then
        return temp
    end
    for i=1,n do
        for j=1,n do
            insert(temp[i],j == i and 1 or 0)
        end
    end
    temp = rowechelon(temp,1)
    for i=1,n do
        for j=1,n do
            remove(temp[i], 1)
        end
    end
    return temp
end

matrix.inverse = inverse

\stopluacode

\stopmodule

\unexpanded\def\ctxmodulematrix#1{\ctxlua{moduledata.matrix.#1}}

\continueifinputfile{m-matrix.mkiv}

\starttext

\startluacode
document.DemoMatrixA = {
    { 0, 2,  4, -4, 1 },
    { 0, 0,  2,  3, 4 },
    { 2, 2, -6,  2, 4 },
    { 2, 0, -6,  9, 7 },
    { 2, 3,  4,  5, 6 },
    { 6, 6, -6,  6, 6 },
}

document.DemoMatrixB = {
    { 0, 2,  4, -4, 1 },
    { 0, 0,  2,  3, 4 },
    { 2, 2, -6,  2, 4 },
    { 2, 0, -6,  9, 7 },
    { 2, 2, -6,  2, 4 },
    { 2, 2, -6,  2, 4 },
}
\stopluacode

\startsubject[title={A symbolic matrix}]

\ctxmodulematrix{typeset(moduledata.matrix.symbolic("a", "m", "n"))}
\ctxmodulematrix{typeset(moduledata.matrix.symbolic("a", "m", "n", 4, 8))}

\stopsubject

\startsubject[title={Swap two rows (2 and 4)}]

\startluacode
moduledata.matrix.typeset(document.DemoMatrixA)
context.blank()
moduledata.matrix.swap(document.DemoMatrixA, 2, 4)
context.blank()
moduledata.matrix.typeset(document.DemoMatrixA)
\stopluacode

\stopsubject

\startsubject[title={Multiply $3 \times r_2$}]

\startluacode
moduledata.matrix.typeset(document.DemoMatrixA)
context.blank()
moduledata.matrix.typeset(moduledata.matrix.multiply(document.DemoMatrixA, 2, 3))
\stopluacode

\stopsubject

\startsubject[title={Row 2 + $3 \times r_4$}]

\startluacode
moduledata.matrix.typeset(document.DemoMatrixA)
context.blank()
moduledata.matrix.sumrow(document.DemoMatrixA, 2, 3, 4)
context.blank()
moduledata.matrix.typeset(document.DemoMatrixA)
\stopluacode

\stopsubject

\startsubject[title={Transpose a matrix}]

\startluacode
moduledata.matrix.typeset(document.DemoMatrixA)
context.blank()
moduledata.matrix.typeset(moduledata.matrix.transpose(document.DemoMatrixA))
\stopluacode

\stopsubject

\startsubject[title={The inner product of two vectors}]

\startluacode
context(moduledata.matrix.inner({ 1, 2, 3 }, { 3, 1, 2 }))
context.blank()
context(moduledata.matrix.inner({ 1, 2, 3 }, { 3, 1, 2, 4 }))
\stopluacode

\startsubject[title={The product of two matrices}]

\startluacode
moduledata.matrix.typeset(document.DemoMatrixA)
context.blank()
moduledata.matrix.typeset(moduledata.matrix.product(document.DemoMatrixA,document.DemoMatrixA))
\stopluacode

\stopsubject

\startsubject[title={An Upper Triangular Matrix}]

\ctxmodulematrix{typeset(moduledata.matrix.uppertri(document.DemoMatrixB))}

\startsubject[title={A determinant}]

\startluacode
local m = {
    { 1, 2,  4 },
    { 0, 0,  2 },
    { 2, 2, -6 },
}
context(moduledata.matrix.determinant(m))
\stopluacode

\stopsubject

\startsubject[title={Row echelon form}]

\startluacode
local m = {
    { 1, 3, -2,  0, 2,  0,  0 },
    { 2, 6, -5, -2, 4, -3, -1 },
    { 0, 0,  5, 10, 0, 15,  5 },
    { 2, 6,  0,  8, 4, 18,  6 },
}

moduledata.matrix.typeset(m)
moduledata.matrix.typeset(moduledata.matrix.rowechelon(m,1))
\stopluacode

\stopsubject

\startsubject[title={Solving linear equation}]

\startluacode
local m = {
    {  1,  3, -2,  0 },
    {  2,  0,  1,  2 },
    {  6, -5, -2,  4 },
    { -3, -1,  5, 10 },
}

local c = { 5, 2, 6, 8 }

moduledata.matrix.typeset(moduledata.matrix.solve(m,c))
\stopluacode

\stopsubject

\startsubject[title={Inverse matrix}]

\startcombination[2*1]
    {\ctxlua{moduledata.matrix.typeset { { 1, 1, 1 }, { 0, 2, 3 }, { 3, 2, 1 } }}} {}
    {\ctxlua{moduledata.matrix.typeset(moduledata.matrix.inverse { { 1, 1, 1 }, { 0, 2, 3 }, { 3, 2, 1 } })}} {}
\stopcombination

\stopsubject

\stoptext
