Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I am hoping to be able to create a macro to multiply matrices using a lua module. The module is here:

https://raw.github.com/davidm/lua-matrix/master/lua/matrix.lua

As suggested to me in a previous question I put my lua code in a separate lua file and dofiled it as below:

\documentclass{article}
\usepackage{xparse}
\usepackage{luacode}
\usepackage{fontspec}

\directlua{dofile("matrix.lua")}
\directlua{dofile("lua2.lua")}
\ExplSyntaxOn

\NewDocumentCommand{\matrixop}{ m m }
{
    \luaexec{
        matrop(#1,#2)
    }
}
\ExplSyntaxOff

\begin{document}
\matrixop{{1,2,3},{4,5,6}}{{7,8,9},{10,11,12}}
\end{document}

Where the file lua2.lua contained the following:

function matrop (mat1, mat2)
local matrix = require 'matrix'
m1 = matrix {mat1}
m2 = matrix {mat2}
m3 = matrix.add(m1,m2)
a  = matrix.latex(m3,c)
tex.print(a)
end

The issue that arose with this is that in matrix.lua a matrix is defined as e.g.

m = matrix{{1,2,3},{4,5,6}}

Thus, I have to pass as mat1 something that looks like {1,2,3},{4,5,6}. However, as it stands, in the code above matrop(#1,#2) is equivalent to

matrop({1,2,3},{4,5,6},{7,8,9},{10,11,12})

Where the comma separating the rows in mat1 is visible to the macro and so matrop sees 4 comma separated arguments rather than 2 arguments that are comma separated. Lua understands me to have passed a pair of single row matrices after dropping the "extra" two arguments (this took forever to figure out). Adding extra braces around each argument to hide the commas doesn't work because it screws up the table that matrix is expecting. My idea was to replace the row separating comma by a *, pass that to matrop, revert the * to a comma in lua using strings, convert the strings back into tables and then pass that to the matrix command (this took much reading and fiddling, I don't know lua at all). The result is below:

\documentclass{article}
\usepackage{xparse}
\usepackage{luacode}
\usepackage{fontspec}

\directlua{dofile("matrix.lua")}
\directlua{dofile("lua2.lua")}
\ExplSyntaxOn

\NewDocumentCommand{\matrixop}{ m m }
{
    % quotes are so that matrop sees args as strings
    \luaexec{
        matrop("{#1}","{#2}")
    }
}
\ExplSyntaxOff

\begin{document}
% row separator now a *
\matrixop{{1,2,3}*{4,5,6}}{{7,8,9}*{10,11,12}}
\end{document}

Where lua2.lua is as follows:

function matrop (mat1, mat2)
    -- replace *'s with ,'s
    s1 = string.gsub(mat1,"*",",")
    s2 = string.gsub(mat2,"*",",")

    -- functions that load resulting strings
    f1 = assert(loadstring("return"..s1))
    f2 = assert(loadstring("return"..s2))

    -- t1 and t2 are now tables (as I understand it)
    t1 = f1()
    t2 = f2()

    local matrix = require 'matrix'
    -- pass table entries (rows) to matrix definers
    m1 = matrix {t1[1],t1[2]}
    m2 = matrix {t2[1],t2[2]}
    m3 = matrix.add(m1,m2)
    a  = matrix.latex(m3,c)
    tex.print(a)
end

I am sure that I have done many bad/improper/horrible things above. My question is: how in the world should I actually be doing this? Is there an easy way to pass the information needed to define the matrices to lua? Any and all suggestions, comments, tips, solutions etc. are welcome.

share|improve this question
add comment

3 Answers 3

up vote 6 down vote accepted

If you use (IMHO) a more user-friendly input similar to (a, b, c)(d, e, f) than it is easy to create the matrices with basic lua commands without the need to know the sizes of the matrices.

\documentclass{article}
\usepackage{xparse}
\usepackage{fontspec}
\usepackage{filecontents}

\begin{filecontents*}{luaFunctions.lua}
function matrop (mat1, mat2)
    local matrix = require 'matrix'

    m1 = matrix{unpack(CreateMatrix(mat1))}
    m2 = matrix{unpack(CreateMatrix(mat2))}
    m3 = matrix.add(m1,m2)

    --tex.print("\\\\")
    tex.sprint( matrix.latex(m1,c).."+")
    tex.sprint( matrix.latex(m2,c).."=")
    tex.sprint( matrix.latex(m3,c))
end

function CreateMatrix(str)
    rows={}

    -- get the elements between the braces 
    -- and execute the following function
    string.gsub(str, "%((.-)%)",
        function(sub)
            -- for debugging
            --tex.print("Row: "..sub.."\\\\")
            -- split the string at ','
            elements = string.explode(sub,",")
            row={}
            for i,e in ipairs(elements) do
                -- remove spaces (not really necessary)
                e = string.gsub(e," ","")
                -- insert the element to the row-table
                table.insert(row,e)
                -- for debugging
                --tex.print("Element :"..e.."\\\\")            
            end
            -- insert the row-table to the rows-table
            table.insert(rows,row)
        end 
    )

    return rows
end
\end{filecontents*}

\directlua{dofile("luaFunctions.lua")}
\ExplSyntaxOn

\NewDocumentCommand{\matrixop}{mm}{
    \directlua{matrop("#1","#2")}}

\ExplSyntaxOff

\begin{document}\noindent
\matrixop{(-1)} {( 10)}\\[2ex]
%
\matrixop{(-1, -2, -3)} {( 10, 11, 12)}\\[2ex]
%
\matrixop{(-1, -2, -3)(4, 5, -6)}{(7, -98, 9) (10, 11, 12)}\\[2ex]
%
\matrixop{(-1, -2, -3, 4, 5, -6)(8, 4, 1, 6, 8, 3)} {(7, -98, 9, 10, 11, 12)(8, 4, 1, 6, 8, 3)}\\[2ex]
%
\matrixop{(-1, -2, -3)(4, 5, -6)(8, 4, 1)(6, 8, 3)} {(7, -98, 9) (10, 11, 12)(8, 4, 1)(6, 8, 3)}\\[2ex]
%
\matrixop{(-1, -2)( -3, 4)( 5, -6)(8, 4)( 1, 6)( 8, 3)} {(7, -98)( 9, 10)( 11, 12)(8, 4)( 1, 6)( 8, 3)}\\[2ex]
%
\matrixop{(-1)( -3)( 5)} {(7)( 9)( 11)}\\[2ex]
\end{document}

enter image description here

Edit Matlab style: If you want to use a matrix-input similar to Matlab you can use the following functions. Here it is not necessary to use the xparse package and it is not needed to know how many matrices (and there dimensions) the user give to the function.

\documentclass{article}
\usepackage{fontspec}
\usepackage{filecontents}

\begin{filecontents*}{luaFunctions.lua}
function matropMatlab (mat)
    local matrix = require 'matrix'

    matrices = CreateMatrixMatlab(mat)
    m1 = matrix{unpack(matrices[1])}
    m2 = matrix{unpack(matrices[2])}
    m3 = matrix.add(m1,m2)

    tex.sprint( matrix.latex(m1,c).."+")
    tex.sprint( matrix.latex(m2,c).."=")
    tex.sprint( matrix.latex(m3,c))
end

function CreateMatrixMatlab(str)    
    matrices={}
    string.gsub(str, "%[(.-)%]",
        function(sub)
            splitedRows = string.explode(sub,";")
            rows={}
            for i,sr in ipairs(splitedRows) do
               elements = string.explode(sr)
               row={}
               for k,e in ipairs(elements) do
                  table.insert(row,e)
               end       
               table.insert(rows,row)
            end
            table.insert(matrices,rows)
        end 
    )    
    return matrices
end
\end{filecontents*}

\directlua{dofile("luaFunctions.lua")}

\def\matrixopMatlab#1{%
    \directlua{matropMatlab("#1")}}

\begin{document}
\matrixopMatlab{
[16 3 2 13; 5 -10 11 8; 19 65 7 12; 41 15 -14 1] 
[4 3 21 17; 5 14 11 23; 18 1 71 12; 24 15 44 14]}\\[2ex]
\matrixopMatlab{
[16 3 2 13 1; 5 -10 11 8 2; 19 65 7 12 3] 
[4 3 21 17 1; 5 14 11 23 2; 18 1 71 12 3]}
\end{document}
share|improve this answer
    
Ahh, thank you very much. This is great, I think I'll learn quite a bit as I work through it :) Was the choice to use square brackets rather than parentheses in the second method just to distinguish the two or was there an explict reason for it? –  Scott H. Sep 22 '12 at 19:51
    
@ScottH. The reason for the square brackets is that the syntax is similar to the Matlab syntax of matrices (maybe your users are more familiar with the Matlab syntax or you want to read it from an other script file). Also it shows that you can choose different delimiters for you input and that you can create a pure Lua solution without the xparse(LaTeX3) package. –  Holle Sep 22 '12 at 20:57
add comment

Not really an answer (because you've already analyzed the problem), but just my thoughts:

I think you are doing a great job! The problem might be more obvious if you consider macro expansion as a textual replacement:

\NewDocumentCommand{\matrixop}{ m m }
{
    \luaexec{
        matrop(#1,#2)
    }
}

is essentially the same as

\def\matrixop#1#2{\luaexec{matrop(#1,#2)}

right? So when you call \matrixop{{1,2,3},{4,5,6}}{{7,8,9},{10,11,12}}, #1 gets {1,2,3},{4,5,6} and #2 {7,8,9},{10,11,12} (this is what you've written). So the replacement text of the \def above is: \luaexec{matrop(#1,#2) -> \luaexec{matrop({1,2,3},{4,5,6},{7,8,9},{10,11,12}), a function call with four arguments. If you know that you always separate the macro by commas, you can define the function matrop() taking four arguments:

function matrop (_1, _2, _3, _4)
  m1 = matrix {_1,_2}
  m2 = matrix {_3,_4}
  m3 = matrix.add(m1,m2)
  a  = matrix.latex(m3,c)
  tex.print(a)
end

I think that is just the way it is.

I wouldn't go for your second solution, as it is potentially unsafe (loadstring("return"..s1). If this doesn't parse, your code will blow up. And just replacing one delimiter with another is not worth the trouble (IMHO).

share|improve this answer
    
Hello Mr. Gundla, thanks for the reply. The issue with hardcoding the values (and I know this wasn't clear because I did it above) is that I'd like to access other matrix functions as well such as matrix.mul and have the user (me) input matrices of arbitrary dimension. I know that I can define a lua function that takes a variable number of args. If the dimensions are passed to lua as the first arguments, would I be able to have a loop within the matrix definition? Something like m1=matrix{for i=1,arg do t[i] end}? Or would that break things? –  Scott H. Sep 21 '12 at 19:38
    
@ScottH. I guess you really have to do some processing. Find out the number of arguments you get (num = select("#",...)) and act according to that number. It's stupid work in Lua, I know, but I think it is the best solution. The other possibility would be to define more TeX commands that call different Lua commands, one for add, one for mul,... Perhaps these functions always take the same amount of arguments? –  topskip Sep 21 '12 at 20:02
    
Ack, I was afraid that was going to be the case! The functions all either take two matrices as arguments or a single matrix but to allow for arbitrary matrices, the number of rows passed would be unpredictable. –  Scott H. Sep 22 '12 at 1:27
1  
@ScottH. You can always construct arguments for the matrix.* functions by collecting the arguments in a table and call matrix.add(unpack(mytable)) for example. That way the entries 1,2 and so forth are passed as arguments to the new function. –  topskip Sep 22 '12 at 8:27
add comment

Thanks Holle and Mr. Gundla this is what have ended up with. Sharing it here because I think that people will find it useful:


The following is a hacked together LaTeX interface to a lua matrix computation module. The result is that matrix computations can be performed "in document". Matrixop.sty provides two macros: \matrixop<args> and \formatmatrix{matrix}. In one of its basic forms:

\matrixop{operation}{matrix1}{matrix2}

performs the computation and typesets the operation and result. For example the input on the left produces the output on the right:

enter image description here

Most operations provided by the matrix module are supported in both numeric and symbolic forms: addition, subtraction, multiplication, inversion, "division", determinants, vector scalar and cross products. For details put the files matrixop.sty, matrix.lua, matrixop.lua and matrixop-test.tex in the same directory (or install the .sty and put the lua files in the same directory) and compile matrixop-test.tex with lualatex.

File: matrixop.sty

\NeedsTeXFormat{LaTeX2e}

\ProvidesPackage{matrixop}[2012/09/26 ver 1.0]


\RequirePackage{xparse}
\RequirePackage{fontspec}
\RequirePackage{mathtools}
\RequirePackage{geometry}

\DeclareMathOperator{\rref}{rref}

\directlua{dofile(kpse.find_file("matrixop.lua"))}
\directlua{dofile(kpse.find_file("matrix.lua"))}

\ExplSyntaxOn

\seq_new:N \g__matrop_symbops_seq
\seq_gset_split:Nnn \g__matrop_symbops_seq {,}{add,sub,mul,det,transpose,scalar,cross,add*,sub*,mul*,transpose*,cross*}

% Main Document Command: \matrixop
% - starred gives symbolic output
% - operation is first mandatory argument
% - operations are: add, sub, mul, div, invert, pow, root, det, dogauss, scalar and cross
% - starring the operation name, e.g \matrixop{mul*}... doesn't typeset operation but returns
% - the resulting matrix (not in useable form) in the macro \mout
% - root and pow require a numeric argument after the operation name giving the power/root, e.g. \matrixop{pow}[3]{matrix}
% - the final, optional, argument is the number of digits to round the output to

\NewDocumentCommand{\matrixop}{ s m o m g o }
    {
        \IfBooleanTF{#1}
        { % if star
            \seq_if_in:NnTF \g__matrop_symbops_seq {#2}
            {
            \IfNoValueTF{#5}
                { % if unary
                    \directlua{matropsymb("#2","#4")}
                }
                { % if binary
                    \directlua{matropsymb("#2","#4","#5")}
                }
            }
            {
                \msg_error:nnx {matrop} {Operation~not~compatible~with~symbolic} {#2}
            }
        }
        { % if no star
                % if not root or pow
            \IfNoValueTF{#3}
                { % if not binary
                    \IfNoValueTF{#5}
                        { % if unary
                            % if not round
                            \IfNoValueTF{#6}
                                % then unary op, no round
                                {\directlua{matrop("#2","#4")}}
                                % then unary op, with round
                                {\directlua{matrop("#2","#4","#6")}}
                        }
                        { % if binary
                            % if not round
                            \IfNoValueTF{#6}
                                % then binary, no round
                                {\directlua{matrop("#2","#4","#5")}}
                                % then binary, with round
                                {\directlua{matrop("#2","#4","#5","#6")}}
                        }
                }
                { % root or pow
                    % if not round
                    \IfNoValueTF{#6}
                        % then root or pow, no round
                        {\directlua{matrop("#2","#3","#4")}}
                        % then root or pow, with round
                        {\directlua{matrop("#2","#3","#4","#6")}}
                }
        }
    }

% This takes a macro that contains a matrix, and typesets the result.
% - first arg is optional to specify column justification

\NewDocumentCommand{\formatmatrix}{ O{r} m}
    {
        \matrixop_format:nn {#1}{#2}
    }

% helper macro for the above

\cs_new:Npn \matrixop_format:nn #1#2
    {
        \tl_set:NV \l_tmpa_tl {#2}
        \tl_replace_all:Nnn \l_tmpa_tl {)(}{\\}
        \tl_replace_all:Nnn \l_tmpa_tl {,}{&}
        \tl_set:Nx \l_tmpa_tl {\tl_tail:N \l_tmpa_tl}
        \tl_replace_all:Nnn \l_tmpa_tl {)}{}
        \begin{bmatrix*}[#1]
            \tl_use:N \l_tmpa_tl
        \end{bmatrix*}
    }

\ExplSyntaxOff

File: matrixop.lua

function matropsymb (...)
        -- desired operation is passed as first arg
        local op = arg[1]
    local matrix = require 'matrix'
        local symbol = matrix.symbol
        local m1, m2, mout

        -- determine whether starred
        local test = op
        if string.match(test,"*") then
            -- if so, then set flag and drop star
            op = string.sub(op,1,-2)
            star = true
        else
            star = false
        end -- if string

        -- if 2 args, must be unary
        if #arg == 2 then
            m1   = matrix{unpack(CreateMatrix(arg[2]))}:replace(symbol)
            mout = matrix[op](m1)
        -- if 3 args, must be binary
        elseif #arg == 3 then
                m1   = matrix{unpack(CreateMatrix(arg[2]))}:replace(symbol)
                m2   = matrix{unpack(CreateMatrix(arg[3]))}:replace(symbol)
                mout = matrix[op](m1,m2)--:replace(symbol)
        end

-- if no star, then typeset operation and result    
if not star then
  if op == "mul" then
    tex.sprint( matrix.latex(m1,"r").."\\cdot")
    tex.sprint( matrix.latex(m2,"r").."=")
    tex.sprint( matrix.latex(mout,"r"))

  elseif op == "add" then 
    tex.sprint( matrix.latex(m1,"r").."+")
    tex.sprint( matrix.latex(m2,"r").."=")
    tex.sprint( matrix.latex(mout,"r"))

  elseif op == "sub" then
    tex.sprint( matrix.latex(m1,"r").."-")
    tex.sprint( matrix.latex(m2,"r").."=")
    tex.sprint( matrix.latex(mout,"r"))

    elseif op == "det" then
        tex.sprint( "\\det"..matrix.latex(m1,"r").."=")
        tex.sprint(mout)

    elseif op == "transpose" then
        tex.sprint(matrix.latex(m1,"r").."^{T}".."\\mkern -8mu=")
        tex.sprint(matrix.latex(mout,"r"))

    elseif op == "scalar" then
        m1 = matrix.transpose(m1)
        m2 = matrix.transpose(m2)
        tex.sprint(matrix.latex(m1,"r").."\\cdot"..matrix.latex(m2,"r").."=")
        tex.sprint(mout)

    elseif op == "cross" then
        tex.sprint(matrix.latex(m1,"r").."\\times"..matrix.latex(m2,"r").."=")
        tex.sprint(matrix.latex(mout,"r"))

  end -- if
end -- if not star

-- store output matrix in macro.
-- tex.print("\\gdef\\matout{"..ReturnMatrix(mout).."}")
    if op ~= "det" then
        if op ~="scalar" then
        tex.print("\\gdef\\matout{"..ReturnMatrix(mout).."}")
        end
    end

end -- function

function matrop (...)
        -- desired operation is passed as first arg
        local op = arg[1]
    local matrix = require 'matrix'
        local m1, m2, m3, mout

        -- determine whether starred
        local test = op
        if string.match(test,"*") then
            op = string.sub(op,1,-2)
            star = true
        else
            star = false
        end

        -- to test whether args are numbers or matrices
        local test2 = tonumber(arg[2])
        local test3 = tonumber(arg[3])

        if #arg == 2 then
        -- if 2 args, then unary
        -- dogauss, alters original matrix so handled differently
            m1   = matrix{unpack(CreateMatrix(arg[2]))}
            if op == "dogauss" then
                mout = matrix.copy(m1)
                matrix[op](mout)
            else
                mout = matrix[op](m1)
            end -- if gauss
        -- several cases for 3 args
        elseif #arg == 3 then
            -- if second arg is a number-> root or pow
            if test2 then
                m1   = matrix{unpack(CreateMatrix(arg[3]))}
                mout = matrix[op](m1,tonumber(arg[2]))
            else
                -- if not then if arg 3 is a number-> unary with round
                -- as above, dogauss handled differently
                if test3 then
                m1   = matrix{unpack(CreateMatrix(arg[2]))}
                    if op == "dogauss" then
                        mout = matrix.copy(m1)
                        matrix[op](mout)
                        mout = matrix.round(mout,arg[3])
                    else
                        m2   = matrix[op](m1)
                        mout = matrix.round(m2,arg[3])
                    end -- if dogauss
                -- if arg 3 is nan-> binary no round
                else
                    m1   = matrix{unpack(CreateMatrix(arg[2]))}
                    m2   = matrix{unpack(CreateMatrix(arg[3]))}
                    mout = matrix[op](m1,m2)
                end -- if test3
            end -- if test2
        else -- pow or root with round or binary with round
            if test2 then
                m1   = matrix{unpack(CreateMatrix(arg[3]))}
                m2   = matrix[op](m1,tonumber(arg[2]))
                mout = matrix.round(m2,arg[4])
            else
                m1   = matrix{unpack(CreateMatrix(arg[2]))}
                m2   = matrix{unpack(CreateMatrix(arg[3]))}
                m3   = matrix[op](m1,m2)
                mout = matrix.round(m3,arg[4])
            end -- if test2
        end -- if #arg

-- no star-> typeset operation and result   
if not star then
  if op == "mul" then
    tex.sprint( matrix.latex(m1,"r").."\\cdot")
    tex.sprint( matrix.latex(m2,"r").."=")
    tex.sprint( matrix.latex(mout,"r"))

  elseif op == "add" then 
    tex.sprint( matrix.latex(m1,"r").."+")
    tex.sprint( matrix.latex(m2,"r").."=")
    tex.sprint( matrix.latex(mout,"r"))

  elseif op == "div" then
    tex.sprint( matrix.latex(m1,"r").."\\cdot")
    tex.sprint( matrix.latex(m2,"r").."^{-1}".."\\mkern -8mu=")
    tex.sprint( matrix.latex(mout,"r"))

  elseif op == "sub" then
    tex.sprint( matrix.latex(m1,"r").."-")
    tex.sprint( matrix.latex(m2,"r").."=")
    tex.sprint( matrix.latex(mout,"r"))

    elseif op == "invert" then
        tex.sprint( matrix.latex(m1,"r").."^{-1}".."\\mkern -8mu=")
        tex.sprint( matrix.latex(mout,"r"))

    elseif op == "det" then
        tex.sprint( "\\det"..matrix.latex(m1,"r").."=")
        tex.sprint(mout)

    elseif op == "dogauss" then
        tex.sprint("\\rref"..matrix.latex(m1,"r").."=")
        tex.sprint( matrix.latex(mout,"r"))

    elseif op == "root" then
        tex.sprint(matrix.latex(m1,"r").."^{\\frac{1}{"..arg[2].."}}".."\\mkern -8mu=")
        tex.sprint(matrix.latex(mout,"r"))

    elseif op == "transpose" then
        tex.sprint(matrix.latex(m1,"r").."^{T}".."\\mkern -8mu=")
        tex.sprint(matrix.latex(mout,"r"))

    elseif op == "pow" then
        tex.sprint(matrix.latex(m1,"r").."^{"..arg[2].."}".."\\mkern -8mu=")
        tex.sprint(matrix.latex(mout,"r"))

    elseif op == "scalar" then
        m1 = matrix.transpose(m1)
        m2 = matrix.transpose(m2)
        tex.sprint(matrix.latex(m1,"r").."\\cdot"..matrix.latex(m2,"r").."=")
        tex.sprint(mout)

    elseif op == "cross" then
        tex.sprint(matrix.latex(m1,"r").."\\times"..matrix.latex(m2,"r").."=")
        tex.sprint(matrix.latex(mout,"r"))
  end -- if op
end -- if not star

tex.print("\\gdef\\matout{"..ReturnMatrix(mout).."}")

end -- function

-- builds matrix table from input passed by tex
function CreateMatrix(str)
    rows={}

    -- get the elements between the braces
    -- and execute the following function
    string.gsub(str, "%((.-)%)",
        function(sub)
            -- for debugging
            -- tex.print("Row: "..sub.."\\\\")
            -- split the string at ','
            elements = string.explode(sub,",")
            row={}
            for i,e in ipairs(elements) do
                -- remove spaces (not really necessary)
                e = string.gsub(e," ","")
                -- insert the element to the row-table
                table.insert(row,e)
                -- for debugging
                -- tex.print("Element :"..e.."\\\\")
            end
            -- insert the row-table to the rows-table
            table.insert(rows,row)
        end
    )

    return rows
end

-- returns output matrix in format that can be passed back
function ReturnMatrix(t)
    if not (type(t)=="table") then
        return t
    else
    local str = ""
        for i=1,#t do
            str=str.."("..t[i][1]
                for j=2,#t[1] do
                    str=str..","..t[i][j]
                end
            str=str..")"
        end
    return str
    end
end

File: matrix.lua This file can be found here: https://github.com/davidm/lua-matrix I modified the file matrix.lua slightly. In particular, I changed the matrix.latex function. For the macro to work you will need to edit that function to the following (lines 898-918) in the source:

--// matrix.latex ( mtx [, align] )
-- LaTeX output
function matrix.latex( mtx, align )
    -- align : option to align the elements
    --      c = center; l = left; r = right
    --      \usepackage{dcolumn}; D{.}{,}{-1}; aligns number by . replaces it with ,
    local align = align or "c"
    local str = "\\begin{bmatrix*}[r]"
    local getstr = matrix.type( mtx ) == "tensor" and tensor_tostring or number_tostring
    for i = 1,#mtx do
        str = str.."\t"..getstr(mtx[i][1])
        for j = 2,#mtx[1] do
            str = str.." & "..getstr(mtx[i][j])
        end
        -- close line
        if i == #mtx then
            str = str.."\n"
        else
            str = str.." \\\\\n"
        end
    end
    return str.."\\end{bmatrix*}"
end

File: matrixop-test.tex

\documentclass{article}
\usepackage{matrixop}
\usepackage{showexpl}

\setlength{\parindent}{0pt}

\begin{document}

\begin{center}{\Huge Matrix operations in \TeX}\end{center}

\textbf{matrixop.sty} provides two commands: \verb=\matrixop= and \verb=\formatmatrix=.  The package requires that the document be compiled with Lua\LaTeX.  Matrices are passed to the macro either directly or in macros.  A matrix is passed as bracket delimited rows with comma separated entries, e.g. $(1,2,3)(4,5,6)$ would represent a $2\times 3$ matrix.  The \verb=\matrixop= macro itself takes a variety of forms.
\begin{enumerate}
    \item \verb=\matrixop<args>= Performs the operation and typesets the operation and result.
    \item \verb=\matrixop*<args>= Produces symbolic rather than calculated results.
    \item \verb=\matrixop<args>[n]= Rounds the entries of the resulting matrix to $n$ decimal places.
    \item \verb=\matrixop{op*}<args>= Performs the operation, but does not typeset the operation or result.
\end{enumerate}
In all cases, excluding symbolic determinant or symbolic dot product, the resulting matrix is stored in the macro \verb=\matout= in the same format as input.  Thus, \verb=\matout= can be reused as input.  Since \verb=\matout= is overwritten after each operation, it may be necessary to save it via, e.g. \verb=\let\mymat\matout=, so that it can be used later.  The supported operations are
\begin{itemize}
    \item Binary:
        \begin{itemize}
            \item add
            \item sub
            \item mul
            \item div
            \item scalar (dot product)
            \item cross (cross product)
        \end{itemize}
    \item Unary:
        \begin{itemize}
            \item det
            \item invert
            \item dogauss (returns $\rref$)
            \item root
            \item pow
            \item transpose
        \end{itemize}
\end{itemize}

The operations that support symbolic output are: add, mul, sub, det, transpose, scalar, and cross.

\section{Usage}

\def\matA{(1,2,3)(4,5,6)(7,0,9)}
\def\matB{(1,2,-1)(2,2,4)(1,3,-3)}
\def\matC{(1,1,2)(2,3,4)(5,6,7)}
\def\vA{(1)(2)(3)}
\def\vB{(4)(5)(6)}
\def\vC{(a)(b)(c)}
\def\matD{(a,b,c)(d,e,f)(g,h,i)}
\def\matE{(r,s,t)(u,v,w)(x,y,z)}

\begin{LTXexample}[pos=r]
\def\matA{(1,2,3)(4,5,6)(7,0,9)}
\def\matB{(1,2,-1)(2,2,4)(1,3,-3)}
\[\matrixop{mul}{\matA}{\matB}\]
\end{LTXexample}

For symbolic output, we may use (symbolic can be used with numeric input as well)

\begin{LTXexample}[pos=b, wide=true]
\def\matD{(a,b,c)(d,e,f)(g,h,i)}
\def\matE{(r,s,t)(u,v,w)(x,y,z)}
\[\matrixop*{mul}{\matD}{\matE}\]
\end{LTXexample}

If we prefer that the output is not typeset, then a star can be appended to the operation name within the first argument.  In this case the matrix (or number) resulting from the operation is stored in the \verb=\matout= macro and can be invoked as such before the next operation is executed, or stored for later use.  With the following code:

\begin{LTXexample}[pos=r,rframe=]
\matrixop{invert*}{\matA} % output stored in \matout
\let\ainv\matout % save for later
\matrixop{invert*}{\matB} % etc.
\let\binv\matout
\matrixop{mul*}{\binv}{\ainv}[5]
\let\bainv\matout
\matrixop{mul*}{\matA}{\matB}
\let\abprod\matout
\matrixop{mul*}{\abprod}{\bainv}[3]
\end{LTXexample}

% this is just here because of grouping introduced by showexpl, ie. the definitions
% above don't leave the scope of the environment
\matrixop{invert*}{\matA}
\let\ainv\matout
\matrixop{invert*}{\matB}
\let\binv\matout
\matrixop{mul*}{\binv}{\ainv}[5]
\let\bainv\matout
\matrixop{mul*}{\matA}{\matB}
\let\abprod\matout
\matrixop{mul*}{\abprod}{\bainv}[3]

We could then type,
\begin{LTXexample}[pos=b]
If $A$ and $B$ are as given below, 
\[
        A=\formatmatrix{\matA}\qquad B=\formatmatrix{\matB}
\]
Then the product $AB$
\[
    AB=\formatmatrix{\abprod}
\]
when multiplied (on the right) by the matrix $B^{-1}A^{-1}$
\[
    B^{-1}A^{-1}=\formatmatrix{\bainv}
\]
should produce the identity matrix,
\[
    AB(B^{-1}A^{-1})=\formatmatrix{\abprod}\cdot\formatmatrix{\bainv}=\formatmatrix{\matout}
\]
\end{LTXexample}

Some more examples below

\begin{LTXexample}[pos=b]
$\matrixop*{add}{\matD}{\matE}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{mul}{\matA}{\matB}$
\end{LTXexample}
\begin{LTXexample}[pos=b,wide=true]
$\matrixop*{mul}{\matD}{\matE}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{div}{\matA}{\matB}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{sub}{\matA}{\matB}$
\end{LTXexample}
\begin{LTXexample}[pos=b]
$\matrixop*{sub}{\matD}{\matE}$
\end{LTXexample}
\begin{LTXexample}[pos=b]
$\matrixop{invert}{\matA}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{invert}{\matA}[4]$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{det}{\matA}$
\end{LTXexample}
\begin{LTXexample}[pos=b]
$\matrixop*{det}{\matE}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{dogauss}{\matA}$
\end{LTXexample}
\begin{LTXexample}[pos=b]
$\matrixop{root}[5]{\matA}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{root}[5]{\matA}[4]$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{transpose}{\matA}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{pow}[3]{\matA}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{scalar}{\vA}{\vB}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop*{scalar}{\vA}{\vC}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop{cross}{\vA}{\vB}$
\end{LTXexample}
\begin{LTXexample}[pos=r]
$\matrixop*{cross}{\vA}{\vC}$
\end{LTXexample}

\end{document}
share|improve this answer
    
Answer was too long to post so I had to cut it down, hopefully I didn't break anything. I'm sure my lua could be improved, so criticism is welcome. –  Scott H. Sep 29 '12 at 0:05
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.