%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 random = math.random local context = context 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 = { parentheses = { left = "\\left(\\,", right = "\\,\\right)" }, brackets = { left = "\\left[\\,", right = "\\,\\right]" }, bars = { left = "\\left|\\,", right = "\\,\\right|" }, } -- one can add more fences fences.bar = fences.bars fences.parenthesis = fences.parentheses fences.bracket = fences.brackets -- one can set the template matrix.template = "%0.3F" function matrix.typeset(m,options) if type(m) == "table" then local options = settings_to_hash(options or "") local whatever = options.determinant == "yes" and fences.bars or fences.parentheses if options.fences then whatever = fences[options.fences] or whatever elseif options.determinant then -- whatever = fences.brackets whatever = fences.bars end local template = options.template or matrix.template if template == "yes" then template = matrix.template elseif template == "no" then template = false elseif tonumber(template) then template = "%0." .. template .. "F" end context.startmatrix(whatever) for i=1, #m do local mi = m[i] for j=1,#mi do context.NC() local n = mi[j] if template and tonumber(n) then context(template,n) else context(mi[j]) end end context.NR() end context.stopmatrix() elseif m then context(m) end end -- interchange two rows (i-th, j-th) -- function matrix.swaprows(t,i,j) -- if i <= #t and j <= #t then -- t[i], t[j] = t[j], t[i] -- return t -- else -- return "error: out of bound" -- end -- end function matrix.swaprows(t,i,j) local ti = t[i] if not ti then return "error: no row i" end local tj = t[j] if not tj then return "error: no row j" end t[i], t[j] = tj, ti return t end -- interchange two columns (i-th, j-th) -- function matrix.swapcolumns(t, i, j) -- if i <= #t[1] and j <= #t[1] then -- for k = 1, #t do -- t[k][i], t[k][j] = t[k][j], t[k][i] -- end -- return t -- else -- return "error: out of bound" -- end -- end function matrix.swapcolumns(t, i, j) local t1 = t[1] if not t1 then return "error: no rows" end local n = #t1 if i <= n then return "error: no row i" end if j <= n then return "error: no row j" end for k = 1, #t do local tk = t[k] tk[i], tk[j] = tk[j], tk[i] end return t end matrix.swapC = matrix.swapcolumns matrix.swapR = matrix.swaprows matrix.swap = matrix.swaprows -- 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 "error: size mismatch" 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) if #m1[1] == #m2 then local product = { } 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 return product else return "error: size mismatch" end 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 local function 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 "error: not a square matrix" end end matrix.determinant = determinant 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 -- make matrices until its determinant is not 0 function matrix.make(n,m,low,high) if not n then n = 10 end if not m then m = 10 end if not low then low = 0 end if not high then high = 100 end local t = { } -- make an empty n1 x n2 array local again = true for i=1,n do t[i] = { } end while true do for i=1,n do local ti = t[i] for j=1,m do ti[j] = random(low,high) end end if n ~= m or determinant(t,1) ~= 0 then return t end end end -- extract submatrix by using local function submatrix(t,i,j) local rows = #t local columns = #t[1] local sign = 1 -- not used if i <= rows and j <= columns then local c = copy(t) remove(c,i) for k=1,rows-1 do remove(c[k],j) end return c else return "error: out of bound" end end matrix.submatrix = submatrix -- calculating determinant using Laplace Expansion function matrix.laplace(t) -- not sure if this is the most effient but local factors = { 1 } -- it's not used for number crunching anyway local data = copy(t) local det = 0 while #data > 0 do local mat = { } local siz = #data[1] if siz == 0 then return "error: no determinant" elseif siz == 1 then det = data[1][1] return det end for i=1,siz do mat[i] = data[1] remove(data,1) end local factor = remove(factors,1) local m1 = mat[1] if siz == 2 then local m2 = mat[2] det = det + factor * (m1[1]*m2[2] - m1[2]*m2[1]) else for j=1,#m1 do local m1j = m1[j] if m1j ~= 0 then insert(factors, (-1)^(j+1) * factor * m1j) local m = submatrix(mat,1,j) for k, v in next, m do insert(data,v) end end end end end return det end -- 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 + $4 \times r_3$}] \startluacode moduledata.matrix.typeset(document.DemoMatrixA) context.blank() moduledata.matrix.sumrow(document.DemoMatrixA, 2, 3, 4) context.blank() moduledata.matrix.typeset(document.DemoMatrixA,{ fences = "bars" } ) \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, "determinant=yes" )) \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) context.blank() moduledata.matrix.typeset(moduledata.matrix.rowechelon(m,1), { determinant = "yes" }) \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)) context.blank() moduledata.matrix.typeset(moduledata.matrix.solve(m,c), { template = 6 }) context.blank() moduledata.matrix.typeset(moduledata.matrix.solve(m,c), { template = "no" }) context.blank() moduledata.matrix.typeset(moduledata.matrix.solve(m,c), { template = "%0.3f" }) context.blank() moduledata.matrix.typeset(moduledata.matrix.solve(m,c), { template = "%0.4F" }) \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