Row reduction macro
up vote
12
down vote
favorite
This is more a suggestion/request than a question:
Someone should write a LaTeX macro that automatically row reduces a matrix until it's in (reduced) echelon form and typesets all the steps. (As far as I can tell, none such exists.)
I'm thinking of something like the gauss package, except that the row reductions themselves are carried out automatically, like in the Linear Algebra Toolkit. This would be similar to the polylongdiv
command in the polynom package, where all one needs to do is enter the polynomials to be divided and the macro carries out the algorithm and typesets the steps.
Of course you might be wondering why I don't just do it myself. Well I guess my answer to that is: some (linear) combination of laziness, busyness, not being the right person for the job, etc.
Thanks, regards, respect, and even a little love. :*
matrices package-writing gauss
|
show 12 more comments
up vote
12
down vote
favorite
This is more a suggestion/request than a question:
Someone should write a LaTeX macro that automatically row reduces a matrix until it's in (reduced) echelon form and typesets all the steps. (As far as I can tell, none such exists.)
I'm thinking of something like the gauss package, except that the row reductions themselves are carried out automatically, like in the Linear Algebra Toolkit. This would be similar to the polylongdiv
command in the polynom package, where all one needs to do is enter the polynomials to be divided and the macro carries out the algorithm and typesets the steps.
Of course you might be wondering why I don't just do it myself. Well I guess my answer to that is: some (linear) combination of laziness, busyness, not being the right person for the job, etc.
Thanks, regards, respect, and even a little love. :*
matrices package-writing gauss
rref is numerically an ill conditioned algorithm. It becomes even worse with TeX precision. The industry standard is LU factorization.
– percusse
Mar 11 '17 at 16:12
@percusse Good point. But I just want something that can handle the simple matrices the students are required to row reduce in the class I teach. Know what I mean?
– Quinn Culver
Mar 11 '17 at 16:18
1
Doesn't matter, you'll get wrong results in even 3x3 matrices. multiply first row with 2/3 and add to the second row even that would screw up all integer valued entries.
– percusse
Mar 11 '17 at 16:29
1
Thesagetex
package relies on the computer algebra system SAGE to calculate the row echelon form of a matrix, even if it has variables and gives you access to a lot of other commands on matrices. However, it doesn't give the steps to get the answer.
– DJP
Mar 11 '17 at 21:44
1
Are you sure you need everything to be done inside TeX itself? Why not rely on an external program for doing all the computation (and communicate with the program from TeX maybe)?
– ShreevatsaR
Mar 22 '17 at 17:07
|
show 12 more comments
up vote
12
down vote
favorite
up vote
12
down vote
favorite
This is more a suggestion/request than a question:
Someone should write a LaTeX macro that automatically row reduces a matrix until it's in (reduced) echelon form and typesets all the steps. (As far as I can tell, none such exists.)
I'm thinking of something like the gauss package, except that the row reductions themselves are carried out automatically, like in the Linear Algebra Toolkit. This would be similar to the polylongdiv
command in the polynom package, where all one needs to do is enter the polynomials to be divided and the macro carries out the algorithm and typesets the steps.
Of course you might be wondering why I don't just do it myself. Well I guess my answer to that is: some (linear) combination of laziness, busyness, not being the right person for the job, etc.
Thanks, regards, respect, and even a little love. :*
matrices package-writing gauss
This is more a suggestion/request than a question:
Someone should write a LaTeX macro that automatically row reduces a matrix until it's in (reduced) echelon form and typesets all the steps. (As far as I can tell, none such exists.)
I'm thinking of something like the gauss package, except that the row reductions themselves are carried out automatically, like in the Linear Algebra Toolkit. This would be similar to the polylongdiv
command in the polynom package, where all one needs to do is enter the polynomials to be divided and the macro carries out the algorithm and typesets the steps.
Of course you might be wondering why I don't just do it myself. Well I guess my answer to that is: some (linear) combination of laziness, busyness, not being the right person for the job, etc.
Thanks, regards, respect, and even a little love. :*
matrices package-writing gauss
matrices package-writing gauss
edited 2 days ago
Werner
431k599491626
431k599491626
asked Mar 11 '17 at 15:50
Quinn Culver
1453923
1453923
rref is numerically an ill conditioned algorithm. It becomes even worse with TeX precision. The industry standard is LU factorization.
– percusse
Mar 11 '17 at 16:12
@percusse Good point. But I just want something that can handle the simple matrices the students are required to row reduce in the class I teach. Know what I mean?
– Quinn Culver
Mar 11 '17 at 16:18
1
Doesn't matter, you'll get wrong results in even 3x3 matrices. multiply first row with 2/3 and add to the second row even that would screw up all integer valued entries.
– percusse
Mar 11 '17 at 16:29
1
Thesagetex
package relies on the computer algebra system SAGE to calculate the row echelon form of a matrix, even if it has variables and gives you access to a lot of other commands on matrices. However, it doesn't give the steps to get the answer.
– DJP
Mar 11 '17 at 21:44
1
Are you sure you need everything to be done inside TeX itself? Why not rely on an external program for doing all the computation (and communicate with the program from TeX maybe)?
– ShreevatsaR
Mar 22 '17 at 17:07
|
show 12 more comments
rref is numerically an ill conditioned algorithm. It becomes even worse with TeX precision. The industry standard is LU factorization.
– percusse
Mar 11 '17 at 16:12
@percusse Good point. But I just want something that can handle the simple matrices the students are required to row reduce in the class I teach. Know what I mean?
– Quinn Culver
Mar 11 '17 at 16:18
1
Doesn't matter, you'll get wrong results in even 3x3 matrices. multiply first row with 2/3 and add to the second row even that would screw up all integer valued entries.
– percusse
Mar 11 '17 at 16:29
1
Thesagetex
package relies on the computer algebra system SAGE to calculate the row echelon form of a matrix, even if it has variables and gives you access to a lot of other commands on matrices. However, it doesn't give the steps to get the answer.
– DJP
Mar 11 '17 at 21:44
1
Are you sure you need everything to be done inside TeX itself? Why not rely on an external program for doing all the computation (and communicate with the program from TeX maybe)?
– ShreevatsaR
Mar 22 '17 at 17:07
rref is numerically an ill conditioned algorithm. It becomes even worse with TeX precision. The industry standard is LU factorization.
– percusse
Mar 11 '17 at 16:12
rref is numerically an ill conditioned algorithm. It becomes even worse with TeX precision. The industry standard is LU factorization.
– percusse
Mar 11 '17 at 16:12
@percusse Good point. But I just want something that can handle the simple matrices the students are required to row reduce in the class I teach. Know what I mean?
– Quinn Culver
Mar 11 '17 at 16:18
@percusse Good point. But I just want something that can handle the simple matrices the students are required to row reduce in the class I teach. Know what I mean?
– Quinn Culver
Mar 11 '17 at 16:18
1
1
Doesn't matter, you'll get wrong results in even 3x3 matrices. multiply first row with 2/3 and add to the second row even that would screw up all integer valued entries.
– percusse
Mar 11 '17 at 16:29
Doesn't matter, you'll get wrong results in even 3x3 matrices. multiply first row with 2/3 and add to the second row even that would screw up all integer valued entries.
– percusse
Mar 11 '17 at 16:29
1
1
The
sagetex
package relies on the computer algebra system SAGE to calculate the row echelon form of a matrix, even if it has variables and gives you access to a lot of other commands on matrices. However, it doesn't give the steps to get the answer.– DJP
Mar 11 '17 at 21:44
The
sagetex
package relies on the computer algebra system SAGE to calculate the row echelon form of a matrix, even if it has variables and gives you access to a lot of other commands on matrices. However, it doesn't give the steps to get the answer.– DJP
Mar 11 '17 at 21:44
1
1
Are you sure you need everything to be done inside TeX itself? Why not rely on an external program for doing all the computation (and communicate with the program from TeX maybe)?
– ShreevatsaR
Mar 22 '17 at 17:07
Are you sure you need everything to be done inside TeX itself? Why not rely on an external program for doing all the computation (and communicate with the program from TeX maybe)?
– ShreevatsaR
Mar 22 '17 at 17:07
|
show 12 more comments
2 Answers
2
active
oldest
votes
up vote
19
down vote
Update-2
I heard someone said Givens rotations.
% Givens rotation
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotaterow #1 and row #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/w/.get=pgflabw}
pgfplotsforeachungroupedg@j in{1,...,pgflabw}{
pgfkeys{/lab/#5/#1/g@j/.get=pgflabtempentrya}
pgfkeys{/lab/#5/#2/g@j/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/#1/g@j/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/#2/g@j/.let=pgfmathresult}
}
}
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotatecol #1 and col #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/h/.get=pgflabh}
pgfplotsforeachungroupedg@i in{1,...,pgflabh}{
pgfkeys{/lab/#5/g@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#5/g@i/#2/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#1/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#2/.let=pgfmathresult}
}
}
% A = QR decomposition
defpgflabQRdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/w/.get=pgflabW}
pgfkeys{/lab/#1/h/.get=pgflabH}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set Q as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% copy A to R
% copy #1 to #3
pgflabcopymatrix {#1} to {#3}
% forget A, do job at Q and R
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh}{
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempradius{sqrt(pgflabtempentrya*pgflabtempentrya+pgflabtempentryb*pgflabtempentryb)}
pgfmathsetmacropgflabtempcos{ pgflabtempentrya/pgflabtempradius} % cosine
pgfmathsetmacropgflabtempsin{-pgflabtempentryb/pgflabtempradius} % sine
pgflabgivensrotaterow {d@i} and row {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#3}
pgflabgivensrotatecol {d@i} and col {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#2}
eliminate one entry. check Q and Rpar
$Q=pgflabtypeset{#2};$
$R=pgflabtypeset{#3};$
}
}
}
pgflabread{A}{
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
}
pgflabQRdecompose A as Q times R
For a 10 by 10 random matrix, the norm of A - QR is about 4e-4. The norm of QQᵀ - I is about 2e-4.
Update-1: New Answer
I implement three decompositions:
A = LU
A = PLU (i.e. partial pivoting)
A = PLUQ (i.e. complete pivoting)
If A is m by n, then P, L are m by m; U is the same as A; and Q is n by n.
Advantages
The complexity of accessing a matrix entry is O(1). (Assuming
csname
is O(1)). So the complexity of decompositions is O(m²n).The input utilize
pgfplotstableread
from pgfplotstable. So it accepts inline-table, file, loaded table, and even the table created bypgfplotstablenew
. You can also pass options to it. (such as filtering)The output utilize
pgfplotstabletypeset
from the same package. Or you can convert the matrix back to a table and do whatever you want.The calculation is done by
pgfmathparse
. I assume FPU is on. But one can reimplement that.There is a debug macro that output the raw data of matrices. You can copy and paste those data into whatever modern matrix calculator.
According to Wikipeida, even partial pivoting is numerically stable in practice. I test a 10 by 10 random matrix and check A - PLUQ in sage; the norm is about 1.1e-6. (This is about the precision of FPU)
documentclass{article}
usepackage[a3paper,landscape,margin=1cm]{geometry}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu,/pgf/number format/fixed}
begin{document}
makeatletter
% pgfmatrix... is used
% we use pgflab...
% call pgfplotstable to read the data
% put options in if desired
% the options go to pgfplotstableread
defpgflabread{
pgfutil@ifnextchar[
{pgflabread@opt}
{pgflabread@opt}
}
% #1: optional option
% #2: a name of the matrix... usually A
defpgflabread@opt[#1]#2{
edefpgflabname{#2}
pgfplotstableread[header=false,#1]
}
% we did not provide a macro to pgfplotstable to store the table
% we give it a temporary one called pgflabtemptable
% and then copy it to our data structure
longdefpgfplotstableread@impl@collectfirstarg#1#2{
pgfplotstableread@impl@{#1}{#2}pgflabtemptable
pgflabconverttablepgflabtemptable to matrix{pgflabname}
}
% this helps us to deal with pgfleys
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% copy pgfplotstable table to our data structure in pgfkeys
% #1: the macro that pgfplotstable used to store the table
% #2: a name of the matrix
defpgflabconverttable#1to matrix#2{
% extract height and width
pgfplotstablegetrowsof#1xdefpgflabh{pgfplotsretval}pgfkeys{/lab/#2/h/.let=pgflabh}
%%%height = pgflabh par
pgfplotstablegetcolsof#1xdefpgflabw{pgfplotsretval}pgfkeys{/lab/#2/w/.let=pgflabw}
%%%width = pgflabw par
% extract entries
% c@i and c@j cannot be used outside
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
% since fpu is on, this is easier way to do 9-1
pgfplotstablegetelem{thenumexprc@i-1}{thenumexprc@j-1}ofpgflabtemptable
pgfkeys{/lab/#2/c@i/c@j/.let=pgfplotsretval}
%%%pgfplotsretval,
}
%%%; par
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
% the opposite of the previous one
% #1: the name of the matrix
% #2: a macro for pgfplotstable to store the table
defpgflabconvertmatrix #1 to table #2{
% makeup meta data
expandafterdefcsnamestring#2@@table@nameendcsname{<inline_table>}
% build a new list of columns
pgfkeys{/lab/#1/h/.get=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}
pgfplotslistnew#2{0,...,thenumexprpgflabw-1}
% fill in columns
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
pgfplotslistnewemptypgflabtempcolumn
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfkeys{/lab/#1/c@i/c@j/.get=pgflabtempentry}
expandafterpgfplotslistpushbackpgflabtempentrytopgflabtempcolumn
}
edefc@k{thenumexprc@j-1}
expandafterletcsnamestring#2@c@kendcsnamepgflabtempcolumn
}
}
% typeset the matrix by pgfplotstabletypeset
defpgflabtypeset{
pgfutil@ifnextchar[
{pgflabtypeset@opt}
{pgflabtypeset@opt}
}
% #1: optional option
% #2: the name of the matrix
defpgflabtypeset@opt[#1]#2{
pgflabconvertmatrix #2 to table pgflabtemptable
pgfplotstabletypeset[every head row/.style={output empty row}]pgflabtemptable
}
Matrix A is
$A=pgflabtypeset{A}$
% define row operation: switch
% does not check boundary
defpgflabswitchrow #1 and row #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupeds@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/s@j/.get=pgflabtempentrya}
pgfkeys{/lab/#3/#2/s@j/.get=pgflabtempentryb}
pgfkeys{/lab/#3/#1/s@j/.let=pgflabtempentryb}
pgfkeys{/lab/#3/#2/s@j/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchrow 1 and row 3 in A
switch row 1 and row 3;
$A=pgflabtypeset{A}$
% define column operation: switch
% does not check boundary
defpgflabswitchcol #1 and col #2 in #3{
pgfkeys{/lab/#3/h/.get=pgflabh}
pgfplotsforeachungroupeds@i in{1,...,pgflabh}{
pgfkeys{/lab/#3/s@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#3/s@i/#2/.get=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#1/.let=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#2/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchcol 2 and col 3 in A
switch col 2 and col 3;
$A=pgflabtypeset{A}$
% define row operation: multiplication
% does not check boundary
defpgflabmultiplyrow #1 by #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupedm@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/m@j/.get=pgflabtempentry}
pgfmathparse{pgflabtempentry*#2}
pgfkeys{/lab/#3/#1/m@j/.let=pgfmathresult}
}
}
bigskip
pgflabmultiplyrow 3 by -1 in A
multiply row 3 by -1;
$A=pgflabtypeset{A}$
% define row operation: addition
% does not check boundary
defpgflabaddrow #1 by row #2 times #3 in #4{
pgfkeys{/lab/#4/w/.get=pgflabw}
pgfplotsforeachungroupeda@j in{1,...,pgflabw}{
pgfkeys{/lab/#4/#1/a@j/.get=pgflabtempentrya}
pgfkeys{/lab/#4/#2/a@j/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/#1/a@j/.let=pgfmathresult}
}
}
bigskip
pgflabaddrow 2 by row 3 times 2 in A
add row 2 by row 3 times 2;
$A=pgflabtypeset{A}$
% define column operation: addition
% does not check boundary
defpgflabaddcol #1 by col #2 times #3 in #4{
pgfkeys{/lab/#4/h/.get=pgflabh}
pgfplotsforeachungroupeda@i in{1,...,pgflabh}{
pgfkeys{/lab/#4/a@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#4/a@i/#2/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/a@i/#1/.let=pgfmathresult}
}
}
bigskip
pgflabaddcol 5 by col 4 times -1 in A
add col 5 by row 4 times -1;
$A=pgflabtypeset{A}$
% new identity matrix
defpgflabneweyeof #1 by #2 as #3{
defpgflabh{#1}pgfkeys{/lab/#3/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#3/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
ifnumn@i=n@j
pgfkeys{/lab/#3/n@i/n@i/.initial=1}
else
pgfkeys{/lab/#3/n@i/n@j/.initial=0}
fi
}
}
}
bigskip
pgflabneweyeof 4 by 4 as I
identity matrix;
$A=pgflabtypeset{I}$
bigskip
pgflabneweyeof 3 by 5 as B
rectangular identity matrix;
$B=pgflabtypeset{B}$
% copy matrix
defpgflabcopymatrix #1 to #2{
pgfkeys{/lab/#1/h/.get=pgflabh}pgfkeys{/lab/#2/h/.let=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}pgfkeys{/lab/#2/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfkeys{/lab/#1/n@i/n@j/.get=pgflabtempentry}
pgfkeys{/lab/#2/n@i/n@j/.let=pgflabtempentry}
}
}
}
bigskip
pgflabcopymatrix A to B
copy matrix A to B;
$B=pgflabtypeset{B}$
% LU decomposition
% if encounter 0, probably will result in inf or nan
defpgflabLUdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/h/.get=pgflabh@u}
pgfkeys{/lab/#1/w/.get=pgflabw@u}
% decide the loop boundary
edefpgflabh@v{thenumexprpgflabh@u-1}
ifnumpgflabh@v>pgflabw@u
edefpgflabh@v{pgflabw@u}
fi
% set L as identity
% set #2 as identity
pgflabneweyeof {pgflabh@u} by {pgflabh@u} as #2
% copy A to U
% copy #1 to #3
pgflabcopymatrix #1 to #3
% forget A, do job at L and U
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflabh@v}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh@u}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#2}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#3}
medskip
eliminate one entry. check L and U par
$L=pgflabtypeset{#2};$
$U=pgflabtypeset{#3};$
}
}
}
clearpage
$A=pgflabtypeset{A}$
pgflabLUdecompose A as L times U
% find pivot in the specific column
% find pivot in the range (#1,#1) to (#1,end)
% does not check boundary
defpgflabfindpivotatcol #1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfkeys{/lab/#2/f@i/#1/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
fi
}
}
clearpage
$A=pgflabtypeset{A}$
find pivot at specific column: par
pgflabfindpivotatcol 1 in A
at col 1 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 2 in A
at col 2 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 3 in A
at col 2 it is pgflabtempmax at row pgflabtempindex
% A = PLU decomposition
% partial pivoting
defpgflabPLUdecompose #1 as #2 times #3 times #4{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotatcol {d@i} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
parmedskip
switch d@i{} and pgflabtempindexpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
parmedskip
eliminate one column. check P and L and U par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
bigskip
pgflabPLUdecompose A as P times L times U
% find pivot in the specific column and row
% find pivot in the range (#1,#1) to (end,end)
% does not check boundary
defpgflabfindpivotafter#1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
pgfkeys{/lab/#2/w/.get=pgflabw}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
defpgflabtempjndex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfplotsforeachungroupedf@j in{#1,...,pgflabw}{
pgfkeys{/lab/#2/f@i/f@j/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
letpgflabtempjndexf@j
fi
}
}
}
% A = PLUQ decomposition
% partial pivoting
defpgflabPLUQdecompose #1 as #2 times #3 times #4 times #5{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% set Q as identity
% set #5 as identity
pgflabneweyeof {pgflabW} by {pgflabW} as {#5}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotafter {d@i} in #4
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
{}
pgflabswitchcol {d@i} and col {pgflabtempjndex} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempjndex} in {#5}
switch (d@i{},d@i{}) and (pgflabtempindex,pgflabtempjndex) par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
eliminate one column. check P and L and U and Qpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
}
}
pgflabread{A}{
3 -7 5 0 1 0 1
-9 -8 -2 9 -1 9 -4
4 6 0 -1 -2 -1 -3
-5 2 -6 7 8 7 8
-1 -2 -1 -3 4 6 0
7 8 7 8 -5 2 -6
}
bigskip
$A=pgflabtypeset{A}$
pgflabPLUQdecompose A as P times L times U times Q
% new matrix with desired entry
% entry can contain n@i and n@j
defpgflabnewmatrixof #1 by #2 with #3 as #4{
defpgflabh{#1}pgfkeys{/lab/#4/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#4/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfmathparse{#3}
pgfkeys{/lab/#4/n@i/n@j/.let=pgfmathresult}
}
}
}
clearpage
pgflabnewmatrixof 10 by 10 with rand as C
pgflabPLUQdecompose C as P times L times U times Q
% debug macro
% we can pass it to sage
% but we need to replace negative sign by ascii's -
defpgflabrawoutput#1{%
pgfkeys{/lab/#1/h/.get=pgflabh}%
pgfkeys{/lab/#1/w/.get=pgflabw}%
matrix([%
pgfplotsforeachungroupedt@i in{1,...,pgflabh}{%
[%
pgfplotsforeachungroupedt@j in{1,...,pgflabw}{%
pgfkeys{/lab/#1/t@i/t@j/.get=pgflabtempentry}%
pgfmathparse{pgflabtempentry}%
pgfmathfloattosci{pgfmathresult}%
mbox{pgfmathresult}%
ifnumt@j<pgflabw,hskip1ptplus3ptallowbreakfi
}%
]%
ifnumt@i<pgflabh,hskip1ptplus3ptallowbreakfi
}%
])%
}
clearpage
C=pgflabrawoutput{C};par
P=pgflabrawoutput{P};par
L=pgflabrawoutput{L};par
U=pgflabrawoutput{U};par
Q=pgflabrawoutput{Q};par
(C-P*L*U*Q).norm()
end{document}
debug mode
Old Answer
I would like to try
documentclass{article}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu}
begin{document}
% we are lazy
% let pgfplotstable read the matrix
pgfplotstableread[header=false]{
8 1 6 8
3 5 7 5
4 9 2 7
}matrixA
% we will store data by pgfleys
% create a handy handler
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% PS: /.initial is more like def, but we want xdef or edef or let
% but we also need some fast macros
pgfplotstablegetrowsofmatrixA xdefmatrixheight{pgfplotsretval}pgfkeys{/matrix/A/height/.let=matrixheight}
pgfplotstablegetcolsofmatrixA xdefmatrixwidth{pgfplotsretval} pgfkeys{/matrix/A/width/.let=matrixwidth}
% check data
Matrix $A$ is pgfkeys{/matrix/A/height} by pgfkeys{/matrix/A/width}.
In other words: par Matrix $A$ is matrixheight{} by matrixwidth{}.
% store the entries into pgfkeys
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
% since fpu is on, this is easier way to do 9+1
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
% check data
bigskip The matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: switch
defrowoperationswitch#1and#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfkeys{/matrix/A/#1/j/.let=tempmatrixentryB}
pgfkeys{/matrix/A/#2/j/.let=tempmatrixentryA}
}
}
% try and check
rowoperationswitch3and2
bigskip After switching row3 and row2, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: multiplication
defrowoperationmultiply#1by#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry*#2}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationmultiply3by9
bigskip After multiplying row3 by 9, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
remember: fpu is on! par
Human readable version par
defpgfmathprintmatrix{
pgfplotsforeachungroupedi in{1,...,matrixheight}{
indent
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry}
clap{pgfmathprintnumber{pgfmathresult}}hskip20pt
}
par
}
}
pgfmathprintmatrix
% define row operation: addition
defrowoperationadd#1by#2times#3 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfmathparse{tempmatrixentryA+tempmatrixentryB*#3}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationadd1by2times-1
bigskip After adding row2 by row1 times -1, the matrix entries are: par
pgfmathprintmatrix
% We do RREF by hand
pgfkeys{/pgf/number format/fixed}
bigskip We do RREF by hand par
add 2 by 1 times -1: par
rowoperationadd2by1times-1
pgfmathprintmatrix
medskip add 3 by 1 times -6.75: par
rowoperationadd3by1times-6.75
pgfmathprintmatrix
medskip add 3 by 2 times -5.8235: par
rowoperationadd3by2times-5.8235
pgfmathprintmatrix
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% Automatic RREF without row switching
% I is different form i
xdefmatrixheightminusone{thenumexprmatrixheight-1}
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
entry [I][I] is tempmatrixentryA par
entry [J][I] is tempmatrixentryB par
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
message{^^J^^JI,J,temprowscaler^^J^^J}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% maybe we need pivoting
defrowoperationfindpivot{
% find the maximal element in this column
defmaxofthiscolumn{-inf}
defmaxofthiscolumnindex{0}
pgfplotsforeachungroupedK in{I,...,matrixheight}{
pgfkeys{/matrix/A/K/I/.get=tempmatrixentry}
% compare
pgfmathparse{abs(tempmatrixentry)}
lettempmatrixabsentrypgfmathresult
pgfmathparse{maxofthiscolumn<tempmatrixabsentry}
% update if necessary
ifpgfmathfloatcomparison
letmaxofthiscolumntempmatrixabsentry
letmaxofthiscolumnindexK
fi
}
}
xdefI{1}
rowoperationfindpivot
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex
% Automatic RREF with partial pivot
defRREFwithpivoting{
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
rowoperationfindpivot
rowoperationswitchI and{maxofthiscolumnindex}
bigskip
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex par
so we switch rowI{} and rowmaxofthiscolumnindex, the matrix entries are: par
pgfmathprintmatrix
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
}
RREFwithpivoting
.......
The rest is deleted because of the length limitation.
By the way, the product of pivots is -862566.82. And by Wolfram|Alpha the determinant of the first six columns is 862575. So the error is .0009%
– Symbol 1
Mar 22 '17 at 21:09
add a comment |
up vote
19
down vote
This answer has some macros picked up from https://tex.stackexchange.com/a/143035/4686. I am not too happy with some internal data structure, but I decided to live it standing.
The https://tex.stackexchange.com/a/143035/4686 computes determinants, inverses, etc..., either exactly or with float operations.
Here I focus on exact computations. The matrix entries may be integers, fractions, decimal numbers, or in scientific notation, but they are handled exactly. Hence, there is no question of numerical instability here. Regarding input, the format is with semi-colon separated rows of comma separated coefficients.
The last edit improves some internal aspects, has a better example for A=PLUQ
, and redoes the initial example of Row Reduction to use for display truncated, not rounded, decimal expansions as they are followed with dots.
PLUQ ANSWER
The code typesets with TeX and also outputs to a file in Maple matrix notation
the final result, for example.
A:=Matrix([[3, 1, -7, 5, 0, 9, -9, 7, -5], [-9, -4, 22, -14, 9, 2, 7, -6, -8], [-6, -3, 15, -9, 9, 11, -2, 1, -13], [-5, 8, 2, -18, 7, -1, 8, -7, 0], [4, 6, -14, 2, 1, -5, 6, 5, -3], [-11, 5, 17, -27, 16, 10, 6, -6, -13]]);
P:=Matrix([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1]]);
L:=Matrix([[1, 0, 0, 0, 0, 0], [-3, 1, 0, 0, 0, 0], [-5/3, -29/3, 1, 0, 0, 0], [4/3, -14/3, 43/94, 1, 0, 0], [-2, 1, 0, 0, 1, 0], [-11/3, -26/3, 1, 0, 0, 1]]);
U:=Matrix([[3, 1, 0, 9, -7, 5, -9, 7, -5], [0, -1, 9, 29, 1, 1, -20, 15, -23], [0, 0, 94, 883/3, 0, 0, -601/3, 449/3, -692/3], [0, 0, 0, -1533/94, 0, 0, 1533/94, -263/94, 87/47], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]);
Q:=Matrix([[1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1]]);
Now we can copy paste into Maple and check that indeed A=PLUQ
:
> with(LinearAlgebra):
> MatrixAdd(A,-P.L.U.Q);
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
Notice that in a PLUQ decomposition, a P and a Q will appear with my code only if necessary.
documentclass[a4paper]{article}
usepackage[hscale=0.85, vscale=0.85]{geometry}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
% usepackage {siunitx}
% usepackage {numprint}
catcode`_ 11
makeatletter
newwriteMATout
immediateopenoutMATout=jobname.pluqoutrelax
% (the typeout format is for input in Maple for example)
defMATtypeout {MATtypeoutwith {MATtypeoutone}}%
defMATtypeoutone #1{xintPRaw{xintRawWithZeros{#1}}}% (lacking an xintPRawWithZeros)
defMATtypeoutwith #1#2#3{%
edefI{xintSeq {1}{#3[I]}}% indices for rows
edefJ{xintSeq {1}{#3[J]}}% indices for columns
immediatewriteMATout{#2:=Matrix([[%
xintListWithSep {], [}{xintApply { MAT_typeout_row {#1}#3}{I}}%
]]);}%
}%
defMAT_typeout_row #1#2#3{%
xintListWithSep {, }{xintApply { MAT_typeout_one {#1}#2{#3}}{J}}%
}%
defMAT_typeout_one #1#2#3#4{#1{#2[#3,#4]}}%
% we don't need all of them
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
% The final semi-colon is optional.
% We indeed focus here on manipulating matrices with rational entries, the
% code at https://tex.stackexchange.com/a/143035/4686 has the set-up for
% floating point numbers too (in an arbitrary, user decided precision).
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% a bit convoluted, no comments.
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
% removed toks2 et toks4 usage from https://tex.stackexchange.com/a/143035/4686
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% We need identity matrices.
% again copied as is from https://tex.stackexchange.com/a/143035/4686
% IDENTITY MATRIX
% usage MATidfoo{37} defines a 37 times 37 identity matrix.
defMATid {defMAT_tmpf{/1}MAT_id }%
%defMATfloatid {defMAT_tmpf{}MAT_id }%
% This identity matrix insists on coefficients written internally
% 0[0] or 1[0], this is a remnant of
% https://tex.stackexchange.com/a/143035/4686 whose aim is is minuscule
% optimization when these numbers are involved in computations done by
% the xintfrac macros.
defMAT_id #1#2{%
MAT_cntc #2relax
MAT_cnta xint_c_i % 1
xintloop
{expandafterdefexpandafterMAT_tmpa expandafter{theMAT_cnta}%
MAT_cntb xint_c_i % 1
xintloop
expandafteredef
csname MAT@string#1{MAT_tmpa}{theMAT_cntb}endcsname
{ifnumMAT_cntb=MAT_cnta 1else 0fi MAT_tmpf[0]}%
ifnumMAT_cntb<MAT_cntc
advanceMAT_cntb xint_c_i
repeat
ifnumMAT_cnta<MAT_cntc
advanceMAT_cnta xint_c_i
}repeat
expandafterdefcsname MAT@string#1{I}expandafterendcsname
expandafter {theMAT_cntc}%
expandafterdefcsname MAT@string#1{J}expandafterendcsname
expandafter {theMAT_cntc}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% EXCHANGING ROWS OR COLUMNS OF A GIVEN MATRIX
defMATexchangecol #1#2#3{%
MAT_cnta=#3[I]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{theMAT_cntb}{#1}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#1}expandafterendcsname
csname MAT@string#3{theMAT_cntb}{#2}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#2}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
% perhaps only columns "to the right" actually need exchange in usage of this
defMATexchangerow #1#2#3{%
MAT_cnta=#3[J]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
defMATexchangerowspecial #1#2#3{%#1>#2, only columns <#2 need update
MAT_cnta=#2relax
MAT_cntb=xint_c_ % 0
xintloop
advanceMAT_cntbxint_c_i
ifnumMAT_cntb<MAT_cnta
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
repeat
}%
% Usage:
% MATpluqA (A previously defined by MATset)
% Effect: sets P, L, U, Q, to matrices in the sense of MATset,
% so that "A=PLUQ" and it writes all matrices out
% to some file. See initial answer about row reduction for typesetting
% in document.
% The code is a simple adaptation of this initial answer. Now I use MATpluq
% prefix.
defMATpluq #1{%
% begingroup
MATlet@U#1%
edefMATpluq@rows{@U[I]}% nb of rows
edefMATpluq@cols{@U[J]}% nb of columns.
MATid@PMATpluq@rows
MATid@LMATpluq@rows
MATid@QMATpluq@cols
defMATpluq@pivrow {0}%
defMATpluq@pivcol {0}%
%edefMATpluq@name {string#1}%
letMATpluq@ifcontinueiftrue
% Starting the reduction.
MATtypeout{^^JA}#1%
[A = MATdisplay@U]
xintloop
% Nota Bene: in the PLUQ reduction, the pivots are anyhow organized
% along the main diagonal so pivrow and pivcol will be kept in sync over
% the execution of the algorithm but we use two variables nevertheless.
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
edefMATpluq@pivcol{thenumexprMATpluq@pivcol+xint_c_i}%
MATpluq@dopiv
MATpluq@ifcontinue
repeat
% Done. The rank of the matrix is thenumexprMATpluq@pivrow-xint_c_i.par
% endgroup
MATtypeout{P}@P
MATtypeout{L}@L
MATtypeout{U}@U
MATtypeout{Q}@Q
[ P = MATdisplay@P]
[ L = MATdisplay@Lqquad U = MATdisplay@U]
[ Q = MATdisplay@Q]
}
defMATpluq@done {letMATpluq@ifcontinueiffalse}
% Remark on algorithm: I hesitated about doing column permutations first,
% rather than row permutations with the idea to recognize faster an entirely
% vanishing row, so that we can put it at the end and ignore it entirely, in
% effect reducing the number of rows by one, and possibly making algorithm
% faster. But for simplicity I just keep algorithm close to the one as in my
% initial answer. We only have to keep track in P, L, Q of the needed
% operations.
defMATpluq@dopiv{%
letMATpluq@rowMATpluq@pivrow
letMATpluq@colMATpluq@pivcol
ifnumMATpluq@row>MATpluq@rowsrelax
MATpluq@done
else
ifnumMATpluq@col>MATpluq@colsrelax
MATpluq@done
else
expandafterexpandafterexpandafterMATpluq@dopiv@i
fi
fi
}
defMATpluq@dopiv@i{%
edefMATpluq@piv@value{@U[MATpluq@row,MATpluq@col]}%
xintifZero{MATpluq@piv@value}
MATpluq@dopiv@steprow
MATpluq@dopiv@ii
}
defMATpluq@dopiv@steprow{%
ifnumMATpluq@row=MATpluq@rowsrelax
par No pivot found in column MATpluq@col.par
letMATpluq@rowMATpluq@pivrow
expandafterMATpluq@dopiv@stepcol
else
edefMATpluq@row{thenumexprMATpluq@row+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
defMATpluq@dopiv@stepcol{%
ifnumMATpluq@col=MATpluq@colsrelax
MATpluq@done
else
edefMATpluq@col{thenumexprMATpluq@col+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
% found a pivot
defMATpluq@dopiv@ii{%
Pivot MATpluqprintonevalue{MATpluq@piv@value} at MATpluq@row, MATpluq@col.par
ifnumMATpluq@col>MATpluq@pivcolrelax
Exchange of columns MATpluq@pivcolspace and MATpluq@col.par
MATexchangerow{MATpluq@col}{MATpluq@pivcol}@Q
MATexchangecol{MATpluq@col}{MATpluq@pivcol}@U
[U = MATdisplay@Uqquad Q = MATdisplay@Q]
fi
ifnumMATpluq@pivrow=MATpluq@rowsrelax
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
MATpluq@done
else
expandafterMATpluq@dopiv@iii
fi
}
defMATpluq@dopiv@iii{%
ifnumMATpluq@row>MATpluq@pivrowrelax
Exchange of rows MATpluq@pivrowspace and MATpluq@row.par
MATexchangecol{MATpluq@row}{MATpluq@pivrow}@P
MATexchangerow{MATpluq@row}{MATpluq@pivrow}@U
MATexchangerowspecial{MATpluq@row}{MATpluq@pivrow}@L
[L = MATdisplay@Lqquad U = MATdisplay@U]
[P = MATdisplay@P]
fi
MAT_cntcMATpluq@pivrowrelax% we are guaranteed < nb of rows
xintloop
advanceMAT_cntcxint_c_i
edefMATpluq@entry{@U[MAT_cntc,MATpluq@pivcol]}%
xintifZeroMATpluq@entry
{% nothing to do, the L coeff is already set to zero
}%
{edefMATpluq@ratio
{xintIrr{xintDiv{MATpluq@entry}{MATpluq@piv@value}}[0]}%
expandafterlet
csname MAT@string@L{theMAT_cntc}{MATpluq@pivcol}endcsname
MATpluq@ratio
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATpluqprintonevalue{MATpluq@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATpluq@pivcol}}{0[0]}%
MAT_cntdMATpluq@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATpluq@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATpluq@ratio}{@U[MATpluq@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATpluq@rows=MAT_cntc
repeat
[L = MATdisplay@Lqquad U = MATdisplay@U]
}
defMATpluqprintonevalue{xintPRaw}
%defMATpluqdisplay#1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}pagestyle{empty}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 1/11 ; }
MATpluqMatrixA
See pluqout file.clearpage
MATsetA {
3, -7, 5, 0, 1, 0, 1;
-9, -8, -2, 9, -1, 9, -4;
4, 6, 0, -1, -2, -1, -3;
-5, 2, -6, 7, 8, 7, 8;
-1, -2, -1, -3, 4, 6, 0;
7, 8, 7, 8, -5, 2, -6;
}
MATpluqA
See pluqout file.clearpage
MATsetA {
2, 0, 3, 0;
1, 0, 0, 0;
0, 0, 4, 0;
0, 2, 0, 1;
}
MATpluqA
See pluqout file.clearpage
MATsetMatrixB {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
4, -3, 6, 0, -1, 5, -4, -3, 4;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
3, 6, -2, -1, 8, -2, -6, 7, -7;
4, 6, 3, -9, 1, -5, 0, 5, -3;
}
MATpluqMatrixB
See pluqout file.clearpage
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, 22, -14, 9, 2, 7, -6, -8;
-6, -3, 15, -9, 9, 11, -2, 1, -13;
-5, 8, 2, -18, 7, -1, 8, -7, 0;
4, 6, -14, 2, 1, -5, 6, 5, -3;
-11, 5, 17, -27, 16, 10, 6, -6, -13;
}
MATpluqMatrixC
See pluqout file for the matrices in Maple format.clearpage
immediatecloseoutMATout
end{document}
ROW REDUCTION (INITIAL) ANSWER
I have improved a bit some internal aspects of the code in an edit.
documentclass{article}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
catcode`_ 11
makeatletter
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
%
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
defMATrowreduce #1{%
begingroup
edefMATrr@rows{#1[I]}%
edefMATrr@cols{#1[J]}%
defMATrr@pivrow {0}%
defMATrr@pivcol {0}%
MATlet@U #1%
letMATrr@ifcontinueiftrue
Starting the reduction.
MATrrdisplaymatrix@U
xintloop
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}%
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
MATrr@dopiv
MATrr@ifcontinue
repeat
Done. The rank of the matrix is thenumexprMATrr@pivrow-xint_c_i.par
endgroup
}
defMATrr@done {letMATrr@ifcontinueiffalse}
defMATrr@dopiv{%
letMATrr@rowMATrr@pivrow
letMATrr@colMATrr@pivcol
ifnumMATrr@row>MATrr@rowsrelax
MATrr@done
else
ifnumMATrr@col>MATrr@colsrelax
MATrr@done
else
expandafterexpandafterexpandafterMATrr@dopiv@i
fi
fi
}
defMATrr@dopiv@i{%
edefMATrr@piv@value{@U[MATrr@row,MATrr@pivcol]}%
xintifZero{MATrr@piv@value}
MATrr@dopiv@steprow
MATrr@dopiv@ii
}
defMATrr@dopiv@steprow{%
ifnumMATrr@row=MATrr@rowsrelax
letMATrr@rowMATrr@pivrow
par No pivot found in column MATrr@pivcol.par
expandafterMATrr@dopiv@stepcol
else
edefMATrr@row{thenumexprMATrr@row+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@stepcol{%
ifnumMATrr@pivcol=MATrr@colsrelax
MATrr@done
else
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@ii{%
ifnumMATrr@pivrow=MATrr@rowsrelax
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}MATrr@done
else
expandafterMATrr@dopiv@iii
fi
}
defMATrr@dopiv@iii{%
Now using the pivot with value MATrrprintonevalue{MATrr@piv@value}
at row MATrr@rowspace and column MATrr@pivcol.par
ifnumMATrr@row>MATrr@pivrowrelax
Exchange of row MATrr@rowspace with row MATrr@pivrow.par
MAT_cntb=MATrr@pivcolrelax
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string@U{MATrr@row}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@row}{theMAT_cntb}expandafterendcsname
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
MAT@tmp
ifnumMATrr@cols>MAT_cntb
advanceMAT_cntbxint_c_i
repeat
MATrrdisplaymatrix@Upar
fi
MAT_cntcMATrr@pivrow
xintloop
advanceMAT_cntcxint_c_i
edefMATrr@entry{@U[MAT_cntc,MATrr@pivcol]}%
xintifZeroMATrr@entry
{}%
{edefMATrr@ratio{xintIrr{xintDiv{MATrr@entry}{MATrr@piv@value}}[0]}%
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATrrprintonevalue{MATrr@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATrr@pivcol}}{0[0]}%
MAT_cntdMATrr@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATrr@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATrr@ratio}{@U[MATrr@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATrr@rows=MAT_cntc
repeat
MATrrdisplaymatrix@U
}
defMATrrprintonevalue{xintPRaw}
defMATrrdisplaymatrix #1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
-6, -3, -15, 3, 9, 11, -1, 1, -13;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
4, 6, 3, -9, 1, -5, 0, 5, -3;
-11, 5, -13, -3, 16, 10, 0, -6, -13;
}
MATrowreduceMatrixC
end{document}
Entries may be decimal numbers like 37.156
.
defMATrrprintonevalue{xintRound{2}}
defMATrrdisplaymatrix #1{[MATdisplaywith{xintRound{2}}#1]}%
Example (as I add dots, I use truncating rather than rounding):
defMATrrprintonevalue#1{xintTrunc{3}{#1}dots (=xintPRaw{#1})}
defMATrrdisplaymatrix #1{[MATdisplay#1=MATdisplaywith{TruncWithDots{3}}#1]}%
defTruncWithDots #1#2{xintTrunc{#1}{#2}...}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 0.09 ; }
MATrowreduceMatrixA
It is important to stress relative toPLUQ
(and even to Row Reduction) that I am doing exact computations, thus there is no need for the algorithm to look for the largest possible pivot, with the attendant fact that entries ofL
would be smaller than one in absolute value. Thus I do not follow here the algorithm fashionable in numerical analysis (by the way, it may in exceptional cases have stability problems, theU
matrix containing exponentially big entries respective to the size of the matrices). I just pick up the first available pivot irrespective of its size.
– jfbu
Mar 25 '17 at 14:49
don't you want Bruhat decomposition of invertible matrices ? that will be a challenge to numerical analysis, but no problem for exact computations.
– jfbu
Mar 25 '17 at 14:57
by the way Maple'sLUDecomposition
operates like this: For Matrices with... at least one floating-point entry, pivots ... according to absolute magnitude. For Matrices with ... rational... entries, pivots ... the first nonzero element in the current column.. and indeed this is what I did here. One can check that withp, l, u := LUDecomposition(A);
one does getp=P
,l = L
, andu = U.Q
, whereP, L, U, Q
are computed as above. (Maple16)
– jfbu
Mar 25 '17 at 18:01
At first glance, this looks great. I'll need to give it a more thorough inspection before I reward you with the 500 reputation and risqué pics though. ;)
– Quinn Culver
Mar 25 '17 at 22:30
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
19
down vote
Update-2
I heard someone said Givens rotations.
% Givens rotation
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotaterow #1 and row #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/w/.get=pgflabw}
pgfplotsforeachungroupedg@j in{1,...,pgflabw}{
pgfkeys{/lab/#5/#1/g@j/.get=pgflabtempentrya}
pgfkeys{/lab/#5/#2/g@j/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/#1/g@j/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/#2/g@j/.let=pgfmathresult}
}
}
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotatecol #1 and col #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/h/.get=pgflabh}
pgfplotsforeachungroupedg@i in{1,...,pgflabh}{
pgfkeys{/lab/#5/g@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#5/g@i/#2/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#1/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#2/.let=pgfmathresult}
}
}
% A = QR decomposition
defpgflabQRdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/w/.get=pgflabW}
pgfkeys{/lab/#1/h/.get=pgflabH}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set Q as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% copy A to R
% copy #1 to #3
pgflabcopymatrix {#1} to {#3}
% forget A, do job at Q and R
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh}{
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempradius{sqrt(pgflabtempentrya*pgflabtempentrya+pgflabtempentryb*pgflabtempentryb)}
pgfmathsetmacropgflabtempcos{ pgflabtempentrya/pgflabtempradius} % cosine
pgfmathsetmacropgflabtempsin{-pgflabtempentryb/pgflabtempradius} % sine
pgflabgivensrotaterow {d@i} and row {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#3}
pgflabgivensrotatecol {d@i} and col {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#2}
eliminate one entry. check Q and Rpar
$Q=pgflabtypeset{#2};$
$R=pgflabtypeset{#3};$
}
}
}
pgflabread{A}{
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
}
pgflabQRdecompose A as Q times R
For a 10 by 10 random matrix, the norm of A - QR is about 4e-4. The norm of QQᵀ - I is about 2e-4.
Update-1: New Answer
I implement three decompositions:
A = LU
A = PLU (i.e. partial pivoting)
A = PLUQ (i.e. complete pivoting)
If A is m by n, then P, L are m by m; U is the same as A; and Q is n by n.
Advantages
The complexity of accessing a matrix entry is O(1). (Assuming
csname
is O(1)). So the complexity of decompositions is O(m²n).The input utilize
pgfplotstableread
from pgfplotstable. So it accepts inline-table, file, loaded table, and even the table created bypgfplotstablenew
. You can also pass options to it. (such as filtering)The output utilize
pgfplotstabletypeset
from the same package. Or you can convert the matrix back to a table and do whatever you want.The calculation is done by
pgfmathparse
. I assume FPU is on. But one can reimplement that.There is a debug macro that output the raw data of matrices. You can copy and paste those data into whatever modern matrix calculator.
According to Wikipeida, even partial pivoting is numerically stable in practice. I test a 10 by 10 random matrix and check A - PLUQ in sage; the norm is about 1.1e-6. (This is about the precision of FPU)
documentclass{article}
usepackage[a3paper,landscape,margin=1cm]{geometry}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu,/pgf/number format/fixed}
begin{document}
makeatletter
% pgfmatrix... is used
% we use pgflab...
% call pgfplotstable to read the data
% put options in if desired
% the options go to pgfplotstableread
defpgflabread{
pgfutil@ifnextchar[
{pgflabread@opt}
{pgflabread@opt}
}
% #1: optional option
% #2: a name of the matrix... usually A
defpgflabread@opt[#1]#2{
edefpgflabname{#2}
pgfplotstableread[header=false,#1]
}
% we did not provide a macro to pgfplotstable to store the table
% we give it a temporary one called pgflabtemptable
% and then copy it to our data structure
longdefpgfplotstableread@impl@collectfirstarg#1#2{
pgfplotstableread@impl@{#1}{#2}pgflabtemptable
pgflabconverttablepgflabtemptable to matrix{pgflabname}
}
% this helps us to deal with pgfleys
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% copy pgfplotstable table to our data structure in pgfkeys
% #1: the macro that pgfplotstable used to store the table
% #2: a name of the matrix
defpgflabconverttable#1to matrix#2{
% extract height and width
pgfplotstablegetrowsof#1xdefpgflabh{pgfplotsretval}pgfkeys{/lab/#2/h/.let=pgflabh}
%%%height = pgflabh par
pgfplotstablegetcolsof#1xdefpgflabw{pgfplotsretval}pgfkeys{/lab/#2/w/.let=pgflabw}
%%%width = pgflabw par
% extract entries
% c@i and c@j cannot be used outside
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
% since fpu is on, this is easier way to do 9-1
pgfplotstablegetelem{thenumexprc@i-1}{thenumexprc@j-1}ofpgflabtemptable
pgfkeys{/lab/#2/c@i/c@j/.let=pgfplotsretval}
%%%pgfplotsretval,
}
%%%; par
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
% the opposite of the previous one
% #1: the name of the matrix
% #2: a macro for pgfplotstable to store the table
defpgflabconvertmatrix #1 to table #2{
% makeup meta data
expandafterdefcsnamestring#2@@table@nameendcsname{<inline_table>}
% build a new list of columns
pgfkeys{/lab/#1/h/.get=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}
pgfplotslistnew#2{0,...,thenumexprpgflabw-1}
% fill in columns
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
pgfplotslistnewemptypgflabtempcolumn
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfkeys{/lab/#1/c@i/c@j/.get=pgflabtempentry}
expandafterpgfplotslistpushbackpgflabtempentrytopgflabtempcolumn
}
edefc@k{thenumexprc@j-1}
expandafterletcsnamestring#2@c@kendcsnamepgflabtempcolumn
}
}
% typeset the matrix by pgfplotstabletypeset
defpgflabtypeset{
pgfutil@ifnextchar[
{pgflabtypeset@opt}
{pgflabtypeset@opt}
}
% #1: optional option
% #2: the name of the matrix
defpgflabtypeset@opt[#1]#2{
pgflabconvertmatrix #2 to table pgflabtemptable
pgfplotstabletypeset[every head row/.style={output empty row}]pgflabtemptable
}
Matrix A is
$A=pgflabtypeset{A}$
% define row operation: switch
% does not check boundary
defpgflabswitchrow #1 and row #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupeds@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/s@j/.get=pgflabtempentrya}
pgfkeys{/lab/#3/#2/s@j/.get=pgflabtempentryb}
pgfkeys{/lab/#3/#1/s@j/.let=pgflabtempentryb}
pgfkeys{/lab/#3/#2/s@j/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchrow 1 and row 3 in A
switch row 1 and row 3;
$A=pgflabtypeset{A}$
% define column operation: switch
% does not check boundary
defpgflabswitchcol #1 and col #2 in #3{
pgfkeys{/lab/#3/h/.get=pgflabh}
pgfplotsforeachungroupeds@i in{1,...,pgflabh}{
pgfkeys{/lab/#3/s@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#3/s@i/#2/.get=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#1/.let=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#2/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchcol 2 and col 3 in A
switch col 2 and col 3;
$A=pgflabtypeset{A}$
% define row operation: multiplication
% does not check boundary
defpgflabmultiplyrow #1 by #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupedm@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/m@j/.get=pgflabtempentry}
pgfmathparse{pgflabtempentry*#2}
pgfkeys{/lab/#3/#1/m@j/.let=pgfmathresult}
}
}
bigskip
pgflabmultiplyrow 3 by -1 in A
multiply row 3 by -1;
$A=pgflabtypeset{A}$
% define row operation: addition
% does not check boundary
defpgflabaddrow #1 by row #2 times #3 in #4{
pgfkeys{/lab/#4/w/.get=pgflabw}
pgfplotsforeachungroupeda@j in{1,...,pgflabw}{
pgfkeys{/lab/#4/#1/a@j/.get=pgflabtempentrya}
pgfkeys{/lab/#4/#2/a@j/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/#1/a@j/.let=pgfmathresult}
}
}
bigskip
pgflabaddrow 2 by row 3 times 2 in A
add row 2 by row 3 times 2;
$A=pgflabtypeset{A}$
% define column operation: addition
% does not check boundary
defpgflabaddcol #1 by col #2 times #3 in #4{
pgfkeys{/lab/#4/h/.get=pgflabh}
pgfplotsforeachungroupeda@i in{1,...,pgflabh}{
pgfkeys{/lab/#4/a@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#4/a@i/#2/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/a@i/#1/.let=pgfmathresult}
}
}
bigskip
pgflabaddcol 5 by col 4 times -1 in A
add col 5 by row 4 times -1;
$A=pgflabtypeset{A}$
% new identity matrix
defpgflabneweyeof #1 by #2 as #3{
defpgflabh{#1}pgfkeys{/lab/#3/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#3/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
ifnumn@i=n@j
pgfkeys{/lab/#3/n@i/n@i/.initial=1}
else
pgfkeys{/lab/#3/n@i/n@j/.initial=0}
fi
}
}
}
bigskip
pgflabneweyeof 4 by 4 as I
identity matrix;
$A=pgflabtypeset{I}$
bigskip
pgflabneweyeof 3 by 5 as B
rectangular identity matrix;
$B=pgflabtypeset{B}$
% copy matrix
defpgflabcopymatrix #1 to #2{
pgfkeys{/lab/#1/h/.get=pgflabh}pgfkeys{/lab/#2/h/.let=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}pgfkeys{/lab/#2/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfkeys{/lab/#1/n@i/n@j/.get=pgflabtempentry}
pgfkeys{/lab/#2/n@i/n@j/.let=pgflabtempentry}
}
}
}
bigskip
pgflabcopymatrix A to B
copy matrix A to B;
$B=pgflabtypeset{B}$
% LU decomposition
% if encounter 0, probably will result in inf or nan
defpgflabLUdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/h/.get=pgflabh@u}
pgfkeys{/lab/#1/w/.get=pgflabw@u}
% decide the loop boundary
edefpgflabh@v{thenumexprpgflabh@u-1}
ifnumpgflabh@v>pgflabw@u
edefpgflabh@v{pgflabw@u}
fi
% set L as identity
% set #2 as identity
pgflabneweyeof {pgflabh@u} by {pgflabh@u} as #2
% copy A to U
% copy #1 to #3
pgflabcopymatrix #1 to #3
% forget A, do job at L and U
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflabh@v}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh@u}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#2}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#3}
medskip
eliminate one entry. check L and U par
$L=pgflabtypeset{#2};$
$U=pgflabtypeset{#3};$
}
}
}
clearpage
$A=pgflabtypeset{A}$
pgflabLUdecompose A as L times U
% find pivot in the specific column
% find pivot in the range (#1,#1) to (#1,end)
% does not check boundary
defpgflabfindpivotatcol #1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfkeys{/lab/#2/f@i/#1/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
fi
}
}
clearpage
$A=pgflabtypeset{A}$
find pivot at specific column: par
pgflabfindpivotatcol 1 in A
at col 1 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 2 in A
at col 2 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 3 in A
at col 2 it is pgflabtempmax at row pgflabtempindex
% A = PLU decomposition
% partial pivoting
defpgflabPLUdecompose #1 as #2 times #3 times #4{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotatcol {d@i} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
parmedskip
switch d@i{} and pgflabtempindexpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
parmedskip
eliminate one column. check P and L and U par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
bigskip
pgflabPLUdecompose A as P times L times U
% find pivot in the specific column and row
% find pivot in the range (#1,#1) to (end,end)
% does not check boundary
defpgflabfindpivotafter#1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
pgfkeys{/lab/#2/w/.get=pgflabw}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
defpgflabtempjndex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfplotsforeachungroupedf@j in{#1,...,pgflabw}{
pgfkeys{/lab/#2/f@i/f@j/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
letpgflabtempjndexf@j
fi
}
}
}
% A = PLUQ decomposition
% partial pivoting
defpgflabPLUQdecompose #1 as #2 times #3 times #4 times #5{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% set Q as identity
% set #5 as identity
pgflabneweyeof {pgflabW} by {pgflabW} as {#5}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotafter {d@i} in #4
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
{}
pgflabswitchcol {d@i} and col {pgflabtempjndex} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempjndex} in {#5}
switch (d@i{},d@i{}) and (pgflabtempindex,pgflabtempjndex) par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
eliminate one column. check P and L and U and Qpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
}
}
pgflabread{A}{
3 -7 5 0 1 0 1
-9 -8 -2 9 -1 9 -4
4 6 0 -1 -2 -1 -3
-5 2 -6 7 8 7 8
-1 -2 -1 -3 4 6 0
7 8 7 8 -5 2 -6
}
bigskip
$A=pgflabtypeset{A}$
pgflabPLUQdecompose A as P times L times U times Q
% new matrix with desired entry
% entry can contain n@i and n@j
defpgflabnewmatrixof #1 by #2 with #3 as #4{
defpgflabh{#1}pgfkeys{/lab/#4/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#4/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfmathparse{#3}
pgfkeys{/lab/#4/n@i/n@j/.let=pgfmathresult}
}
}
}
clearpage
pgflabnewmatrixof 10 by 10 with rand as C
pgflabPLUQdecompose C as P times L times U times Q
% debug macro
% we can pass it to sage
% but we need to replace negative sign by ascii's -
defpgflabrawoutput#1{%
pgfkeys{/lab/#1/h/.get=pgflabh}%
pgfkeys{/lab/#1/w/.get=pgflabw}%
matrix([%
pgfplotsforeachungroupedt@i in{1,...,pgflabh}{%
[%
pgfplotsforeachungroupedt@j in{1,...,pgflabw}{%
pgfkeys{/lab/#1/t@i/t@j/.get=pgflabtempentry}%
pgfmathparse{pgflabtempentry}%
pgfmathfloattosci{pgfmathresult}%
mbox{pgfmathresult}%
ifnumt@j<pgflabw,hskip1ptplus3ptallowbreakfi
}%
]%
ifnumt@i<pgflabh,hskip1ptplus3ptallowbreakfi
}%
])%
}
clearpage
C=pgflabrawoutput{C};par
P=pgflabrawoutput{P};par
L=pgflabrawoutput{L};par
U=pgflabrawoutput{U};par
Q=pgflabrawoutput{Q};par
(C-P*L*U*Q).norm()
end{document}
debug mode
Old Answer
I would like to try
documentclass{article}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu}
begin{document}
% we are lazy
% let pgfplotstable read the matrix
pgfplotstableread[header=false]{
8 1 6 8
3 5 7 5
4 9 2 7
}matrixA
% we will store data by pgfleys
% create a handy handler
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% PS: /.initial is more like def, but we want xdef or edef or let
% but we also need some fast macros
pgfplotstablegetrowsofmatrixA xdefmatrixheight{pgfplotsretval}pgfkeys{/matrix/A/height/.let=matrixheight}
pgfplotstablegetcolsofmatrixA xdefmatrixwidth{pgfplotsretval} pgfkeys{/matrix/A/width/.let=matrixwidth}
% check data
Matrix $A$ is pgfkeys{/matrix/A/height} by pgfkeys{/matrix/A/width}.
In other words: par Matrix $A$ is matrixheight{} by matrixwidth{}.
% store the entries into pgfkeys
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
% since fpu is on, this is easier way to do 9+1
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
% check data
bigskip The matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: switch
defrowoperationswitch#1and#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfkeys{/matrix/A/#1/j/.let=tempmatrixentryB}
pgfkeys{/matrix/A/#2/j/.let=tempmatrixentryA}
}
}
% try and check
rowoperationswitch3and2
bigskip After switching row3 and row2, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: multiplication
defrowoperationmultiply#1by#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry*#2}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationmultiply3by9
bigskip After multiplying row3 by 9, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
remember: fpu is on! par
Human readable version par
defpgfmathprintmatrix{
pgfplotsforeachungroupedi in{1,...,matrixheight}{
indent
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry}
clap{pgfmathprintnumber{pgfmathresult}}hskip20pt
}
par
}
}
pgfmathprintmatrix
% define row operation: addition
defrowoperationadd#1by#2times#3 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfmathparse{tempmatrixentryA+tempmatrixentryB*#3}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationadd1by2times-1
bigskip After adding row2 by row1 times -1, the matrix entries are: par
pgfmathprintmatrix
% We do RREF by hand
pgfkeys{/pgf/number format/fixed}
bigskip We do RREF by hand par
add 2 by 1 times -1: par
rowoperationadd2by1times-1
pgfmathprintmatrix
medskip add 3 by 1 times -6.75: par
rowoperationadd3by1times-6.75
pgfmathprintmatrix
medskip add 3 by 2 times -5.8235: par
rowoperationadd3by2times-5.8235
pgfmathprintmatrix
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% Automatic RREF without row switching
% I is different form i
xdefmatrixheightminusone{thenumexprmatrixheight-1}
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
entry [I][I] is tempmatrixentryA par
entry [J][I] is tempmatrixentryB par
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
message{^^J^^JI,J,temprowscaler^^J^^J}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% maybe we need pivoting
defrowoperationfindpivot{
% find the maximal element in this column
defmaxofthiscolumn{-inf}
defmaxofthiscolumnindex{0}
pgfplotsforeachungroupedK in{I,...,matrixheight}{
pgfkeys{/matrix/A/K/I/.get=tempmatrixentry}
% compare
pgfmathparse{abs(tempmatrixentry)}
lettempmatrixabsentrypgfmathresult
pgfmathparse{maxofthiscolumn<tempmatrixabsentry}
% update if necessary
ifpgfmathfloatcomparison
letmaxofthiscolumntempmatrixabsentry
letmaxofthiscolumnindexK
fi
}
}
xdefI{1}
rowoperationfindpivot
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex
% Automatic RREF with partial pivot
defRREFwithpivoting{
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
rowoperationfindpivot
rowoperationswitchI and{maxofthiscolumnindex}
bigskip
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex par
so we switch rowI{} and rowmaxofthiscolumnindex, the matrix entries are: par
pgfmathprintmatrix
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
}
RREFwithpivoting
.......
The rest is deleted because of the length limitation.
By the way, the product of pivots is -862566.82. And by Wolfram|Alpha the determinant of the first six columns is 862575. So the error is .0009%
– Symbol 1
Mar 22 '17 at 21:09
add a comment |
up vote
19
down vote
Update-2
I heard someone said Givens rotations.
% Givens rotation
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotaterow #1 and row #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/w/.get=pgflabw}
pgfplotsforeachungroupedg@j in{1,...,pgflabw}{
pgfkeys{/lab/#5/#1/g@j/.get=pgflabtempentrya}
pgfkeys{/lab/#5/#2/g@j/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/#1/g@j/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/#2/g@j/.let=pgfmathresult}
}
}
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotatecol #1 and col #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/h/.get=pgflabh}
pgfplotsforeachungroupedg@i in{1,...,pgflabh}{
pgfkeys{/lab/#5/g@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#5/g@i/#2/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#1/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#2/.let=pgfmathresult}
}
}
% A = QR decomposition
defpgflabQRdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/w/.get=pgflabW}
pgfkeys{/lab/#1/h/.get=pgflabH}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set Q as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% copy A to R
% copy #1 to #3
pgflabcopymatrix {#1} to {#3}
% forget A, do job at Q and R
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh}{
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempradius{sqrt(pgflabtempentrya*pgflabtempentrya+pgflabtempentryb*pgflabtempentryb)}
pgfmathsetmacropgflabtempcos{ pgflabtempentrya/pgflabtempradius} % cosine
pgfmathsetmacropgflabtempsin{-pgflabtempentryb/pgflabtempradius} % sine
pgflabgivensrotaterow {d@i} and row {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#3}
pgflabgivensrotatecol {d@i} and col {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#2}
eliminate one entry. check Q and Rpar
$Q=pgflabtypeset{#2};$
$R=pgflabtypeset{#3};$
}
}
}
pgflabread{A}{
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
}
pgflabQRdecompose A as Q times R
For a 10 by 10 random matrix, the norm of A - QR is about 4e-4. The norm of QQᵀ - I is about 2e-4.
Update-1: New Answer
I implement three decompositions:
A = LU
A = PLU (i.e. partial pivoting)
A = PLUQ (i.e. complete pivoting)
If A is m by n, then P, L are m by m; U is the same as A; and Q is n by n.
Advantages
The complexity of accessing a matrix entry is O(1). (Assuming
csname
is O(1)). So the complexity of decompositions is O(m²n).The input utilize
pgfplotstableread
from pgfplotstable. So it accepts inline-table, file, loaded table, and even the table created bypgfplotstablenew
. You can also pass options to it. (such as filtering)The output utilize
pgfplotstabletypeset
from the same package. Or you can convert the matrix back to a table and do whatever you want.The calculation is done by
pgfmathparse
. I assume FPU is on. But one can reimplement that.There is a debug macro that output the raw data of matrices. You can copy and paste those data into whatever modern matrix calculator.
According to Wikipeida, even partial pivoting is numerically stable in practice. I test a 10 by 10 random matrix and check A - PLUQ in sage; the norm is about 1.1e-6. (This is about the precision of FPU)
documentclass{article}
usepackage[a3paper,landscape,margin=1cm]{geometry}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu,/pgf/number format/fixed}
begin{document}
makeatletter
% pgfmatrix... is used
% we use pgflab...
% call pgfplotstable to read the data
% put options in if desired
% the options go to pgfplotstableread
defpgflabread{
pgfutil@ifnextchar[
{pgflabread@opt}
{pgflabread@opt}
}
% #1: optional option
% #2: a name of the matrix... usually A
defpgflabread@opt[#1]#2{
edefpgflabname{#2}
pgfplotstableread[header=false,#1]
}
% we did not provide a macro to pgfplotstable to store the table
% we give it a temporary one called pgflabtemptable
% and then copy it to our data structure
longdefpgfplotstableread@impl@collectfirstarg#1#2{
pgfplotstableread@impl@{#1}{#2}pgflabtemptable
pgflabconverttablepgflabtemptable to matrix{pgflabname}
}
% this helps us to deal with pgfleys
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% copy pgfplotstable table to our data structure in pgfkeys
% #1: the macro that pgfplotstable used to store the table
% #2: a name of the matrix
defpgflabconverttable#1to matrix#2{
% extract height and width
pgfplotstablegetrowsof#1xdefpgflabh{pgfplotsretval}pgfkeys{/lab/#2/h/.let=pgflabh}
%%%height = pgflabh par
pgfplotstablegetcolsof#1xdefpgflabw{pgfplotsretval}pgfkeys{/lab/#2/w/.let=pgflabw}
%%%width = pgflabw par
% extract entries
% c@i and c@j cannot be used outside
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
% since fpu is on, this is easier way to do 9-1
pgfplotstablegetelem{thenumexprc@i-1}{thenumexprc@j-1}ofpgflabtemptable
pgfkeys{/lab/#2/c@i/c@j/.let=pgfplotsretval}
%%%pgfplotsretval,
}
%%%; par
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
% the opposite of the previous one
% #1: the name of the matrix
% #2: a macro for pgfplotstable to store the table
defpgflabconvertmatrix #1 to table #2{
% makeup meta data
expandafterdefcsnamestring#2@@table@nameendcsname{<inline_table>}
% build a new list of columns
pgfkeys{/lab/#1/h/.get=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}
pgfplotslistnew#2{0,...,thenumexprpgflabw-1}
% fill in columns
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
pgfplotslistnewemptypgflabtempcolumn
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfkeys{/lab/#1/c@i/c@j/.get=pgflabtempentry}
expandafterpgfplotslistpushbackpgflabtempentrytopgflabtempcolumn
}
edefc@k{thenumexprc@j-1}
expandafterletcsnamestring#2@c@kendcsnamepgflabtempcolumn
}
}
% typeset the matrix by pgfplotstabletypeset
defpgflabtypeset{
pgfutil@ifnextchar[
{pgflabtypeset@opt}
{pgflabtypeset@opt}
}
% #1: optional option
% #2: the name of the matrix
defpgflabtypeset@opt[#1]#2{
pgflabconvertmatrix #2 to table pgflabtemptable
pgfplotstabletypeset[every head row/.style={output empty row}]pgflabtemptable
}
Matrix A is
$A=pgflabtypeset{A}$
% define row operation: switch
% does not check boundary
defpgflabswitchrow #1 and row #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupeds@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/s@j/.get=pgflabtempentrya}
pgfkeys{/lab/#3/#2/s@j/.get=pgflabtempentryb}
pgfkeys{/lab/#3/#1/s@j/.let=pgflabtempentryb}
pgfkeys{/lab/#3/#2/s@j/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchrow 1 and row 3 in A
switch row 1 and row 3;
$A=pgflabtypeset{A}$
% define column operation: switch
% does not check boundary
defpgflabswitchcol #1 and col #2 in #3{
pgfkeys{/lab/#3/h/.get=pgflabh}
pgfplotsforeachungroupeds@i in{1,...,pgflabh}{
pgfkeys{/lab/#3/s@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#3/s@i/#2/.get=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#1/.let=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#2/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchcol 2 and col 3 in A
switch col 2 and col 3;
$A=pgflabtypeset{A}$
% define row operation: multiplication
% does not check boundary
defpgflabmultiplyrow #1 by #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupedm@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/m@j/.get=pgflabtempentry}
pgfmathparse{pgflabtempentry*#2}
pgfkeys{/lab/#3/#1/m@j/.let=pgfmathresult}
}
}
bigskip
pgflabmultiplyrow 3 by -1 in A
multiply row 3 by -1;
$A=pgflabtypeset{A}$
% define row operation: addition
% does not check boundary
defpgflabaddrow #1 by row #2 times #3 in #4{
pgfkeys{/lab/#4/w/.get=pgflabw}
pgfplotsforeachungroupeda@j in{1,...,pgflabw}{
pgfkeys{/lab/#4/#1/a@j/.get=pgflabtempentrya}
pgfkeys{/lab/#4/#2/a@j/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/#1/a@j/.let=pgfmathresult}
}
}
bigskip
pgflabaddrow 2 by row 3 times 2 in A
add row 2 by row 3 times 2;
$A=pgflabtypeset{A}$
% define column operation: addition
% does not check boundary
defpgflabaddcol #1 by col #2 times #3 in #4{
pgfkeys{/lab/#4/h/.get=pgflabh}
pgfplotsforeachungroupeda@i in{1,...,pgflabh}{
pgfkeys{/lab/#4/a@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#4/a@i/#2/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/a@i/#1/.let=pgfmathresult}
}
}
bigskip
pgflabaddcol 5 by col 4 times -1 in A
add col 5 by row 4 times -1;
$A=pgflabtypeset{A}$
% new identity matrix
defpgflabneweyeof #1 by #2 as #3{
defpgflabh{#1}pgfkeys{/lab/#3/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#3/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
ifnumn@i=n@j
pgfkeys{/lab/#3/n@i/n@i/.initial=1}
else
pgfkeys{/lab/#3/n@i/n@j/.initial=0}
fi
}
}
}
bigskip
pgflabneweyeof 4 by 4 as I
identity matrix;
$A=pgflabtypeset{I}$
bigskip
pgflabneweyeof 3 by 5 as B
rectangular identity matrix;
$B=pgflabtypeset{B}$
% copy matrix
defpgflabcopymatrix #1 to #2{
pgfkeys{/lab/#1/h/.get=pgflabh}pgfkeys{/lab/#2/h/.let=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}pgfkeys{/lab/#2/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfkeys{/lab/#1/n@i/n@j/.get=pgflabtempentry}
pgfkeys{/lab/#2/n@i/n@j/.let=pgflabtempentry}
}
}
}
bigskip
pgflabcopymatrix A to B
copy matrix A to B;
$B=pgflabtypeset{B}$
% LU decomposition
% if encounter 0, probably will result in inf or nan
defpgflabLUdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/h/.get=pgflabh@u}
pgfkeys{/lab/#1/w/.get=pgflabw@u}
% decide the loop boundary
edefpgflabh@v{thenumexprpgflabh@u-1}
ifnumpgflabh@v>pgflabw@u
edefpgflabh@v{pgflabw@u}
fi
% set L as identity
% set #2 as identity
pgflabneweyeof {pgflabh@u} by {pgflabh@u} as #2
% copy A to U
% copy #1 to #3
pgflabcopymatrix #1 to #3
% forget A, do job at L and U
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflabh@v}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh@u}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#2}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#3}
medskip
eliminate one entry. check L and U par
$L=pgflabtypeset{#2};$
$U=pgflabtypeset{#3};$
}
}
}
clearpage
$A=pgflabtypeset{A}$
pgflabLUdecompose A as L times U
% find pivot in the specific column
% find pivot in the range (#1,#1) to (#1,end)
% does not check boundary
defpgflabfindpivotatcol #1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfkeys{/lab/#2/f@i/#1/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
fi
}
}
clearpage
$A=pgflabtypeset{A}$
find pivot at specific column: par
pgflabfindpivotatcol 1 in A
at col 1 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 2 in A
at col 2 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 3 in A
at col 2 it is pgflabtempmax at row pgflabtempindex
% A = PLU decomposition
% partial pivoting
defpgflabPLUdecompose #1 as #2 times #3 times #4{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotatcol {d@i} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
parmedskip
switch d@i{} and pgflabtempindexpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
parmedskip
eliminate one column. check P and L and U par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
bigskip
pgflabPLUdecompose A as P times L times U
% find pivot in the specific column and row
% find pivot in the range (#1,#1) to (end,end)
% does not check boundary
defpgflabfindpivotafter#1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
pgfkeys{/lab/#2/w/.get=pgflabw}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
defpgflabtempjndex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfplotsforeachungroupedf@j in{#1,...,pgflabw}{
pgfkeys{/lab/#2/f@i/f@j/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
letpgflabtempjndexf@j
fi
}
}
}
% A = PLUQ decomposition
% partial pivoting
defpgflabPLUQdecompose #1 as #2 times #3 times #4 times #5{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% set Q as identity
% set #5 as identity
pgflabneweyeof {pgflabW} by {pgflabW} as {#5}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotafter {d@i} in #4
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
{}
pgflabswitchcol {d@i} and col {pgflabtempjndex} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempjndex} in {#5}
switch (d@i{},d@i{}) and (pgflabtempindex,pgflabtempjndex) par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
eliminate one column. check P and L and U and Qpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
}
}
pgflabread{A}{
3 -7 5 0 1 0 1
-9 -8 -2 9 -1 9 -4
4 6 0 -1 -2 -1 -3
-5 2 -6 7 8 7 8
-1 -2 -1 -3 4 6 0
7 8 7 8 -5 2 -6
}
bigskip
$A=pgflabtypeset{A}$
pgflabPLUQdecompose A as P times L times U times Q
% new matrix with desired entry
% entry can contain n@i and n@j
defpgflabnewmatrixof #1 by #2 with #3 as #4{
defpgflabh{#1}pgfkeys{/lab/#4/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#4/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfmathparse{#3}
pgfkeys{/lab/#4/n@i/n@j/.let=pgfmathresult}
}
}
}
clearpage
pgflabnewmatrixof 10 by 10 with rand as C
pgflabPLUQdecompose C as P times L times U times Q
% debug macro
% we can pass it to sage
% but we need to replace negative sign by ascii's -
defpgflabrawoutput#1{%
pgfkeys{/lab/#1/h/.get=pgflabh}%
pgfkeys{/lab/#1/w/.get=pgflabw}%
matrix([%
pgfplotsforeachungroupedt@i in{1,...,pgflabh}{%
[%
pgfplotsforeachungroupedt@j in{1,...,pgflabw}{%
pgfkeys{/lab/#1/t@i/t@j/.get=pgflabtempentry}%
pgfmathparse{pgflabtempentry}%
pgfmathfloattosci{pgfmathresult}%
mbox{pgfmathresult}%
ifnumt@j<pgflabw,hskip1ptplus3ptallowbreakfi
}%
]%
ifnumt@i<pgflabh,hskip1ptplus3ptallowbreakfi
}%
])%
}
clearpage
C=pgflabrawoutput{C};par
P=pgflabrawoutput{P};par
L=pgflabrawoutput{L};par
U=pgflabrawoutput{U};par
Q=pgflabrawoutput{Q};par
(C-P*L*U*Q).norm()
end{document}
debug mode
Old Answer
I would like to try
documentclass{article}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu}
begin{document}
% we are lazy
% let pgfplotstable read the matrix
pgfplotstableread[header=false]{
8 1 6 8
3 5 7 5
4 9 2 7
}matrixA
% we will store data by pgfleys
% create a handy handler
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% PS: /.initial is more like def, but we want xdef or edef or let
% but we also need some fast macros
pgfplotstablegetrowsofmatrixA xdefmatrixheight{pgfplotsretval}pgfkeys{/matrix/A/height/.let=matrixheight}
pgfplotstablegetcolsofmatrixA xdefmatrixwidth{pgfplotsretval} pgfkeys{/matrix/A/width/.let=matrixwidth}
% check data
Matrix $A$ is pgfkeys{/matrix/A/height} by pgfkeys{/matrix/A/width}.
In other words: par Matrix $A$ is matrixheight{} by matrixwidth{}.
% store the entries into pgfkeys
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
% since fpu is on, this is easier way to do 9+1
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
% check data
bigskip The matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: switch
defrowoperationswitch#1and#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfkeys{/matrix/A/#1/j/.let=tempmatrixentryB}
pgfkeys{/matrix/A/#2/j/.let=tempmatrixentryA}
}
}
% try and check
rowoperationswitch3and2
bigskip After switching row3 and row2, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: multiplication
defrowoperationmultiply#1by#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry*#2}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationmultiply3by9
bigskip After multiplying row3 by 9, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
remember: fpu is on! par
Human readable version par
defpgfmathprintmatrix{
pgfplotsforeachungroupedi in{1,...,matrixheight}{
indent
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry}
clap{pgfmathprintnumber{pgfmathresult}}hskip20pt
}
par
}
}
pgfmathprintmatrix
% define row operation: addition
defrowoperationadd#1by#2times#3 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfmathparse{tempmatrixentryA+tempmatrixentryB*#3}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationadd1by2times-1
bigskip After adding row2 by row1 times -1, the matrix entries are: par
pgfmathprintmatrix
% We do RREF by hand
pgfkeys{/pgf/number format/fixed}
bigskip We do RREF by hand par
add 2 by 1 times -1: par
rowoperationadd2by1times-1
pgfmathprintmatrix
medskip add 3 by 1 times -6.75: par
rowoperationadd3by1times-6.75
pgfmathprintmatrix
medskip add 3 by 2 times -5.8235: par
rowoperationadd3by2times-5.8235
pgfmathprintmatrix
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% Automatic RREF without row switching
% I is different form i
xdefmatrixheightminusone{thenumexprmatrixheight-1}
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
entry [I][I] is tempmatrixentryA par
entry [J][I] is tempmatrixentryB par
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
message{^^J^^JI,J,temprowscaler^^J^^J}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% maybe we need pivoting
defrowoperationfindpivot{
% find the maximal element in this column
defmaxofthiscolumn{-inf}
defmaxofthiscolumnindex{0}
pgfplotsforeachungroupedK in{I,...,matrixheight}{
pgfkeys{/matrix/A/K/I/.get=tempmatrixentry}
% compare
pgfmathparse{abs(tempmatrixentry)}
lettempmatrixabsentrypgfmathresult
pgfmathparse{maxofthiscolumn<tempmatrixabsentry}
% update if necessary
ifpgfmathfloatcomparison
letmaxofthiscolumntempmatrixabsentry
letmaxofthiscolumnindexK
fi
}
}
xdefI{1}
rowoperationfindpivot
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex
% Automatic RREF with partial pivot
defRREFwithpivoting{
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
rowoperationfindpivot
rowoperationswitchI and{maxofthiscolumnindex}
bigskip
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex par
so we switch rowI{} and rowmaxofthiscolumnindex, the matrix entries are: par
pgfmathprintmatrix
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
}
RREFwithpivoting
.......
The rest is deleted because of the length limitation.
By the way, the product of pivots is -862566.82. And by Wolfram|Alpha the determinant of the first six columns is 862575. So the error is .0009%
– Symbol 1
Mar 22 '17 at 21:09
add a comment |
up vote
19
down vote
up vote
19
down vote
Update-2
I heard someone said Givens rotations.
% Givens rotation
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotaterow #1 and row #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/w/.get=pgflabw}
pgfplotsforeachungroupedg@j in{1,...,pgflabw}{
pgfkeys{/lab/#5/#1/g@j/.get=pgflabtempentrya}
pgfkeys{/lab/#5/#2/g@j/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/#1/g@j/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/#2/g@j/.let=pgfmathresult}
}
}
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotatecol #1 and col #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/h/.get=pgflabh}
pgfplotsforeachungroupedg@i in{1,...,pgflabh}{
pgfkeys{/lab/#5/g@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#5/g@i/#2/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#1/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#2/.let=pgfmathresult}
}
}
% A = QR decomposition
defpgflabQRdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/w/.get=pgflabW}
pgfkeys{/lab/#1/h/.get=pgflabH}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set Q as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% copy A to R
% copy #1 to #3
pgflabcopymatrix {#1} to {#3}
% forget A, do job at Q and R
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh}{
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempradius{sqrt(pgflabtempentrya*pgflabtempentrya+pgflabtempentryb*pgflabtempentryb)}
pgfmathsetmacropgflabtempcos{ pgflabtempentrya/pgflabtempradius} % cosine
pgfmathsetmacropgflabtempsin{-pgflabtempentryb/pgflabtempradius} % sine
pgflabgivensrotaterow {d@i} and row {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#3}
pgflabgivensrotatecol {d@i} and col {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#2}
eliminate one entry. check Q and Rpar
$Q=pgflabtypeset{#2};$
$R=pgflabtypeset{#3};$
}
}
}
pgflabread{A}{
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
}
pgflabQRdecompose A as Q times R
For a 10 by 10 random matrix, the norm of A - QR is about 4e-4. The norm of QQᵀ - I is about 2e-4.
Update-1: New Answer
I implement three decompositions:
A = LU
A = PLU (i.e. partial pivoting)
A = PLUQ (i.e. complete pivoting)
If A is m by n, then P, L are m by m; U is the same as A; and Q is n by n.
Advantages
The complexity of accessing a matrix entry is O(1). (Assuming
csname
is O(1)). So the complexity of decompositions is O(m²n).The input utilize
pgfplotstableread
from pgfplotstable. So it accepts inline-table, file, loaded table, and even the table created bypgfplotstablenew
. You can also pass options to it. (such as filtering)The output utilize
pgfplotstabletypeset
from the same package. Or you can convert the matrix back to a table and do whatever you want.The calculation is done by
pgfmathparse
. I assume FPU is on. But one can reimplement that.There is a debug macro that output the raw data of matrices. You can copy and paste those data into whatever modern matrix calculator.
According to Wikipeida, even partial pivoting is numerically stable in practice. I test a 10 by 10 random matrix and check A - PLUQ in sage; the norm is about 1.1e-6. (This is about the precision of FPU)
documentclass{article}
usepackage[a3paper,landscape,margin=1cm]{geometry}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu,/pgf/number format/fixed}
begin{document}
makeatletter
% pgfmatrix... is used
% we use pgflab...
% call pgfplotstable to read the data
% put options in if desired
% the options go to pgfplotstableread
defpgflabread{
pgfutil@ifnextchar[
{pgflabread@opt}
{pgflabread@opt}
}
% #1: optional option
% #2: a name of the matrix... usually A
defpgflabread@opt[#1]#2{
edefpgflabname{#2}
pgfplotstableread[header=false,#1]
}
% we did not provide a macro to pgfplotstable to store the table
% we give it a temporary one called pgflabtemptable
% and then copy it to our data structure
longdefpgfplotstableread@impl@collectfirstarg#1#2{
pgfplotstableread@impl@{#1}{#2}pgflabtemptable
pgflabconverttablepgflabtemptable to matrix{pgflabname}
}
% this helps us to deal with pgfleys
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% copy pgfplotstable table to our data structure in pgfkeys
% #1: the macro that pgfplotstable used to store the table
% #2: a name of the matrix
defpgflabconverttable#1to matrix#2{
% extract height and width
pgfplotstablegetrowsof#1xdefpgflabh{pgfplotsretval}pgfkeys{/lab/#2/h/.let=pgflabh}
%%%height = pgflabh par
pgfplotstablegetcolsof#1xdefpgflabw{pgfplotsretval}pgfkeys{/lab/#2/w/.let=pgflabw}
%%%width = pgflabw par
% extract entries
% c@i and c@j cannot be used outside
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
% since fpu is on, this is easier way to do 9-1
pgfplotstablegetelem{thenumexprc@i-1}{thenumexprc@j-1}ofpgflabtemptable
pgfkeys{/lab/#2/c@i/c@j/.let=pgfplotsretval}
%%%pgfplotsretval,
}
%%%; par
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
% the opposite of the previous one
% #1: the name of the matrix
% #2: a macro for pgfplotstable to store the table
defpgflabconvertmatrix #1 to table #2{
% makeup meta data
expandafterdefcsnamestring#2@@table@nameendcsname{<inline_table>}
% build a new list of columns
pgfkeys{/lab/#1/h/.get=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}
pgfplotslistnew#2{0,...,thenumexprpgflabw-1}
% fill in columns
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
pgfplotslistnewemptypgflabtempcolumn
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfkeys{/lab/#1/c@i/c@j/.get=pgflabtempentry}
expandafterpgfplotslistpushbackpgflabtempentrytopgflabtempcolumn
}
edefc@k{thenumexprc@j-1}
expandafterletcsnamestring#2@c@kendcsnamepgflabtempcolumn
}
}
% typeset the matrix by pgfplotstabletypeset
defpgflabtypeset{
pgfutil@ifnextchar[
{pgflabtypeset@opt}
{pgflabtypeset@opt}
}
% #1: optional option
% #2: the name of the matrix
defpgflabtypeset@opt[#1]#2{
pgflabconvertmatrix #2 to table pgflabtemptable
pgfplotstabletypeset[every head row/.style={output empty row}]pgflabtemptable
}
Matrix A is
$A=pgflabtypeset{A}$
% define row operation: switch
% does not check boundary
defpgflabswitchrow #1 and row #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupeds@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/s@j/.get=pgflabtempentrya}
pgfkeys{/lab/#3/#2/s@j/.get=pgflabtempentryb}
pgfkeys{/lab/#3/#1/s@j/.let=pgflabtempentryb}
pgfkeys{/lab/#3/#2/s@j/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchrow 1 and row 3 in A
switch row 1 and row 3;
$A=pgflabtypeset{A}$
% define column operation: switch
% does not check boundary
defpgflabswitchcol #1 and col #2 in #3{
pgfkeys{/lab/#3/h/.get=pgflabh}
pgfplotsforeachungroupeds@i in{1,...,pgflabh}{
pgfkeys{/lab/#3/s@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#3/s@i/#2/.get=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#1/.let=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#2/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchcol 2 and col 3 in A
switch col 2 and col 3;
$A=pgflabtypeset{A}$
% define row operation: multiplication
% does not check boundary
defpgflabmultiplyrow #1 by #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupedm@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/m@j/.get=pgflabtempentry}
pgfmathparse{pgflabtempentry*#2}
pgfkeys{/lab/#3/#1/m@j/.let=pgfmathresult}
}
}
bigskip
pgflabmultiplyrow 3 by -1 in A
multiply row 3 by -1;
$A=pgflabtypeset{A}$
% define row operation: addition
% does not check boundary
defpgflabaddrow #1 by row #2 times #3 in #4{
pgfkeys{/lab/#4/w/.get=pgflabw}
pgfplotsforeachungroupeda@j in{1,...,pgflabw}{
pgfkeys{/lab/#4/#1/a@j/.get=pgflabtempentrya}
pgfkeys{/lab/#4/#2/a@j/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/#1/a@j/.let=pgfmathresult}
}
}
bigskip
pgflabaddrow 2 by row 3 times 2 in A
add row 2 by row 3 times 2;
$A=pgflabtypeset{A}$
% define column operation: addition
% does not check boundary
defpgflabaddcol #1 by col #2 times #3 in #4{
pgfkeys{/lab/#4/h/.get=pgflabh}
pgfplotsforeachungroupeda@i in{1,...,pgflabh}{
pgfkeys{/lab/#4/a@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#4/a@i/#2/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/a@i/#1/.let=pgfmathresult}
}
}
bigskip
pgflabaddcol 5 by col 4 times -1 in A
add col 5 by row 4 times -1;
$A=pgflabtypeset{A}$
% new identity matrix
defpgflabneweyeof #1 by #2 as #3{
defpgflabh{#1}pgfkeys{/lab/#3/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#3/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
ifnumn@i=n@j
pgfkeys{/lab/#3/n@i/n@i/.initial=1}
else
pgfkeys{/lab/#3/n@i/n@j/.initial=0}
fi
}
}
}
bigskip
pgflabneweyeof 4 by 4 as I
identity matrix;
$A=pgflabtypeset{I}$
bigskip
pgflabneweyeof 3 by 5 as B
rectangular identity matrix;
$B=pgflabtypeset{B}$
% copy matrix
defpgflabcopymatrix #1 to #2{
pgfkeys{/lab/#1/h/.get=pgflabh}pgfkeys{/lab/#2/h/.let=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}pgfkeys{/lab/#2/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfkeys{/lab/#1/n@i/n@j/.get=pgflabtempentry}
pgfkeys{/lab/#2/n@i/n@j/.let=pgflabtempentry}
}
}
}
bigskip
pgflabcopymatrix A to B
copy matrix A to B;
$B=pgflabtypeset{B}$
% LU decomposition
% if encounter 0, probably will result in inf or nan
defpgflabLUdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/h/.get=pgflabh@u}
pgfkeys{/lab/#1/w/.get=pgflabw@u}
% decide the loop boundary
edefpgflabh@v{thenumexprpgflabh@u-1}
ifnumpgflabh@v>pgflabw@u
edefpgflabh@v{pgflabw@u}
fi
% set L as identity
% set #2 as identity
pgflabneweyeof {pgflabh@u} by {pgflabh@u} as #2
% copy A to U
% copy #1 to #3
pgflabcopymatrix #1 to #3
% forget A, do job at L and U
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflabh@v}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh@u}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#2}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#3}
medskip
eliminate one entry. check L and U par
$L=pgflabtypeset{#2};$
$U=pgflabtypeset{#3};$
}
}
}
clearpage
$A=pgflabtypeset{A}$
pgflabLUdecompose A as L times U
% find pivot in the specific column
% find pivot in the range (#1,#1) to (#1,end)
% does not check boundary
defpgflabfindpivotatcol #1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfkeys{/lab/#2/f@i/#1/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
fi
}
}
clearpage
$A=pgflabtypeset{A}$
find pivot at specific column: par
pgflabfindpivotatcol 1 in A
at col 1 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 2 in A
at col 2 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 3 in A
at col 2 it is pgflabtempmax at row pgflabtempindex
% A = PLU decomposition
% partial pivoting
defpgflabPLUdecompose #1 as #2 times #3 times #4{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotatcol {d@i} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
parmedskip
switch d@i{} and pgflabtempindexpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
parmedskip
eliminate one column. check P and L and U par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
bigskip
pgflabPLUdecompose A as P times L times U
% find pivot in the specific column and row
% find pivot in the range (#1,#1) to (end,end)
% does not check boundary
defpgflabfindpivotafter#1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
pgfkeys{/lab/#2/w/.get=pgflabw}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
defpgflabtempjndex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfplotsforeachungroupedf@j in{#1,...,pgflabw}{
pgfkeys{/lab/#2/f@i/f@j/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
letpgflabtempjndexf@j
fi
}
}
}
% A = PLUQ decomposition
% partial pivoting
defpgflabPLUQdecompose #1 as #2 times #3 times #4 times #5{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% set Q as identity
% set #5 as identity
pgflabneweyeof {pgflabW} by {pgflabW} as {#5}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotafter {d@i} in #4
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
{}
pgflabswitchcol {d@i} and col {pgflabtempjndex} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempjndex} in {#5}
switch (d@i{},d@i{}) and (pgflabtempindex,pgflabtempjndex) par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
eliminate one column. check P and L and U and Qpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
}
}
pgflabread{A}{
3 -7 5 0 1 0 1
-9 -8 -2 9 -1 9 -4
4 6 0 -1 -2 -1 -3
-5 2 -6 7 8 7 8
-1 -2 -1 -3 4 6 0
7 8 7 8 -5 2 -6
}
bigskip
$A=pgflabtypeset{A}$
pgflabPLUQdecompose A as P times L times U times Q
% new matrix with desired entry
% entry can contain n@i and n@j
defpgflabnewmatrixof #1 by #2 with #3 as #4{
defpgflabh{#1}pgfkeys{/lab/#4/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#4/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfmathparse{#3}
pgfkeys{/lab/#4/n@i/n@j/.let=pgfmathresult}
}
}
}
clearpage
pgflabnewmatrixof 10 by 10 with rand as C
pgflabPLUQdecompose C as P times L times U times Q
% debug macro
% we can pass it to sage
% but we need to replace negative sign by ascii's -
defpgflabrawoutput#1{%
pgfkeys{/lab/#1/h/.get=pgflabh}%
pgfkeys{/lab/#1/w/.get=pgflabw}%
matrix([%
pgfplotsforeachungroupedt@i in{1,...,pgflabh}{%
[%
pgfplotsforeachungroupedt@j in{1,...,pgflabw}{%
pgfkeys{/lab/#1/t@i/t@j/.get=pgflabtempentry}%
pgfmathparse{pgflabtempentry}%
pgfmathfloattosci{pgfmathresult}%
mbox{pgfmathresult}%
ifnumt@j<pgflabw,hskip1ptplus3ptallowbreakfi
}%
]%
ifnumt@i<pgflabh,hskip1ptplus3ptallowbreakfi
}%
])%
}
clearpage
C=pgflabrawoutput{C};par
P=pgflabrawoutput{P};par
L=pgflabrawoutput{L};par
U=pgflabrawoutput{U};par
Q=pgflabrawoutput{Q};par
(C-P*L*U*Q).norm()
end{document}
debug mode
Old Answer
I would like to try
documentclass{article}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu}
begin{document}
% we are lazy
% let pgfplotstable read the matrix
pgfplotstableread[header=false]{
8 1 6 8
3 5 7 5
4 9 2 7
}matrixA
% we will store data by pgfleys
% create a handy handler
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% PS: /.initial is more like def, but we want xdef or edef or let
% but we also need some fast macros
pgfplotstablegetrowsofmatrixA xdefmatrixheight{pgfplotsretval}pgfkeys{/matrix/A/height/.let=matrixheight}
pgfplotstablegetcolsofmatrixA xdefmatrixwidth{pgfplotsretval} pgfkeys{/matrix/A/width/.let=matrixwidth}
% check data
Matrix $A$ is pgfkeys{/matrix/A/height} by pgfkeys{/matrix/A/width}.
In other words: par Matrix $A$ is matrixheight{} by matrixwidth{}.
% store the entries into pgfkeys
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
% since fpu is on, this is easier way to do 9+1
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
% check data
bigskip The matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: switch
defrowoperationswitch#1and#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfkeys{/matrix/A/#1/j/.let=tempmatrixentryB}
pgfkeys{/matrix/A/#2/j/.let=tempmatrixentryA}
}
}
% try and check
rowoperationswitch3and2
bigskip After switching row3 and row2, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: multiplication
defrowoperationmultiply#1by#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry*#2}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationmultiply3by9
bigskip After multiplying row3 by 9, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
remember: fpu is on! par
Human readable version par
defpgfmathprintmatrix{
pgfplotsforeachungroupedi in{1,...,matrixheight}{
indent
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry}
clap{pgfmathprintnumber{pgfmathresult}}hskip20pt
}
par
}
}
pgfmathprintmatrix
% define row operation: addition
defrowoperationadd#1by#2times#3 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfmathparse{tempmatrixentryA+tempmatrixentryB*#3}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationadd1by2times-1
bigskip After adding row2 by row1 times -1, the matrix entries are: par
pgfmathprintmatrix
% We do RREF by hand
pgfkeys{/pgf/number format/fixed}
bigskip We do RREF by hand par
add 2 by 1 times -1: par
rowoperationadd2by1times-1
pgfmathprintmatrix
medskip add 3 by 1 times -6.75: par
rowoperationadd3by1times-6.75
pgfmathprintmatrix
medskip add 3 by 2 times -5.8235: par
rowoperationadd3by2times-5.8235
pgfmathprintmatrix
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% Automatic RREF without row switching
% I is different form i
xdefmatrixheightminusone{thenumexprmatrixheight-1}
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
entry [I][I] is tempmatrixentryA par
entry [J][I] is tempmatrixentryB par
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
message{^^J^^JI,J,temprowscaler^^J^^J}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% maybe we need pivoting
defrowoperationfindpivot{
% find the maximal element in this column
defmaxofthiscolumn{-inf}
defmaxofthiscolumnindex{0}
pgfplotsforeachungroupedK in{I,...,matrixheight}{
pgfkeys{/matrix/A/K/I/.get=tempmatrixentry}
% compare
pgfmathparse{abs(tempmatrixentry)}
lettempmatrixabsentrypgfmathresult
pgfmathparse{maxofthiscolumn<tempmatrixabsentry}
% update if necessary
ifpgfmathfloatcomparison
letmaxofthiscolumntempmatrixabsentry
letmaxofthiscolumnindexK
fi
}
}
xdefI{1}
rowoperationfindpivot
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex
% Automatic RREF with partial pivot
defRREFwithpivoting{
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
rowoperationfindpivot
rowoperationswitchI and{maxofthiscolumnindex}
bigskip
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex par
so we switch rowI{} and rowmaxofthiscolumnindex, the matrix entries are: par
pgfmathprintmatrix
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
}
RREFwithpivoting
.......
The rest is deleted because of the length limitation.
Update-2
I heard someone said Givens rotations.
% Givens rotation
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotaterow #1 and row #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/w/.get=pgflabw}
pgfplotsforeachungroupedg@j in{1,...,pgflabw}{
pgfkeys{/lab/#5/#1/g@j/.get=pgflabtempentrya}
pgfkeys{/lab/#5/#2/g@j/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/#1/g@j/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/#2/g@j/.let=pgfmathresult}
}
}
% I assume #1 < #2
% does not use theta because it is unstable
% #4 is cosine and #5 is sine
defpgflabgivensrotatecol #1 and col #2 by #3 and #4 in #5{
pgfkeys{/lab/#5/h/.get=pgflabh}
pgfplotsforeachungroupedg@i in{1,...,pgflabh}{
pgfkeys{/lab/#5/g@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#5/g@i/#2/.get=pgflabtempentryb}
pgfmathparse{#3*pgflabtempentrya-#4*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#1/.let=pgfmathresult}
pgfmathparse{#4*pgflabtempentrya+#3*pgflabtempentryb}
pgfkeys{/lab/#5/g@i/#2/.let=pgfmathresult}
}
}
% A = QR decomposition
defpgflabQRdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/w/.get=pgflabW}
pgfkeys{/lab/#1/h/.get=pgflabH}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set Q as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% copy A to R
% copy #1 to #3
pgflabcopymatrix {#1} to {#3}
% forget A, do job at Q and R
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh}{
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempradius{sqrt(pgflabtempentrya*pgflabtempentrya+pgflabtempentryb*pgflabtempentryb)}
pgfmathsetmacropgflabtempcos{ pgflabtempentrya/pgflabtempradius} % cosine
pgfmathsetmacropgflabtempsin{-pgflabtempentryb/pgflabtempradius} % sine
pgflabgivensrotaterow {d@i} and row {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#3}
pgflabgivensrotatecol {d@i} and col {d@j} by {pgflabtempcos} and {pgflabtempsin} in {#2}
eliminate one entry. check Q and Rpar
$Q=pgflabtypeset{#2};$
$R=pgflabtypeset{#3};$
}
}
}
pgflabread{A}{
0 0 0 1
1 0 0 0
0 1 0 0
0 0 1 0
}
pgflabQRdecompose A as Q times R
For a 10 by 10 random matrix, the norm of A - QR is about 4e-4. The norm of QQᵀ - I is about 2e-4.
Update-1: New Answer
I implement three decompositions:
A = LU
A = PLU (i.e. partial pivoting)
A = PLUQ (i.e. complete pivoting)
If A is m by n, then P, L are m by m; U is the same as A; and Q is n by n.
Advantages
The complexity of accessing a matrix entry is O(1). (Assuming
csname
is O(1)). So the complexity of decompositions is O(m²n).The input utilize
pgfplotstableread
from pgfplotstable. So it accepts inline-table, file, loaded table, and even the table created bypgfplotstablenew
. You can also pass options to it. (such as filtering)The output utilize
pgfplotstabletypeset
from the same package. Or you can convert the matrix back to a table and do whatever you want.The calculation is done by
pgfmathparse
. I assume FPU is on. But one can reimplement that.There is a debug macro that output the raw data of matrices. You can copy and paste those data into whatever modern matrix calculator.
According to Wikipeida, even partial pivoting is numerically stable in practice. I test a 10 by 10 random matrix and check A - PLUQ in sage; the norm is about 1.1e-6. (This is about the precision of FPU)
documentclass{article}
usepackage[a3paper,landscape,margin=1cm]{geometry}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu,/pgf/number format/fixed}
begin{document}
makeatletter
% pgfmatrix... is used
% we use pgflab...
% call pgfplotstable to read the data
% put options in if desired
% the options go to pgfplotstableread
defpgflabread{
pgfutil@ifnextchar[
{pgflabread@opt}
{pgflabread@opt}
}
% #1: optional option
% #2: a name of the matrix... usually A
defpgflabread@opt[#1]#2{
edefpgflabname{#2}
pgfplotstableread[header=false,#1]
}
% we did not provide a macro to pgfplotstable to store the table
% we give it a temporary one called pgflabtemptable
% and then copy it to our data structure
longdefpgfplotstableread@impl@collectfirstarg#1#2{
pgfplotstableread@impl@{#1}{#2}pgflabtemptable
pgflabconverttablepgflabtemptable to matrix{pgflabname}
}
% this helps us to deal with pgfleys
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% copy pgfplotstable table to our data structure in pgfkeys
% #1: the macro that pgfplotstable used to store the table
% #2: a name of the matrix
defpgflabconverttable#1to matrix#2{
% extract height and width
pgfplotstablegetrowsof#1xdefpgflabh{pgfplotsretval}pgfkeys{/lab/#2/h/.let=pgflabh}
%%%height = pgflabh par
pgfplotstablegetcolsof#1xdefpgflabw{pgfplotsretval}pgfkeys{/lab/#2/w/.let=pgflabw}
%%%width = pgflabw par
% extract entries
% c@i and c@j cannot be used outside
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
% since fpu is on, this is easier way to do 9-1
pgfplotstablegetelem{thenumexprc@i-1}{thenumexprc@j-1}ofpgflabtemptable
pgfkeys{/lab/#2/c@i/c@j/.let=pgfplotsretval}
%%%pgfplotsretval,
}
%%%; par
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
% the opposite of the previous one
% #1: the name of the matrix
% #2: a macro for pgfplotstable to store the table
defpgflabconvertmatrix #1 to table #2{
% makeup meta data
expandafterdefcsnamestring#2@@table@nameendcsname{<inline_table>}
% build a new list of columns
pgfkeys{/lab/#1/h/.get=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}
pgfplotslistnew#2{0,...,thenumexprpgflabw-1}
% fill in columns
pgfplotsforeachungroupedc@j in{1,...,pgflabw}{
pgfplotslistnewemptypgflabtempcolumn
pgfplotsforeachungroupedc@i in{1,...,pgflabh}{
pgfkeys{/lab/#1/c@i/c@j/.get=pgflabtempentry}
expandafterpgfplotslistpushbackpgflabtempentrytopgflabtempcolumn
}
edefc@k{thenumexprc@j-1}
expandafterletcsnamestring#2@c@kendcsnamepgflabtempcolumn
}
}
% typeset the matrix by pgfplotstabletypeset
defpgflabtypeset{
pgfutil@ifnextchar[
{pgflabtypeset@opt}
{pgflabtypeset@opt}
}
% #1: optional option
% #2: the name of the matrix
defpgflabtypeset@opt[#1]#2{
pgflabconvertmatrix #2 to table pgflabtemptable
pgfplotstabletypeset[every head row/.style={output empty row}]pgflabtemptable
}
Matrix A is
$A=pgflabtypeset{A}$
% define row operation: switch
% does not check boundary
defpgflabswitchrow #1 and row #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupeds@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/s@j/.get=pgflabtempentrya}
pgfkeys{/lab/#3/#2/s@j/.get=pgflabtempentryb}
pgfkeys{/lab/#3/#1/s@j/.let=pgflabtempentryb}
pgfkeys{/lab/#3/#2/s@j/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchrow 1 and row 3 in A
switch row 1 and row 3;
$A=pgflabtypeset{A}$
% define column operation: switch
% does not check boundary
defpgflabswitchcol #1 and col #2 in #3{
pgfkeys{/lab/#3/h/.get=pgflabh}
pgfplotsforeachungroupeds@i in{1,...,pgflabh}{
pgfkeys{/lab/#3/s@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#3/s@i/#2/.get=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#1/.let=pgflabtempentryb}
pgfkeys{/lab/#3/s@i/#2/.let=pgflabtempentrya}
}
}
bigskip
pgflabswitchcol 2 and col 3 in A
switch col 2 and col 3;
$A=pgflabtypeset{A}$
% define row operation: multiplication
% does not check boundary
defpgflabmultiplyrow #1 by #2 in #3{
pgfkeys{/lab/#3/w/.get=pgflabw}
pgfplotsforeachungroupedm@j in{1,...,pgflabw}{
pgfkeys{/lab/#3/#1/m@j/.get=pgflabtempentry}
pgfmathparse{pgflabtempentry*#2}
pgfkeys{/lab/#3/#1/m@j/.let=pgfmathresult}
}
}
bigskip
pgflabmultiplyrow 3 by -1 in A
multiply row 3 by -1;
$A=pgflabtypeset{A}$
% define row operation: addition
% does not check boundary
defpgflabaddrow #1 by row #2 times #3 in #4{
pgfkeys{/lab/#4/w/.get=pgflabw}
pgfplotsforeachungroupeda@j in{1,...,pgflabw}{
pgfkeys{/lab/#4/#1/a@j/.get=pgflabtempentrya}
pgfkeys{/lab/#4/#2/a@j/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/#1/a@j/.let=pgfmathresult}
}
}
bigskip
pgflabaddrow 2 by row 3 times 2 in A
add row 2 by row 3 times 2;
$A=pgflabtypeset{A}$
% define column operation: addition
% does not check boundary
defpgflabaddcol #1 by col #2 times #3 in #4{
pgfkeys{/lab/#4/h/.get=pgflabh}
pgfplotsforeachungroupeda@i in{1,...,pgflabh}{
pgfkeys{/lab/#4/a@i/#1/.get=pgflabtempentrya}
pgfkeys{/lab/#4/a@i/#2/.get=pgflabtempentryb}
pgfmathparse{pgflabtempentrya+pgflabtempentryb*#3}
pgfkeys{/lab/#4/a@i/#1/.let=pgfmathresult}
}
}
bigskip
pgflabaddcol 5 by col 4 times -1 in A
add col 5 by row 4 times -1;
$A=pgflabtypeset{A}$
% new identity matrix
defpgflabneweyeof #1 by #2 as #3{
defpgflabh{#1}pgfkeys{/lab/#3/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#3/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
ifnumn@i=n@j
pgfkeys{/lab/#3/n@i/n@i/.initial=1}
else
pgfkeys{/lab/#3/n@i/n@j/.initial=0}
fi
}
}
}
bigskip
pgflabneweyeof 4 by 4 as I
identity matrix;
$A=pgflabtypeset{I}$
bigskip
pgflabneweyeof 3 by 5 as B
rectangular identity matrix;
$B=pgflabtypeset{B}$
% copy matrix
defpgflabcopymatrix #1 to #2{
pgfkeys{/lab/#1/h/.get=pgflabh}pgfkeys{/lab/#2/h/.let=pgflabh}
pgfkeys{/lab/#1/w/.get=pgflabw}pgfkeys{/lab/#2/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfkeys{/lab/#1/n@i/n@j/.get=pgflabtempentry}
pgfkeys{/lab/#2/n@i/n@j/.let=pgflabtempentry}
}
}
}
bigskip
pgflabcopymatrix A to B
copy matrix A to B;
$B=pgflabtypeset{B}$
% LU decomposition
% if encounter 0, probably will result in inf or nan
defpgflabLUdecompose #1 as #2 times #3{
pgfkeys{/lab/#1/h/.get=pgflabh@u}
pgfkeys{/lab/#1/w/.get=pgflabw@u}
% decide the loop boundary
edefpgflabh@v{thenumexprpgflabh@u-1}
ifnumpgflabh@v>pgflabw@u
edefpgflabh@v{pgflabw@u}
fi
% set L as identity
% set #2 as identity
pgflabneweyeof {pgflabh@u} by {pgflabh@u} as #2
% copy A to U
% copy #1 to #3
pgflabcopymatrix #1 to #3
% forget A, do job at L and U
% forget #1, do job at #2 and #3
pgfplotsforeachungroupedd@i in{1,...,pgflabh@v}{
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabh@u}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#3/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#3/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#2}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#3}
medskip
eliminate one entry. check L and U par
$L=pgflabtypeset{#2};$
$U=pgflabtypeset{#3};$
}
}
}
clearpage
$A=pgflabtypeset{A}$
pgflabLUdecompose A as L times U
% find pivot in the specific column
% find pivot in the range (#1,#1) to (#1,end)
% does not check boundary
defpgflabfindpivotatcol #1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfkeys{/lab/#2/f@i/#1/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
fi
}
}
clearpage
$A=pgflabtypeset{A}$
find pivot at specific column: par
pgflabfindpivotatcol 1 in A
at col 1 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 2 in A
at col 2 it is pgflabtempmax at row pgflabtempindex par
pgflabfindpivotatcol 3 in A
at col 2 it is pgflabtempmax at row pgflabtempindex
% A = PLU decomposition
% partial pivoting
defpgflabPLUdecompose #1 as #2 times #3 times #4{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotatcol {d@i} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
parmedskip
switch d@i{} and pgflabtempindexpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
parmedskip
eliminate one column. check P and L and U par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
}
}
pgflabread{A}{
3 1 -7 5 0
-9 -4 -8 -2 9
4 -3 6 0 -1
-5 8 2 -6 7
}
bigskip
pgflabPLUdecompose A as P times L times U
% find pivot in the specific column and row
% find pivot in the range (#1,#1) to (end,end)
% does not check boundary
defpgflabfindpivotafter#1 in #2{
pgfkeys{/lab/#2/h/.get=pgflabh}
pgfkeys{/lab/#2/w/.get=pgflabw}
defpgflabtempmax{-inf}
defpgflabtempindex{0}
defpgflabtempjndex{0}
pgfplotsforeachungroupedf@i in{#1,...,pgflabh}{
pgfplotsforeachungroupedf@j in{#1,...,pgflabw}{
pgfkeys{/lab/#2/f@i/f@j/.get=pgflabtempentry}
% compare the abs value
pgfmathsetmacropgflabtempentry{abs(pgflabtempentry)}
pgfmathparse{pgflabtempmax<pgflabtempentry}
% update if necessary
ifpgfmathfloatcomparison
letpgflabtempmaxpgflabtempentry
letpgflabtempindexf@i
letpgflabtempjndexf@j
fi
}
}
}
% A = PLUQ decomposition
% partial pivoting
defpgflabPLUQdecompose #1 as #2 times #3 times #4 times #5{
pgfkeys{/lab/#1/h/.get=pgflabH}
pgfkeys{/lab/#1/w/.get=pgflabW}
% decide the loop boundary
edefpgflab@H-1{thenumexprpgflabH-1}
ifnumpgflab@H-1>pgflabW
edefpgflab@H-1{pgflabW}
fi
% set P as identity
% set #2 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#2}
% set L as identity
% set #3 as identity
pgflabneweyeof {pgflabH} by {pgflabH} as {#3}
% copy A to U
% copy #1 to #4
pgflabcopymatrix {#1} to {#4}
% set Q as identity
% set #5 as identity
pgflabneweyeof {pgflabW} by {pgflabW} as {#5}
% forget A, do job at P and L and U
% forget #1, do job at #2 and #3 and #4
pgfplotsforeachungroupedd@i in{1,...,pgflab@H-1}{
pgflabfindpivotafter {d@i} in #4
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#4}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#3}
pgflabswitchrow {d@i} and row {pgflabtempindex} in {#3}
pgflabswitchcol {d@i} and col {pgflabtempindex} in {#2}
{}
pgflabswitchcol {d@i} and col {pgflabtempjndex} in {#4}
pgflabswitchrow {d@i} and row {pgflabtempjndex} in {#5}
switch (d@i{},d@i{}) and (pgflabtempindex,pgflabtempjndex) par
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
edefd@@i+1{thenumexprd@i+1}
pgfplotsforeachungroupedd@j in{d@@i+1,...,pgflabH}{
% use (d@i,d@i) to eliminate (d@j,d@i)
pgfkeys{/lab/#4/d@i/d@i/.get=pgflabtempentrya}
pgfkeys{/lab/#4/d@j/d@i/.get=pgflabtempentryb}
pgfmathsetmacropgflabtempratio{pgflabtempentryb/pgflabtempentrya}
pgflabaddcol {d@i} by col {d@j} times {pgflabtempratio} in {#3}
pgflabaddrow {d@j} by row {d@i} times {-pgflabtempratio} in {#4}
}
eliminate one column. check P and L and U and Qpar
$P=pgflabtypeset{#2};$
$L=pgflabtypeset{#3};$
$U=pgflabtypeset{#4};$
$Q=pgflabtypeset{#5};$
}
}
pgflabread{A}{
3 -7 5 0 1 0 1
-9 -8 -2 9 -1 9 -4
4 6 0 -1 -2 -1 -3
-5 2 -6 7 8 7 8
-1 -2 -1 -3 4 6 0
7 8 7 8 -5 2 -6
}
bigskip
$A=pgflabtypeset{A}$
pgflabPLUQdecompose A as P times L times U times Q
% new matrix with desired entry
% entry can contain n@i and n@j
defpgflabnewmatrixof #1 by #2 with #3 as #4{
defpgflabh{#1}pgfkeys{/lab/#4/h/.let=pgflabh}
defpgflabw{#2}pgfkeys{/lab/#4/w/.let=pgflabw}
pgfplotsforeachungroupedn@i in{1,...,pgflabh}{
pgfplotsforeachungroupedn@j in{1,...,pgflabw}{
pgfmathparse{#3}
pgfkeys{/lab/#4/n@i/n@j/.let=pgfmathresult}
}
}
}
clearpage
pgflabnewmatrixof 10 by 10 with rand as C
pgflabPLUQdecompose C as P times L times U times Q
% debug macro
% we can pass it to sage
% but we need to replace negative sign by ascii's -
defpgflabrawoutput#1{%
pgfkeys{/lab/#1/h/.get=pgflabh}%
pgfkeys{/lab/#1/w/.get=pgflabw}%
matrix([%
pgfplotsforeachungroupedt@i in{1,...,pgflabh}{%
[%
pgfplotsforeachungroupedt@j in{1,...,pgflabw}{%
pgfkeys{/lab/#1/t@i/t@j/.get=pgflabtempentry}%
pgfmathparse{pgflabtempentry}%
pgfmathfloattosci{pgfmathresult}%
mbox{pgfmathresult}%
ifnumt@j<pgflabw,hskip1ptplus3ptallowbreakfi
}%
]%
ifnumt@i<pgflabh,hskip1ptplus3ptallowbreakfi
}%
])%
}
clearpage
C=pgflabrawoutput{C};par
P=pgflabrawoutput{P};par
L=pgflabrawoutput{L};par
U=pgflabrawoutput{U};par
Q=pgflabrawoutput{Q};par
(C-P*L*U*Q).norm()
end{document}
debug mode
Old Answer
I would like to try
documentclass{article}
usepackage{pgfplotstable,mathtools}
pgfplotsset{compat=newest}
pgfkeys{/pgf/fpu}
begin{document}
% we are lazy
% let pgfplotstable read the matrix
pgfplotstableread[header=false]{
8 1 6 8
3 5 7 5
4 9 2 7
}matrixA
% we will store data by pgfleys
% create a handy handler
pgfkeys{/handlers/.let/.code=pgfkeyslet{pgfkeyscurrentpath}{#1}}
% PS: /.initial is more like def, but we want xdef or edef or let
% but we also need some fast macros
pgfplotstablegetrowsofmatrixA xdefmatrixheight{pgfplotsretval}pgfkeys{/matrix/A/height/.let=matrixheight}
pgfplotstablegetcolsofmatrixA xdefmatrixwidth{pgfplotsretval} pgfkeys{/matrix/A/width/.let=matrixwidth}
% check data
Matrix $A$ is pgfkeys{/matrix/A/height} by pgfkeys{/matrix/A/width}.
In other words: par Matrix $A$ is matrixheight{} by matrixwidth{}.
% store the entries into pgfkeys
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
% since fpu is on, this is easier way to do 9+1
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
% check data
bigskip The matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: switch
defrowoperationswitch#1and#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfkeys{/matrix/A/#1/j/.let=tempmatrixentryB}
pgfkeys{/matrix/A/#2/j/.let=tempmatrixentryA}
}
}
% try and check
rowoperationswitch3and2
bigskip After switching row3 and row2, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
% define row operation: multiplication
defrowoperationmultiply#1by#2 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry*#2}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationmultiply3by9
bigskip After multiplying row3 by 9, the matrix entries are: par
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j},
}
; par
}
remember: fpu is on! par
Human readable version par
defpgfmathprintmatrix{
pgfplotsforeachungroupedi in{1,...,matrixheight}{
indent
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/i/j/.get=tempmatrixentry}
pgfmathparse{tempmatrixentry}
clap{pgfmathprintnumber{pgfmathresult}}hskip20pt
}
par
}
}
pgfmathprintmatrix
% define row operation: addition
defrowoperationadd#1by#2times#3 {
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfkeys{/matrix/A/#1/j/.get=tempmatrixentryA}
pgfkeys{/matrix/A/#2/j/.get=tempmatrixentryB}
pgfmathparse{tempmatrixentryA+tempmatrixentryB*#3}
pgfkeys{/matrix/A/#1/j/.let=pgfmathresult}
}
}
% try and check
rowoperationadd1by2times-1
bigskip After adding row2 by row1 times -1, the matrix entries are: par
pgfmathprintmatrix
% We do RREF by hand
pgfkeys{/pgf/number format/fixed}
bigskip We do RREF by hand par
add 2 by 1 times -1: par
rowoperationadd2by1times-1
pgfmathprintmatrix
medskip add 3 by 1 times -6.75: par
rowoperationadd3by1times-6.75
pgfmathprintmatrix
medskip add 3 by 2 times -5.8235: par
rowoperationadd3by2times-5.8235
pgfmathprintmatrix
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% Automatic RREF without row switching
% I is different form i
xdefmatrixheightminusone{thenumexprmatrixheight-1}
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
entry [I][I] is tempmatrixentryA par
entry [J][I] is tempmatrixentryB par
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
message{^^J^^JI,J,temprowscaler^^J^^J}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
% renew A
pgfplotsforeachungroupedi in{1,...,matrixheight}{
pgfplotsforeachungroupedj in{1,...,matrixwidth}{
pgfplotstablegetelem{thenumexpri-1}{thenumexprj-1}ofmatrixA % lazy~~
pgfkeys{/matrix/A/i/j/.let=pgfplotsretval}
}
}
clearpage Restart with $A$ par
pgfmathprintmatrix
% maybe we need pivoting
defrowoperationfindpivot{
% find the maximal element in this column
defmaxofthiscolumn{-inf}
defmaxofthiscolumnindex{0}
pgfplotsforeachungroupedK in{I,...,matrixheight}{
pgfkeys{/matrix/A/K/I/.get=tempmatrixentry}
% compare
pgfmathparse{abs(tempmatrixentry)}
lettempmatrixabsentrypgfmathresult
pgfmathparse{maxofthiscolumn<tempmatrixabsentry}
% update if necessary
ifpgfmathfloatcomparison
letmaxofthiscolumntempmatrixabsentry
letmaxofthiscolumnindexK
fi
}
}
xdefI{1}
rowoperationfindpivot
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex
% Automatic RREF with partial pivot
defRREFwithpivoting{
pgfplotsforeachungroupedI in{1,...,matrixheightminusone}{
rowoperationfindpivot
rowoperationswitchI and{maxofthiscolumnindex}
bigskip
For column I, the maximum is pgfmathprintnumber{maxofthiscolumn} at row maxofthiscolumnindex par
so we switch rowI{} and rowmaxofthiscolumnindex, the matrix entries are: par
pgfmathprintmatrix
pgfplotsforeachungroupedJ in{I,...,matrixheightminusone}{
xdefJ{thenumexprJ+1}
pgfkeys{/matrix/A/I/I/.get=tempmatrixentryA}
pgfkeys{/matrix/A/J/I/.get=tempmatrixentryB}
bigskip
pgfmathparse{-tempmatrixentryB/tempmatrixentryA}
xdeftemprowscaler{pgfmathresult}
add rowJ{} by rowI{} times pgfmathprintnumber{temprowscaler} par
rowoperationaddJ byI times{temprowscaler}
pgfmathprintmatrix
}
}
}
RREFwithpivoting
.......
The rest is deleted because of the length limitation.
edited Mar 25 '17 at 3:52
answered Mar 22 '17 at 20:52
Symbol 1
24.2k235115
24.2k235115
By the way, the product of pivots is -862566.82. And by Wolfram|Alpha the determinant of the first six columns is 862575. So the error is .0009%
– Symbol 1
Mar 22 '17 at 21:09
add a comment |
By the way, the product of pivots is -862566.82. And by Wolfram|Alpha the determinant of the first six columns is 862575. So the error is .0009%
– Symbol 1
Mar 22 '17 at 21:09
By the way, the product of pivots is -862566.82. And by Wolfram|Alpha the determinant of the first six columns is 862575. So the error is .0009%
– Symbol 1
Mar 22 '17 at 21:09
By the way, the product of pivots is -862566.82. And by Wolfram|Alpha the determinant of the first six columns is 862575. So the error is .0009%
– Symbol 1
Mar 22 '17 at 21:09
add a comment |
up vote
19
down vote
This answer has some macros picked up from https://tex.stackexchange.com/a/143035/4686. I am not too happy with some internal data structure, but I decided to live it standing.
The https://tex.stackexchange.com/a/143035/4686 computes determinants, inverses, etc..., either exactly or with float operations.
Here I focus on exact computations. The matrix entries may be integers, fractions, decimal numbers, or in scientific notation, but they are handled exactly. Hence, there is no question of numerical instability here. Regarding input, the format is with semi-colon separated rows of comma separated coefficients.
The last edit improves some internal aspects, has a better example for A=PLUQ
, and redoes the initial example of Row Reduction to use for display truncated, not rounded, decimal expansions as they are followed with dots.
PLUQ ANSWER
The code typesets with TeX and also outputs to a file in Maple matrix notation
the final result, for example.
A:=Matrix([[3, 1, -7, 5, 0, 9, -9, 7, -5], [-9, -4, 22, -14, 9, 2, 7, -6, -8], [-6, -3, 15, -9, 9, 11, -2, 1, -13], [-5, 8, 2, -18, 7, -1, 8, -7, 0], [4, 6, -14, 2, 1, -5, 6, 5, -3], [-11, 5, 17, -27, 16, 10, 6, -6, -13]]);
P:=Matrix([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1]]);
L:=Matrix([[1, 0, 0, 0, 0, 0], [-3, 1, 0, 0, 0, 0], [-5/3, -29/3, 1, 0, 0, 0], [4/3, -14/3, 43/94, 1, 0, 0], [-2, 1, 0, 0, 1, 0], [-11/3, -26/3, 1, 0, 0, 1]]);
U:=Matrix([[3, 1, 0, 9, -7, 5, -9, 7, -5], [0, -1, 9, 29, 1, 1, -20, 15, -23], [0, 0, 94, 883/3, 0, 0, -601/3, 449/3, -692/3], [0, 0, 0, -1533/94, 0, 0, 1533/94, -263/94, 87/47], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]);
Q:=Matrix([[1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1]]);
Now we can copy paste into Maple and check that indeed A=PLUQ
:
> with(LinearAlgebra):
> MatrixAdd(A,-P.L.U.Q);
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
Notice that in a PLUQ decomposition, a P and a Q will appear with my code only if necessary.
documentclass[a4paper]{article}
usepackage[hscale=0.85, vscale=0.85]{geometry}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
% usepackage {siunitx}
% usepackage {numprint}
catcode`_ 11
makeatletter
newwriteMATout
immediateopenoutMATout=jobname.pluqoutrelax
% (the typeout format is for input in Maple for example)
defMATtypeout {MATtypeoutwith {MATtypeoutone}}%
defMATtypeoutone #1{xintPRaw{xintRawWithZeros{#1}}}% (lacking an xintPRawWithZeros)
defMATtypeoutwith #1#2#3{%
edefI{xintSeq {1}{#3[I]}}% indices for rows
edefJ{xintSeq {1}{#3[J]}}% indices for columns
immediatewriteMATout{#2:=Matrix([[%
xintListWithSep {], [}{xintApply { MAT_typeout_row {#1}#3}{I}}%
]]);}%
}%
defMAT_typeout_row #1#2#3{%
xintListWithSep {, }{xintApply { MAT_typeout_one {#1}#2{#3}}{J}}%
}%
defMAT_typeout_one #1#2#3#4{#1{#2[#3,#4]}}%
% we don't need all of them
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
% The final semi-colon is optional.
% We indeed focus here on manipulating matrices with rational entries, the
% code at https://tex.stackexchange.com/a/143035/4686 has the set-up for
% floating point numbers too (in an arbitrary, user decided precision).
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% a bit convoluted, no comments.
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
% removed toks2 et toks4 usage from https://tex.stackexchange.com/a/143035/4686
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% We need identity matrices.
% again copied as is from https://tex.stackexchange.com/a/143035/4686
% IDENTITY MATRIX
% usage MATidfoo{37} defines a 37 times 37 identity matrix.
defMATid {defMAT_tmpf{/1}MAT_id }%
%defMATfloatid {defMAT_tmpf{}MAT_id }%
% This identity matrix insists on coefficients written internally
% 0[0] or 1[0], this is a remnant of
% https://tex.stackexchange.com/a/143035/4686 whose aim is is minuscule
% optimization when these numbers are involved in computations done by
% the xintfrac macros.
defMAT_id #1#2{%
MAT_cntc #2relax
MAT_cnta xint_c_i % 1
xintloop
{expandafterdefexpandafterMAT_tmpa expandafter{theMAT_cnta}%
MAT_cntb xint_c_i % 1
xintloop
expandafteredef
csname MAT@string#1{MAT_tmpa}{theMAT_cntb}endcsname
{ifnumMAT_cntb=MAT_cnta 1else 0fi MAT_tmpf[0]}%
ifnumMAT_cntb<MAT_cntc
advanceMAT_cntb xint_c_i
repeat
ifnumMAT_cnta<MAT_cntc
advanceMAT_cnta xint_c_i
}repeat
expandafterdefcsname MAT@string#1{I}expandafterendcsname
expandafter {theMAT_cntc}%
expandafterdefcsname MAT@string#1{J}expandafterendcsname
expandafter {theMAT_cntc}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% EXCHANGING ROWS OR COLUMNS OF A GIVEN MATRIX
defMATexchangecol #1#2#3{%
MAT_cnta=#3[I]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{theMAT_cntb}{#1}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#1}expandafterendcsname
csname MAT@string#3{theMAT_cntb}{#2}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#2}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
% perhaps only columns "to the right" actually need exchange in usage of this
defMATexchangerow #1#2#3{%
MAT_cnta=#3[J]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
defMATexchangerowspecial #1#2#3{%#1>#2, only columns <#2 need update
MAT_cnta=#2relax
MAT_cntb=xint_c_ % 0
xintloop
advanceMAT_cntbxint_c_i
ifnumMAT_cntb<MAT_cnta
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
repeat
}%
% Usage:
% MATpluqA (A previously defined by MATset)
% Effect: sets P, L, U, Q, to matrices in the sense of MATset,
% so that "A=PLUQ" and it writes all matrices out
% to some file. See initial answer about row reduction for typesetting
% in document.
% The code is a simple adaptation of this initial answer. Now I use MATpluq
% prefix.
defMATpluq #1{%
% begingroup
MATlet@U#1%
edefMATpluq@rows{@U[I]}% nb of rows
edefMATpluq@cols{@U[J]}% nb of columns.
MATid@PMATpluq@rows
MATid@LMATpluq@rows
MATid@QMATpluq@cols
defMATpluq@pivrow {0}%
defMATpluq@pivcol {0}%
%edefMATpluq@name {string#1}%
letMATpluq@ifcontinueiftrue
% Starting the reduction.
MATtypeout{^^JA}#1%
[A = MATdisplay@U]
xintloop
% Nota Bene: in the PLUQ reduction, the pivots are anyhow organized
% along the main diagonal so pivrow and pivcol will be kept in sync over
% the execution of the algorithm but we use two variables nevertheless.
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
edefMATpluq@pivcol{thenumexprMATpluq@pivcol+xint_c_i}%
MATpluq@dopiv
MATpluq@ifcontinue
repeat
% Done. The rank of the matrix is thenumexprMATpluq@pivrow-xint_c_i.par
% endgroup
MATtypeout{P}@P
MATtypeout{L}@L
MATtypeout{U}@U
MATtypeout{Q}@Q
[ P = MATdisplay@P]
[ L = MATdisplay@Lqquad U = MATdisplay@U]
[ Q = MATdisplay@Q]
}
defMATpluq@done {letMATpluq@ifcontinueiffalse}
% Remark on algorithm: I hesitated about doing column permutations first,
% rather than row permutations with the idea to recognize faster an entirely
% vanishing row, so that we can put it at the end and ignore it entirely, in
% effect reducing the number of rows by one, and possibly making algorithm
% faster. But for simplicity I just keep algorithm close to the one as in my
% initial answer. We only have to keep track in P, L, Q of the needed
% operations.
defMATpluq@dopiv{%
letMATpluq@rowMATpluq@pivrow
letMATpluq@colMATpluq@pivcol
ifnumMATpluq@row>MATpluq@rowsrelax
MATpluq@done
else
ifnumMATpluq@col>MATpluq@colsrelax
MATpluq@done
else
expandafterexpandafterexpandafterMATpluq@dopiv@i
fi
fi
}
defMATpluq@dopiv@i{%
edefMATpluq@piv@value{@U[MATpluq@row,MATpluq@col]}%
xintifZero{MATpluq@piv@value}
MATpluq@dopiv@steprow
MATpluq@dopiv@ii
}
defMATpluq@dopiv@steprow{%
ifnumMATpluq@row=MATpluq@rowsrelax
par No pivot found in column MATpluq@col.par
letMATpluq@rowMATpluq@pivrow
expandafterMATpluq@dopiv@stepcol
else
edefMATpluq@row{thenumexprMATpluq@row+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
defMATpluq@dopiv@stepcol{%
ifnumMATpluq@col=MATpluq@colsrelax
MATpluq@done
else
edefMATpluq@col{thenumexprMATpluq@col+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
% found a pivot
defMATpluq@dopiv@ii{%
Pivot MATpluqprintonevalue{MATpluq@piv@value} at MATpluq@row, MATpluq@col.par
ifnumMATpluq@col>MATpluq@pivcolrelax
Exchange of columns MATpluq@pivcolspace and MATpluq@col.par
MATexchangerow{MATpluq@col}{MATpluq@pivcol}@Q
MATexchangecol{MATpluq@col}{MATpluq@pivcol}@U
[U = MATdisplay@Uqquad Q = MATdisplay@Q]
fi
ifnumMATpluq@pivrow=MATpluq@rowsrelax
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
MATpluq@done
else
expandafterMATpluq@dopiv@iii
fi
}
defMATpluq@dopiv@iii{%
ifnumMATpluq@row>MATpluq@pivrowrelax
Exchange of rows MATpluq@pivrowspace and MATpluq@row.par
MATexchangecol{MATpluq@row}{MATpluq@pivrow}@P
MATexchangerow{MATpluq@row}{MATpluq@pivrow}@U
MATexchangerowspecial{MATpluq@row}{MATpluq@pivrow}@L
[L = MATdisplay@Lqquad U = MATdisplay@U]
[P = MATdisplay@P]
fi
MAT_cntcMATpluq@pivrowrelax% we are guaranteed < nb of rows
xintloop
advanceMAT_cntcxint_c_i
edefMATpluq@entry{@U[MAT_cntc,MATpluq@pivcol]}%
xintifZeroMATpluq@entry
{% nothing to do, the L coeff is already set to zero
}%
{edefMATpluq@ratio
{xintIrr{xintDiv{MATpluq@entry}{MATpluq@piv@value}}[0]}%
expandafterlet
csname MAT@string@L{theMAT_cntc}{MATpluq@pivcol}endcsname
MATpluq@ratio
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATpluqprintonevalue{MATpluq@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATpluq@pivcol}}{0[0]}%
MAT_cntdMATpluq@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATpluq@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATpluq@ratio}{@U[MATpluq@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATpluq@rows=MAT_cntc
repeat
[L = MATdisplay@Lqquad U = MATdisplay@U]
}
defMATpluqprintonevalue{xintPRaw}
%defMATpluqdisplay#1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}pagestyle{empty}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 1/11 ; }
MATpluqMatrixA
See pluqout file.clearpage
MATsetA {
3, -7, 5, 0, 1, 0, 1;
-9, -8, -2, 9, -1, 9, -4;
4, 6, 0, -1, -2, -1, -3;
-5, 2, -6, 7, 8, 7, 8;
-1, -2, -1, -3, 4, 6, 0;
7, 8, 7, 8, -5, 2, -6;
}
MATpluqA
See pluqout file.clearpage
MATsetA {
2, 0, 3, 0;
1, 0, 0, 0;
0, 0, 4, 0;
0, 2, 0, 1;
}
MATpluqA
See pluqout file.clearpage
MATsetMatrixB {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
4, -3, 6, 0, -1, 5, -4, -3, 4;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
3, 6, -2, -1, 8, -2, -6, 7, -7;
4, 6, 3, -9, 1, -5, 0, 5, -3;
}
MATpluqMatrixB
See pluqout file.clearpage
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, 22, -14, 9, 2, 7, -6, -8;
-6, -3, 15, -9, 9, 11, -2, 1, -13;
-5, 8, 2, -18, 7, -1, 8, -7, 0;
4, 6, -14, 2, 1, -5, 6, 5, -3;
-11, 5, 17, -27, 16, 10, 6, -6, -13;
}
MATpluqMatrixC
See pluqout file for the matrices in Maple format.clearpage
immediatecloseoutMATout
end{document}
ROW REDUCTION (INITIAL) ANSWER
I have improved a bit some internal aspects of the code in an edit.
documentclass{article}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
catcode`_ 11
makeatletter
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
%
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
defMATrowreduce #1{%
begingroup
edefMATrr@rows{#1[I]}%
edefMATrr@cols{#1[J]}%
defMATrr@pivrow {0}%
defMATrr@pivcol {0}%
MATlet@U #1%
letMATrr@ifcontinueiftrue
Starting the reduction.
MATrrdisplaymatrix@U
xintloop
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}%
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
MATrr@dopiv
MATrr@ifcontinue
repeat
Done. The rank of the matrix is thenumexprMATrr@pivrow-xint_c_i.par
endgroup
}
defMATrr@done {letMATrr@ifcontinueiffalse}
defMATrr@dopiv{%
letMATrr@rowMATrr@pivrow
letMATrr@colMATrr@pivcol
ifnumMATrr@row>MATrr@rowsrelax
MATrr@done
else
ifnumMATrr@col>MATrr@colsrelax
MATrr@done
else
expandafterexpandafterexpandafterMATrr@dopiv@i
fi
fi
}
defMATrr@dopiv@i{%
edefMATrr@piv@value{@U[MATrr@row,MATrr@pivcol]}%
xintifZero{MATrr@piv@value}
MATrr@dopiv@steprow
MATrr@dopiv@ii
}
defMATrr@dopiv@steprow{%
ifnumMATrr@row=MATrr@rowsrelax
letMATrr@rowMATrr@pivrow
par No pivot found in column MATrr@pivcol.par
expandafterMATrr@dopiv@stepcol
else
edefMATrr@row{thenumexprMATrr@row+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@stepcol{%
ifnumMATrr@pivcol=MATrr@colsrelax
MATrr@done
else
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@ii{%
ifnumMATrr@pivrow=MATrr@rowsrelax
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}MATrr@done
else
expandafterMATrr@dopiv@iii
fi
}
defMATrr@dopiv@iii{%
Now using the pivot with value MATrrprintonevalue{MATrr@piv@value}
at row MATrr@rowspace and column MATrr@pivcol.par
ifnumMATrr@row>MATrr@pivrowrelax
Exchange of row MATrr@rowspace with row MATrr@pivrow.par
MAT_cntb=MATrr@pivcolrelax
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string@U{MATrr@row}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@row}{theMAT_cntb}expandafterendcsname
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
MAT@tmp
ifnumMATrr@cols>MAT_cntb
advanceMAT_cntbxint_c_i
repeat
MATrrdisplaymatrix@Upar
fi
MAT_cntcMATrr@pivrow
xintloop
advanceMAT_cntcxint_c_i
edefMATrr@entry{@U[MAT_cntc,MATrr@pivcol]}%
xintifZeroMATrr@entry
{}%
{edefMATrr@ratio{xintIrr{xintDiv{MATrr@entry}{MATrr@piv@value}}[0]}%
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATrrprintonevalue{MATrr@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATrr@pivcol}}{0[0]}%
MAT_cntdMATrr@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATrr@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATrr@ratio}{@U[MATrr@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATrr@rows=MAT_cntc
repeat
MATrrdisplaymatrix@U
}
defMATrrprintonevalue{xintPRaw}
defMATrrdisplaymatrix #1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
-6, -3, -15, 3, 9, 11, -1, 1, -13;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
4, 6, 3, -9, 1, -5, 0, 5, -3;
-11, 5, -13, -3, 16, 10, 0, -6, -13;
}
MATrowreduceMatrixC
end{document}
Entries may be decimal numbers like 37.156
.
defMATrrprintonevalue{xintRound{2}}
defMATrrdisplaymatrix #1{[MATdisplaywith{xintRound{2}}#1]}%
Example (as I add dots, I use truncating rather than rounding):
defMATrrprintonevalue#1{xintTrunc{3}{#1}dots (=xintPRaw{#1})}
defMATrrdisplaymatrix #1{[MATdisplay#1=MATdisplaywith{TruncWithDots{3}}#1]}%
defTruncWithDots #1#2{xintTrunc{#1}{#2}...}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 0.09 ; }
MATrowreduceMatrixA
It is important to stress relative toPLUQ
(and even to Row Reduction) that I am doing exact computations, thus there is no need for the algorithm to look for the largest possible pivot, with the attendant fact that entries ofL
would be smaller than one in absolute value. Thus I do not follow here the algorithm fashionable in numerical analysis (by the way, it may in exceptional cases have stability problems, theU
matrix containing exponentially big entries respective to the size of the matrices). I just pick up the first available pivot irrespective of its size.
– jfbu
Mar 25 '17 at 14:49
don't you want Bruhat decomposition of invertible matrices ? that will be a challenge to numerical analysis, but no problem for exact computations.
– jfbu
Mar 25 '17 at 14:57
by the way Maple'sLUDecomposition
operates like this: For Matrices with... at least one floating-point entry, pivots ... according to absolute magnitude. For Matrices with ... rational... entries, pivots ... the first nonzero element in the current column.. and indeed this is what I did here. One can check that withp, l, u := LUDecomposition(A);
one does getp=P
,l = L
, andu = U.Q
, whereP, L, U, Q
are computed as above. (Maple16)
– jfbu
Mar 25 '17 at 18:01
At first glance, this looks great. I'll need to give it a more thorough inspection before I reward you with the 500 reputation and risqué pics though. ;)
– Quinn Culver
Mar 25 '17 at 22:30
add a comment |
up vote
19
down vote
This answer has some macros picked up from https://tex.stackexchange.com/a/143035/4686. I am not too happy with some internal data structure, but I decided to live it standing.
The https://tex.stackexchange.com/a/143035/4686 computes determinants, inverses, etc..., either exactly or with float operations.
Here I focus on exact computations. The matrix entries may be integers, fractions, decimal numbers, or in scientific notation, but they are handled exactly. Hence, there is no question of numerical instability here. Regarding input, the format is with semi-colon separated rows of comma separated coefficients.
The last edit improves some internal aspects, has a better example for A=PLUQ
, and redoes the initial example of Row Reduction to use for display truncated, not rounded, decimal expansions as they are followed with dots.
PLUQ ANSWER
The code typesets with TeX and also outputs to a file in Maple matrix notation
the final result, for example.
A:=Matrix([[3, 1, -7, 5, 0, 9, -9, 7, -5], [-9, -4, 22, -14, 9, 2, 7, -6, -8], [-6, -3, 15, -9, 9, 11, -2, 1, -13], [-5, 8, 2, -18, 7, -1, 8, -7, 0], [4, 6, -14, 2, 1, -5, 6, 5, -3], [-11, 5, 17, -27, 16, 10, 6, -6, -13]]);
P:=Matrix([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1]]);
L:=Matrix([[1, 0, 0, 0, 0, 0], [-3, 1, 0, 0, 0, 0], [-5/3, -29/3, 1, 0, 0, 0], [4/3, -14/3, 43/94, 1, 0, 0], [-2, 1, 0, 0, 1, 0], [-11/3, -26/3, 1, 0, 0, 1]]);
U:=Matrix([[3, 1, 0, 9, -7, 5, -9, 7, -5], [0, -1, 9, 29, 1, 1, -20, 15, -23], [0, 0, 94, 883/3, 0, 0, -601/3, 449/3, -692/3], [0, 0, 0, -1533/94, 0, 0, 1533/94, -263/94, 87/47], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]);
Q:=Matrix([[1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1]]);
Now we can copy paste into Maple and check that indeed A=PLUQ
:
> with(LinearAlgebra):
> MatrixAdd(A,-P.L.U.Q);
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
Notice that in a PLUQ decomposition, a P and a Q will appear with my code only if necessary.
documentclass[a4paper]{article}
usepackage[hscale=0.85, vscale=0.85]{geometry}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
% usepackage {siunitx}
% usepackage {numprint}
catcode`_ 11
makeatletter
newwriteMATout
immediateopenoutMATout=jobname.pluqoutrelax
% (the typeout format is for input in Maple for example)
defMATtypeout {MATtypeoutwith {MATtypeoutone}}%
defMATtypeoutone #1{xintPRaw{xintRawWithZeros{#1}}}% (lacking an xintPRawWithZeros)
defMATtypeoutwith #1#2#3{%
edefI{xintSeq {1}{#3[I]}}% indices for rows
edefJ{xintSeq {1}{#3[J]}}% indices for columns
immediatewriteMATout{#2:=Matrix([[%
xintListWithSep {], [}{xintApply { MAT_typeout_row {#1}#3}{I}}%
]]);}%
}%
defMAT_typeout_row #1#2#3{%
xintListWithSep {, }{xintApply { MAT_typeout_one {#1}#2{#3}}{J}}%
}%
defMAT_typeout_one #1#2#3#4{#1{#2[#3,#4]}}%
% we don't need all of them
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
% The final semi-colon is optional.
% We indeed focus here on manipulating matrices with rational entries, the
% code at https://tex.stackexchange.com/a/143035/4686 has the set-up for
% floating point numbers too (in an arbitrary, user decided precision).
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% a bit convoluted, no comments.
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
% removed toks2 et toks4 usage from https://tex.stackexchange.com/a/143035/4686
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% We need identity matrices.
% again copied as is from https://tex.stackexchange.com/a/143035/4686
% IDENTITY MATRIX
% usage MATidfoo{37} defines a 37 times 37 identity matrix.
defMATid {defMAT_tmpf{/1}MAT_id }%
%defMATfloatid {defMAT_tmpf{}MAT_id }%
% This identity matrix insists on coefficients written internally
% 0[0] or 1[0], this is a remnant of
% https://tex.stackexchange.com/a/143035/4686 whose aim is is minuscule
% optimization when these numbers are involved in computations done by
% the xintfrac macros.
defMAT_id #1#2{%
MAT_cntc #2relax
MAT_cnta xint_c_i % 1
xintloop
{expandafterdefexpandafterMAT_tmpa expandafter{theMAT_cnta}%
MAT_cntb xint_c_i % 1
xintloop
expandafteredef
csname MAT@string#1{MAT_tmpa}{theMAT_cntb}endcsname
{ifnumMAT_cntb=MAT_cnta 1else 0fi MAT_tmpf[0]}%
ifnumMAT_cntb<MAT_cntc
advanceMAT_cntb xint_c_i
repeat
ifnumMAT_cnta<MAT_cntc
advanceMAT_cnta xint_c_i
}repeat
expandafterdefcsname MAT@string#1{I}expandafterendcsname
expandafter {theMAT_cntc}%
expandafterdefcsname MAT@string#1{J}expandafterendcsname
expandafter {theMAT_cntc}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% EXCHANGING ROWS OR COLUMNS OF A GIVEN MATRIX
defMATexchangecol #1#2#3{%
MAT_cnta=#3[I]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{theMAT_cntb}{#1}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#1}expandafterendcsname
csname MAT@string#3{theMAT_cntb}{#2}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#2}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
% perhaps only columns "to the right" actually need exchange in usage of this
defMATexchangerow #1#2#3{%
MAT_cnta=#3[J]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
defMATexchangerowspecial #1#2#3{%#1>#2, only columns <#2 need update
MAT_cnta=#2relax
MAT_cntb=xint_c_ % 0
xintloop
advanceMAT_cntbxint_c_i
ifnumMAT_cntb<MAT_cnta
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
repeat
}%
% Usage:
% MATpluqA (A previously defined by MATset)
% Effect: sets P, L, U, Q, to matrices in the sense of MATset,
% so that "A=PLUQ" and it writes all matrices out
% to some file. See initial answer about row reduction for typesetting
% in document.
% The code is a simple adaptation of this initial answer. Now I use MATpluq
% prefix.
defMATpluq #1{%
% begingroup
MATlet@U#1%
edefMATpluq@rows{@U[I]}% nb of rows
edefMATpluq@cols{@U[J]}% nb of columns.
MATid@PMATpluq@rows
MATid@LMATpluq@rows
MATid@QMATpluq@cols
defMATpluq@pivrow {0}%
defMATpluq@pivcol {0}%
%edefMATpluq@name {string#1}%
letMATpluq@ifcontinueiftrue
% Starting the reduction.
MATtypeout{^^JA}#1%
[A = MATdisplay@U]
xintloop
% Nota Bene: in the PLUQ reduction, the pivots are anyhow organized
% along the main diagonal so pivrow and pivcol will be kept in sync over
% the execution of the algorithm but we use two variables nevertheless.
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
edefMATpluq@pivcol{thenumexprMATpluq@pivcol+xint_c_i}%
MATpluq@dopiv
MATpluq@ifcontinue
repeat
% Done. The rank of the matrix is thenumexprMATpluq@pivrow-xint_c_i.par
% endgroup
MATtypeout{P}@P
MATtypeout{L}@L
MATtypeout{U}@U
MATtypeout{Q}@Q
[ P = MATdisplay@P]
[ L = MATdisplay@Lqquad U = MATdisplay@U]
[ Q = MATdisplay@Q]
}
defMATpluq@done {letMATpluq@ifcontinueiffalse}
% Remark on algorithm: I hesitated about doing column permutations first,
% rather than row permutations with the idea to recognize faster an entirely
% vanishing row, so that we can put it at the end and ignore it entirely, in
% effect reducing the number of rows by one, and possibly making algorithm
% faster. But for simplicity I just keep algorithm close to the one as in my
% initial answer. We only have to keep track in P, L, Q of the needed
% operations.
defMATpluq@dopiv{%
letMATpluq@rowMATpluq@pivrow
letMATpluq@colMATpluq@pivcol
ifnumMATpluq@row>MATpluq@rowsrelax
MATpluq@done
else
ifnumMATpluq@col>MATpluq@colsrelax
MATpluq@done
else
expandafterexpandafterexpandafterMATpluq@dopiv@i
fi
fi
}
defMATpluq@dopiv@i{%
edefMATpluq@piv@value{@U[MATpluq@row,MATpluq@col]}%
xintifZero{MATpluq@piv@value}
MATpluq@dopiv@steprow
MATpluq@dopiv@ii
}
defMATpluq@dopiv@steprow{%
ifnumMATpluq@row=MATpluq@rowsrelax
par No pivot found in column MATpluq@col.par
letMATpluq@rowMATpluq@pivrow
expandafterMATpluq@dopiv@stepcol
else
edefMATpluq@row{thenumexprMATpluq@row+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
defMATpluq@dopiv@stepcol{%
ifnumMATpluq@col=MATpluq@colsrelax
MATpluq@done
else
edefMATpluq@col{thenumexprMATpluq@col+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
% found a pivot
defMATpluq@dopiv@ii{%
Pivot MATpluqprintonevalue{MATpluq@piv@value} at MATpluq@row, MATpluq@col.par
ifnumMATpluq@col>MATpluq@pivcolrelax
Exchange of columns MATpluq@pivcolspace and MATpluq@col.par
MATexchangerow{MATpluq@col}{MATpluq@pivcol}@Q
MATexchangecol{MATpluq@col}{MATpluq@pivcol}@U
[U = MATdisplay@Uqquad Q = MATdisplay@Q]
fi
ifnumMATpluq@pivrow=MATpluq@rowsrelax
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
MATpluq@done
else
expandafterMATpluq@dopiv@iii
fi
}
defMATpluq@dopiv@iii{%
ifnumMATpluq@row>MATpluq@pivrowrelax
Exchange of rows MATpluq@pivrowspace and MATpluq@row.par
MATexchangecol{MATpluq@row}{MATpluq@pivrow}@P
MATexchangerow{MATpluq@row}{MATpluq@pivrow}@U
MATexchangerowspecial{MATpluq@row}{MATpluq@pivrow}@L
[L = MATdisplay@Lqquad U = MATdisplay@U]
[P = MATdisplay@P]
fi
MAT_cntcMATpluq@pivrowrelax% we are guaranteed < nb of rows
xintloop
advanceMAT_cntcxint_c_i
edefMATpluq@entry{@U[MAT_cntc,MATpluq@pivcol]}%
xintifZeroMATpluq@entry
{% nothing to do, the L coeff is already set to zero
}%
{edefMATpluq@ratio
{xintIrr{xintDiv{MATpluq@entry}{MATpluq@piv@value}}[0]}%
expandafterlet
csname MAT@string@L{theMAT_cntc}{MATpluq@pivcol}endcsname
MATpluq@ratio
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATpluqprintonevalue{MATpluq@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATpluq@pivcol}}{0[0]}%
MAT_cntdMATpluq@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATpluq@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATpluq@ratio}{@U[MATpluq@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATpluq@rows=MAT_cntc
repeat
[L = MATdisplay@Lqquad U = MATdisplay@U]
}
defMATpluqprintonevalue{xintPRaw}
%defMATpluqdisplay#1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}pagestyle{empty}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 1/11 ; }
MATpluqMatrixA
See pluqout file.clearpage
MATsetA {
3, -7, 5, 0, 1, 0, 1;
-9, -8, -2, 9, -1, 9, -4;
4, 6, 0, -1, -2, -1, -3;
-5, 2, -6, 7, 8, 7, 8;
-1, -2, -1, -3, 4, 6, 0;
7, 8, 7, 8, -5, 2, -6;
}
MATpluqA
See pluqout file.clearpage
MATsetA {
2, 0, 3, 0;
1, 0, 0, 0;
0, 0, 4, 0;
0, 2, 0, 1;
}
MATpluqA
See pluqout file.clearpage
MATsetMatrixB {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
4, -3, 6, 0, -1, 5, -4, -3, 4;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
3, 6, -2, -1, 8, -2, -6, 7, -7;
4, 6, 3, -9, 1, -5, 0, 5, -3;
}
MATpluqMatrixB
See pluqout file.clearpage
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, 22, -14, 9, 2, 7, -6, -8;
-6, -3, 15, -9, 9, 11, -2, 1, -13;
-5, 8, 2, -18, 7, -1, 8, -7, 0;
4, 6, -14, 2, 1, -5, 6, 5, -3;
-11, 5, 17, -27, 16, 10, 6, -6, -13;
}
MATpluqMatrixC
See pluqout file for the matrices in Maple format.clearpage
immediatecloseoutMATout
end{document}
ROW REDUCTION (INITIAL) ANSWER
I have improved a bit some internal aspects of the code in an edit.
documentclass{article}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
catcode`_ 11
makeatletter
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
%
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
defMATrowreduce #1{%
begingroup
edefMATrr@rows{#1[I]}%
edefMATrr@cols{#1[J]}%
defMATrr@pivrow {0}%
defMATrr@pivcol {0}%
MATlet@U #1%
letMATrr@ifcontinueiftrue
Starting the reduction.
MATrrdisplaymatrix@U
xintloop
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}%
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
MATrr@dopiv
MATrr@ifcontinue
repeat
Done. The rank of the matrix is thenumexprMATrr@pivrow-xint_c_i.par
endgroup
}
defMATrr@done {letMATrr@ifcontinueiffalse}
defMATrr@dopiv{%
letMATrr@rowMATrr@pivrow
letMATrr@colMATrr@pivcol
ifnumMATrr@row>MATrr@rowsrelax
MATrr@done
else
ifnumMATrr@col>MATrr@colsrelax
MATrr@done
else
expandafterexpandafterexpandafterMATrr@dopiv@i
fi
fi
}
defMATrr@dopiv@i{%
edefMATrr@piv@value{@U[MATrr@row,MATrr@pivcol]}%
xintifZero{MATrr@piv@value}
MATrr@dopiv@steprow
MATrr@dopiv@ii
}
defMATrr@dopiv@steprow{%
ifnumMATrr@row=MATrr@rowsrelax
letMATrr@rowMATrr@pivrow
par No pivot found in column MATrr@pivcol.par
expandafterMATrr@dopiv@stepcol
else
edefMATrr@row{thenumexprMATrr@row+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@stepcol{%
ifnumMATrr@pivcol=MATrr@colsrelax
MATrr@done
else
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@ii{%
ifnumMATrr@pivrow=MATrr@rowsrelax
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}MATrr@done
else
expandafterMATrr@dopiv@iii
fi
}
defMATrr@dopiv@iii{%
Now using the pivot with value MATrrprintonevalue{MATrr@piv@value}
at row MATrr@rowspace and column MATrr@pivcol.par
ifnumMATrr@row>MATrr@pivrowrelax
Exchange of row MATrr@rowspace with row MATrr@pivrow.par
MAT_cntb=MATrr@pivcolrelax
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string@U{MATrr@row}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@row}{theMAT_cntb}expandafterendcsname
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
MAT@tmp
ifnumMATrr@cols>MAT_cntb
advanceMAT_cntbxint_c_i
repeat
MATrrdisplaymatrix@Upar
fi
MAT_cntcMATrr@pivrow
xintloop
advanceMAT_cntcxint_c_i
edefMATrr@entry{@U[MAT_cntc,MATrr@pivcol]}%
xintifZeroMATrr@entry
{}%
{edefMATrr@ratio{xintIrr{xintDiv{MATrr@entry}{MATrr@piv@value}}[0]}%
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATrrprintonevalue{MATrr@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATrr@pivcol}}{0[0]}%
MAT_cntdMATrr@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATrr@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATrr@ratio}{@U[MATrr@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATrr@rows=MAT_cntc
repeat
MATrrdisplaymatrix@U
}
defMATrrprintonevalue{xintPRaw}
defMATrrdisplaymatrix #1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
-6, -3, -15, 3, 9, 11, -1, 1, -13;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
4, 6, 3, -9, 1, -5, 0, 5, -3;
-11, 5, -13, -3, 16, 10, 0, -6, -13;
}
MATrowreduceMatrixC
end{document}
Entries may be decimal numbers like 37.156
.
defMATrrprintonevalue{xintRound{2}}
defMATrrdisplaymatrix #1{[MATdisplaywith{xintRound{2}}#1]}%
Example (as I add dots, I use truncating rather than rounding):
defMATrrprintonevalue#1{xintTrunc{3}{#1}dots (=xintPRaw{#1})}
defMATrrdisplaymatrix #1{[MATdisplay#1=MATdisplaywith{TruncWithDots{3}}#1]}%
defTruncWithDots #1#2{xintTrunc{#1}{#2}...}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 0.09 ; }
MATrowreduceMatrixA
It is important to stress relative toPLUQ
(and even to Row Reduction) that I am doing exact computations, thus there is no need for the algorithm to look for the largest possible pivot, with the attendant fact that entries ofL
would be smaller than one in absolute value. Thus I do not follow here the algorithm fashionable in numerical analysis (by the way, it may in exceptional cases have stability problems, theU
matrix containing exponentially big entries respective to the size of the matrices). I just pick up the first available pivot irrespective of its size.
– jfbu
Mar 25 '17 at 14:49
don't you want Bruhat decomposition of invertible matrices ? that will be a challenge to numerical analysis, but no problem for exact computations.
– jfbu
Mar 25 '17 at 14:57
by the way Maple'sLUDecomposition
operates like this: For Matrices with... at least one floating-point entry, pivots ... according to absolute magnitude. For Matrices with ... rational... entries, pivots ... the first nonzero element in the current column.. and indeed this is what I did here. One can check that withp, l, u := LUDecomposition(A);
one does getp=P
,l = L
, andu = U.Q
, whereP, L, U, Q
are computed as above. (Maple16)
– jfbu
Mar 25 '17 at 18:01
At first glance, this looks great. I'll need to give it a more thorough inspection before I reward you with the 500 reputation and risqué pics though. ;)
– Quinn Culver
Mar 25 '17 at 22:30
add a comment |
up vote
19
down vote
up vote
19
down vote
This answer has some macros picked up from https://tex.stackexchange.com/a/143035/4686. I am not too happy with some internal data structure, but I decided to live it standing.
The https://tex.stackexchange.com/a/143035/4686 computes determinants, inverses, etc..., either exactly or with float operations.
Here I focus on exact computations. The matrix entries may be integers, fractions, decimal numbers, or in scientific notation, but they are handled exactly. Hence, there is no question of numerical instability here. Regarding input, the format is with semi-colon separated rows of comma separated coefficients.
The last edit improves some internal aspects, has a better example for A=PLUQ
, and redoes the initial example of Row Reduction to use for display truncated, not rounded, decimal expansions as they are followed with dots.
PLUQ ANSWER
The code typesets with TeX and also outputs to a file in Maple matrix notation
the final result, for example.
A:=Matrix([[3, 1, -7, 5, 0, 9, -9, 7, -5], [-9, -4, 22, -14, 9, 2, 7, -6, -8], [-6, -3, 15, -9, 9, 11, -2, 1, -13], [-5, 8, 2, -18, 7, -1, 8, -7, 0], [4, 6, -14, 2, 1, -5, 6, 5, -3], [-11, 5, 17, -27, 16, 10, 6, -6, -13]]);
P:=Matrix([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1]]);
L:=Matrix([[1, 0, 0, 0, 0, 0], [-3, 1, 0, 0, 0, 0], [-5/3, -29/3, 1, 0, 0, 0], [4/3, -14/3, 43/94, 1, 0, 0], [-2, 1, 0, 0, 1, 0], [-11/3, -26/3, 1, 0, 0, 1]]);
U:=Matrix([[3, 1, 0, 9, -7, 5, -9, 7, -5], [0, -1, 9, 29, 1, 1, -20, 15, -23], [0, 0, 94, 883/3, 0, 0, -601/3, 449/3, -692/3], [0, 0, 0, -1533/94, 0, 0, 1533/94, -263/94, 87/47], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]);
Q:=Matrix([[1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1]]);
Now we can copy paste into Maple and check that indeed A=PLUQ
:
> with(LinearAlgebra):
> MatrixAdd(A,-P.L.U.Q);
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
Notice that in a PLUQ decomposition, a P and a Q will appear with my code only if necessary.
documentclass[a4paper]{article}
usepackage[hscale=0.85, vscale=0.85]{geometry}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
% usepackage {siunitx}
% usepackage {numprint}
catcode`_ 11
makeatletter
newwriteMATout
immediateopenoutMATout=jobname.pluqoutrelax
% (the typeout format is for input in Maple for example)
defMATtypeout {MATtypeoutwith {MATtypeoutone}}%
defMATtypeoutone #1{xintPRaw{xintRawWithZeros{#1}}}% (lacking an xintPRawWithZeros)
defMATtypeoutwith #1#2#3{%
edefI{xintSeq {1}{#3[I]}}% indices for rows
edefJ{xintSeq {1}{#3[J]}}% indices for columns
immediatewriteMATout{#2:=Matrix([[%
xintListWithSep {], [}{xintApply { MAT_typeout_row {#1}#3}{I}}%
]]);}%
}%
defMAT_typeout_row #1#2#3{%
xintListWithSep {, }{xintApply { MAT_typeout_one {#1}#2{#3}}{J}}%
}%
defMAT_typeout_one #1#2#3#4{#1{#2[#3,#4]}}%
% we don't need all of them
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
% The final semi-colon is optional.
% We indeed focus here on manipulating matrices with rational entries, the
% code at https://tex.stackexchange.com/a/143035/4686 has the set-up for
% floating point numbers too (in an arbitrary, user decided precision).
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% a bit convoluted, no comments.
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
% removed toks2 et toks4 usage from https://tex.stackexchange.com/a/143035/4686
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% We need identity matrices.
% again copied as is from https://tex.stackexchange.com/a/143035/4686
% IDENTITY MATRIX
% usage MATidfoo{37} defines a 37 times 37 identity matrix.
defMATid {defMAT_tmpf{/1}MAT_id }%
%defMATfloatid {defMAT_tmpf{}MAT_id }%
% This identity matrix insists on coefficients written internally
% 0[0] or 1[0], this is a remnant of
% https://tex.stackexchange.com/a/143035/4686 whose aim is is minuscule
% optimization when these numbers are involved in computations done by
% the xintfrac macros.
defMAT_id #1#2{%
MAT_cntc #2relax
MAT_cnta xint_c_i % 1
xintloop
{expandafterdefexpandafterMAT_tmpa expandafter{theMAT_cnta}%
MAT_cntb xint_c_i % 1
xintloop
expandafteredef
csname MAT@string#1{MAT_tmpa}{theMAT_cntb}endcsname
{ifnumMAT_cntb=MAT_cnta 1else 0fi MAT_tmpf[0]}%
ifnumMAT_cntb<MAT_cntc
advanceMAT_cntb xint_c_i
repeat
ifnumMAT_cnta<MAT_cntc
advanceMAT_cnta xint_c_i
}repeat
expandafterdefcsname MAT@string#1{I}expandafterendcsname
expandafter {theMAT_cntc}%
expandafterdefcsname MAT@string#1{J}expandafterendcsname
expandafter {theMAT_cntc}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% EXCHANGING ROWS OR COLUMNS OF A GIVEN MATRIX
defMATexchangecol #1#2#3{%
MAT_cnta=#3[I]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{theMAT_cntb}{#1}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#1}expandafterendcsname
csname MAT@string#3{theMAT_cntb}{#2}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#2}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
% perhaps only columns "to the right" actually need exchange in usage of this
defMATexchangerow #1#2#3{%
MAT_cnta=#3[J]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
defMATexchangerowspecial #1#2#3{%#1>#2, only columns <#2 need update
MAT_cnta=#2relax
MAT_cntb=xint_c_ % 0
xintloop
advanceMAT_cntbxint_c_i
ifnumMAT_cntb<MAT_cnta
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
repeat
}%
% Usage:
% MATpluqA (A previously defined by MATset)
% Effect: sets P, L, U, Q, to matrices in the sense of MATset,
% so that "A=PLUQ" and it writes all matrices out
% to some file. See initial answer about row reduction for typesetting
% in document.
% The code is a simple adaptation of this initial answer. Now I use MATpluq
% prefix.
defMATpluq #1{%
% begingroup
MATlet@U#1%
edefMATpluq@rows{@U[I]}% nb of rows
edefMATpluq@cols{@U[J]}% nb of columns.
MATid@PMATpluq@rows
MATid@LMATpluq@rows
MATid@QMATpluq@cols
defMATpluq@pivrow {0}%
defMATpluq@pivcol {0}%
%edefMATpluq@name {string#1}%
letMATpluq@ifcontinueiftrue
% Starting the reduction.
MATtypeout{^^JA}#1%
[A = MATdisplay@U]
xintloop
% Nota Bene: in the PLUQ reduction, the pivots are anyhow organized
% along the main diagonal so pivrow and pivcol will be kept in sync over
% the execution of the algorithm but we use two variables nevertheless.
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
edefMATpluq@pivcol{thenumexprMATpluq@pivcol+xint_c_i}%
MATpluq@dopiv
MATpluq@ifcontinue
repeat
% Done. The rank of the matrix is thenumexprMATpluq@pivrow-xint_c_i.par
% endgroup
MATtypeout{P}@P
MATtypeout{L}@L
MATtypeout{U}@U
MATtypeout{Q}@Q
[ P = MATdisplay@P]
[ L = MATdisplay@Lqquad U = MATdisplay@U]
[ Q = MATdisplay@Q]
}
defMATpluq@done {letMATpluq@ifcontinueiffalse}
% Remark on algorithm: I hesitated about doing column permutations first,
% rather than row permutations with the idea to recognize faster an entirely
% vanishing row, so that we can put it at the end and ignore it entirely, in
% effect reducing the number of rows by one, and possibly making algorithm
% faster. But for simplicity I just keep algorithm close to the one as in my
% initial answer. We only have to keep track in P, L, Q of the needed
% operations.
defMATpluq@dopiv{%
letMATpluq@rowMATpluq@pivrow
letMATpluq@colMATpluq@pivcol
ifnumMATpluq@row>MATpluq@rowsrelax
MATpluq@done
else
ifnumMATpluq@col>MATpluq@colsrelax
MATpluq@done
else
expandafterexpandafterexpandafterMATpluq@dopiv@i
fi
fi
}
defMATpluq@dopiv@i{%
edefMATpluq@piv@value{@U[MATpluq@row,MATpluq@col]}%
xintifZero{MATpluq@piv@value}
MATpluq@dopiv@steprow
MATpluq@dopiv@ii
}
defMATpluq@dopiv@steprow{%
ifnumMATpluq@row=MATpluq@rowsrelax
par No pivot found in column MATpluq@col.par
letMATpluq@rowMATpluq@pivrow
expandafterMATpluq@dopiv@stepcol
else
edefMATpluq@row{thenumexprMATpluq@row+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
defMATpluq@dopiv@stepcol{%
ifnumMATpluq@col=MATpluq@colsrelax
MATpluq@done
else
edefMATpluq@col{thenumexprMATpluq@col+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
% found a pivot
defMATpluq@dopiv@ii{%
Pivot MATpluqprintonevalue{MATpluq@piv@value} at MATpluq@row, MATpluq@col.par
ifnumMATpluq@col>MATpluq@pivcolrelax
Exchange of columns MATpluq@pivcolspace and MATpluq@col.par
MATexchangerow{MATpluq@col}{MATpluq@pivcol}@Q
MATexchangecol{MATpluq@col}{MATpluq@pivcol}@U
[U = MATdisplay@Uqquad Q = MATdisplay@Q]
fi
ifnumMATpluq@pivrow=MATpluq@rowsrelax
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
MATpluq@done
else
expandafterMATpluq@dopiv@iii
fi
}
defMATpluq@dopiv@iii{%
ifnumMATpluq@row>MATpluq@pivrowrelax
Exchange of rows MATpluq@pivrowspace and MATpluq@row.par
MATexchangecol{MATpluq@row}{MATpluq@pivrow}@P
MATexchangerow{MATpluq@row}{MATpluq@pivrow}@U
MATexchangerowspecial{MATpluq@row}{MATpluq@pivrow}@L
[L = MATdisplay@Lqquad U = MATdisplay@U]
[P = MATdisplay@P]
fi
MAT_cntcMATpluq@pivrowrelax% we are guaranteed < nb of rows
xintloop
advanceMAT_cntcxint_c_i
edefMATpluq@entry{@U[MAT_cntc,MATpluq@pivcol]}%
xintifZeroMATpluq@entry
{% nothing to do, the L coeff is already set to zero
}%
{edefMATpluq@ratio
{xintIrr{xintDiv{MATpluq@entry}{MATpluq@piv@value}}[0]}%
expandafterlet
csname MAT@string@L{theMAT_cntc}{MATpluq@pivcol}endcsname
MATpluq@ratio
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATpluqprintonevalue{MATpluq@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATpluq@pivcol}}{0[0]}%
MAT_cntdMATpluq@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATpluq@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATpluq@ratio}{@U[MATpluq@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATpluq@rows=MAT_cntc
repeat
[L = MATdisplay@Lqquad U = MATdisplay@U]
}
defMATpluqprintonevalue{xintPRaw}
%defMATpluqdisplay#1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}pagestyle{empty}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 1/11 ; }
MATpluqMatrixA
See pluqout file.clearpage
MATsetA {
3, -7, 5, 0, 1, 0, 1;
-9, -8, -2, 9, -1, 9, -4;
4, 6, 0, -1, -2, -1, -3;
-5, 2, -6, 7, 8, 7, 8;
-1, -2, -1, -3, 4, 6, 0;
7, 8, 7, 8, -5, 2, -6;
}
MATpluqA
See pluqout file.clearpage
MATsetA {
2, 0, 3, 0;
1, 0, 0, 0;
0, 0, 4, 0;
0, 2, 0, 1;
}
MATpluqA
See pluqout file.clearpage
MATsetMatrixB {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
4, -3, 6, 0, -1, 5, -4, -3, 4;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
3, 6, -2, -1, 8, -2, -6, 7, -7;
4, 6, 3, -9, 1, -5, 0, 5, -3;
}
MATpluqMatrixB
See pluqout file.clearpage
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, 22, -14, 9, 2, 7, -6, -8;
-6, -3, 15, -9, 9, 11, -2, 1, -13;
-5, 8, 2, -18, 7, -1, 8, -7, 0;
4, 6, -14, 2, 1, -5, 6, 5, -3;
-11, 5, 17, -27, 16, 10, 6, -6, -13;
}
MATpluqMatrixC
See pluqout file for the matrices in Maple format.clearpage
immediatecloseoutMATout
end{document}
ROW REDUCTION (INITIAL) ANSWER
I have improved a bit some internal aspects of the code in an edit.
documentclass{article}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
catcode`_ 11
makeatletter
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
%
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
defMATrowreduce #1{%
begingroup
edefMATrr@rows{#1[I]}%
edefMATrr@cols{#1[J]}%
defMATrr@pivrow {0}%
defMATrr@pivcol {0}%
MATlet@U #1%
letMATrr@ifcontinueiftrue
Starting the reduction.
MATrrdisplaymatrix@U
xintloop
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}%
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
MATrr@dopiv
MATrr@ifcontinue
repeat
Done. The rank of the matrix is thenumexprMATrr@pivrow-xint_c_i.par
endgroup
}
defMATrr@done {letMATrr@ifcontinueiffalse}
defMATrr@dopiv{%
letMATrr@rowMATrr@pivrow
letMATrr@colMATrr@pivcol
ifnumMATrr@row>MATrr@rowsrelax
MATrr@done
else
ifnumMATrr@col>MATrr@colsrelax
MATrr@done
else
expandafterexpandafterexpandafterMATrr@dopiv@i
fi
fi
}
defMATrr@dopiv@i{%
edefMATrr@piv@value{@U[MATrr@row,MATrr@pivcol]}%
xintifZero{MATrr@piv@value}
MATrr@dopiv@steprow
MATrr@dopiv@ii
}
defMATrr@dopiv@steprow{%
ifnumMATrr@row=MATrr@rowsrelax
letMATrr@rowMATrr@pivrow
par No pivot found in column MATrr@pivcol.par
expandafterMATrr@dopiv@stepcol
else
edefMATrr@row{thenumexprMATrr@row+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@stepcol{%
ifnumMATrr@pivcol=MATrr@colsrelax
MATrr@done
else
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@ii{%
ifnumMATrr@pivrow=MATrr@rowsrelax
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}MATrr@done
else
expandafterMATrr@dopiv@iii
fi
}
defMATrr@dopiv@iii{%
Now using the pivot with value MATrrprintonevalue{MATrr@piv@value}
at row MATrr@rowspace and column MATrr@pivcol.par
ifnumMATrr@row>MATrr@pivrowrelax
Exchange of row MATrr@rowspace with row MATrr@pivrow.par
MAT_cntb=MATrr@pivcolrelax
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string@U{MATrr@row}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@row}{theMAT_cntb}expandafterendcsname
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
MAT@tmp
ifnumMATrr@cols>MAT_cntb
advanceMAT_cntbxint_c_i
repeat
MATrrdisplaymatrix@Upar
fi
MAT_cntcMATrr@pivrow
xintloop
advanceMAT_cntcxint_c_i
edefMATrr@entry{@U[MAT_cntc,MATrr@pivcol]}%
xintifZeroMATrr@entry
{}%
{edefMATrr@ratio{xintIrr{xintDiv{MATrr@entry}{MATrr@piv@value}}[0]}%
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATrrprintonevalue{MATrr@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATrr@pivcol}}{0[0]}%
MAT_cntdMATrr@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATrr@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATrr@ratio}{@U[MATrr@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATrr@rows=MAT_cntc
repeat
MATrrdisplaymatrix@U
}
defMATrrprintonevalue{xintPRaw}
defMATrrdisplaymatrix #1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
-6, -3, -15, 3, 9, 11, -1, 1, -13;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
4, 6, 3, -9, 1, -5, 0, 5, -3;
-11, 5, -13, -3, 16, 10, 0, -6, -13;
}
MATrowreduceMatrixC
end{document}
Entries may be decimal numbers like 37.156
.
defMATrrprintonevalue{xintRound{2}}
defMATrrdisplaymatrix #1{[MATdisplaywith{xintRound{2}}#1]}%
Example (as I add dots, I use truncating rather than rounding):
defMATrrprintonevalue#1{xintTrunc{3}{#1}dots (=xintPRaw{#1})}
defMATrrdisplaymatrix #1{[MATdisplay#1=MATdisplaywith{TruncWithDots{3}}#1]}%
defTruncWithDots #1#2{xintTrunc{#1}{#2}...}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 0.09 ; }
MATrowreduceMatrixA
This answer has some macros picked up from https://tex.stackexchange.com/a/143035/4686. I am not too happy with some internal data structure, but I decided to live it standing.
The https://tex.stackexchange.com/a/143035/4686 computes determinants, inverses, etc..., either exactly or with float operations.
Here I focus on exact computations. The matrix entries may be integers, fractions, decimal numbers, or in scientific notation, but they are handled exactly. Hence, there is no question of numerical instability here. Regarding input, the format is with semi-colon separated rows of comma separated coefficients.
The last edit improves some internal aspects, has a better example for A=PLUQ
, and redoes the initial example of Row Reduction to use for display truncated, not rounded, decimal expansions as they are followed with dots.
PLUQ ANSWER
The code typesets with TeX and also outputs to a file in Maple matrix notation
the final result, for example.
A:=Matrix([[3, 1, -7, 5, 0, 9, -9, 7, -5], [-9, -4, 22, -14, 9, 2, 7, -6, -8], [-6, -3, 15, -9, 9, 11, -2, 1, -13], [-5, 8, 2, -18, 7, -1, 8, -7, 0], [4, 6, -14, 2, 1, -5, 6, 5, -3], [-11, 5, 17, -27, 16, 10, 6, -6, -13]]);
P:=Matrix([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1]]);
L:=Matrix([[1, 0, 0, 0, 0, 0], [-3, 1, 0, 0, 0, 0], [-5/3, -29/3, 1, 0, 0, 0], [4/3, -14/3, 43/94, 1, 0, 0], [-2, 1, 0, 0, 1, 0], [-11/3, -26/3, 1, 0, 0, 1]]);
U:=Matrix([[3, 1, 0, 9, -7, 5, -9, 7, -5], [0, -1, 9, 29, 1, 1, -20, 15, -23], [0, 0, 94, 883/3, 0, 0, -601/3, 449/3, -692/3], [0, 0, 0, -1533/94, 0, 0, 1533/94, -263/94, 87/47], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]);
Q:=Matrix([[1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1]]);
Now we can copy paste into Maple and check that indeed A=PLUQ
:
> with(LinearAlgebra):
> MatrixAdd(A,-P.L.U.Q);
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
[ ]
[0 0 0 0 0 0 0 0 0]
Notice that in a PLUQ decomposition, a P and a Q will appear with my code only if necessary.
documentclass[a4paper]{article}
usepackage[hscale=0.85, vscale=0.85]{geometry}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
% usepackage {siunitx}
% usepackage {numprint}
catcode`_ 11
makeatletter
newwriteMATout
immediateopenoutMATout=jobname.pluqoutrelax
% (the typeout format is for input in Maple for example)
defMATtypeout {MATtypeoutwith {MATtypeoutone}}%
defMATtypeoutone #1{xintPRaw{xintRawWithZeros{#1}}}% (lacking an xintPRawWithZeros)
defMATtypeoutwith #1#2#3{%
edefI{xintSeq {1}{#3[I]}}% indices for rows
edefJ{xintSeq {1}{#3[J]}}% indices for columns
immediatewriteMATout{#2:=Matrix([[%
xintListWithSep {], [}{xintApply { MAT_typeout_row {#1}#3}{I}}%
]]);}%
}%
defMAT_typeout_row #1#2#3{%
xintListWithSep {, }{xintApply { MAT_typeout_one {#1}#2{#3}}{J}}%
}%
defMAT_typeout_one #1#2#3#4{#1{#2[#3,#4]}}%
% we don't need all of them
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
% The final semi-colon is optional.
% We indeed focus here on manipulating matrices with rational entries, the
% code at https://tex.stackexchange.com/a/143035/4686 has the set-up for
% floating point numbers too (in an arbitrary, user decided precision).
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% a bit convoluted, no comments.
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
% removed toks2 et toks4 usage from https://tex.stackexchange.com/a/143035/4686
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% We need identity matrices.
% again copied as is from https://tex.stackexchange.com/a/143035/4686
% IDENTITY MATRIX
% usage MATidfoo{37} defines a 37 times 37 identity matrix.
defMATid {defMAT_tmpf{/1}MAT_id }%
%defMATfloatid {defMAT_tmpf{}MAT_id }%
% This identity matrix insists on coefficients written internally
% 0[0] or 1[0], this is a remnant of
% https://tex.stackexchange.com/a/143035/4686 whose aim is is minuscule
% optimization when these numbers are involved in computations done by
% the xintfrac macros.
defMAT_id #1#2{%
MAT_cntc #2relax
MAT_cnta xint_c_i % 1
xintloop
{expandafterdefexpandafterMAT_tmpa expandafter{theMAT_cnta}%
MAT_cntb xint_c_i % 1
xintloop
expandafteredef
csname MAT@string#1{MAT_tmpa}{theMAT_cntb}endcsname
{ifnumMAT_cntb=MAT_cnta 1else 0fi MAT_tmpf[0]}%
ifnumMAT_cntb<MAT_cntc
advanceMAT_cntb xint_c_i
repeat
ifnumMAT_cnta<MAT_cntc
advanceMAT_cnta xint_c_i
}repeat
expandafterdefcsname MAT@string#1{I}expandafterendcsname
expandafter {theMAT_cntc}%
expandafterdefcsname MAT@string#1{J}expandafterendcsname
expandafter {theMAT_cntc}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
% EXCHANGING ROWS OR COLUMNS OF A GIVEN MATRIX
defMATexchangecol #1#2#3{%
MAT_cnta=#3[I]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{theMAT_cntb}{#1}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#1}expandafterendcsname
csname MAT@string#3{theMAT_cntb}{#2}endcsname
expandafterlet
csname MAT@string#3{theMAT_cntb}{#2}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
% perhaps only columns "to the right" actually need exchange in usage of this
defMATexchangerow #1#2#3{%
MAT_cnta=#3[J]relax
MAT_cntb=xint_c_i % 1
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
ifnumMAT_cntb<MAT_cnta
advanceMAT_cntbxint_c_i
repeat
}%
defMATexchangerowspecial #1#2#3{%#1>#2, only columns <#2 need update
MAT_cnta=#2relax
MAT_cntb=xint_c_ % 0
xintloop
advanceMAT_cntbxint_c_i
ifnumMAT_cntb<MAT_cnta
expandafterletexpandafterMAT@tmp
csname MAT@string#3{#1}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#1}{theMAT_cntb}expandafterendcsname
csname MAT@string#3{#2}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string#3{#2}{theMAT_cntb}endcsname
MAT@tmp
repeat
}%
% Usage:
% MATpluqA (A previously defined by MATset)
% Effect: sets P, L, U, Q, to matrices in the sense of MATset,
% so that "A=PLUQ" and it writes all matrices out
% to some file. See initial answer about row reduction for typesetting
% in document.
% The code is a simple adaptation of this initial answer. Now I use MATpluq
% prefix.
defMATpluq #1{%
% begingroup
MATlet@U#1%
edefMATpluq@rows{@U[I]}% nb of rows
edefMATpluq@cols{@U[J]}% nb of columns.
MATid@PMATpluq@rows
MATid@LMATpluq@rows
MATid@QMATpluq@cols
defMATpluq@pivrow {0}%
defMATpluq@pivcol {0}%
%edefMATpluq@name {string#1}%
letMATpluq@ifcontinueiftrue
% Starting the reduction.
MATtypeout{^^JA}#1%
[A = MATdisplay@U]
xintloop
% Nota Bene: in the PLUQ reduction, the pivots are anyhow organized
% along the main diagonal so pivrow and pivcol will be kept in sync over
% the execution of the algorithm but we use two variables nevertheless.
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
edefMATpluq@pivcol{thenumexprMATpluq@pivcol+xint_c_i}%
MATpluq@dopiv
MATpluq@ifcontinue
repeat
% Done. The rank of the matrix is thenumexprMATpluq@pivrow-xint_c_i.par
% endgroup
MATtypeout{P}@P
MATtypeout{L}@L
MATtypeout{U}@U
MATtypeout{Q}@Q
[ P = MATdisplay@P]
[ L = MATdisplay@Lqquad U = MATdisplay@U]
[ Q = MATdisplay@Q]
}
defMATpluq@done {letMATpluq@ifcontinueiffalse}
% Remark on algorithm: I hesitated about doing column permutations first,
% rather than row permutations with the idea to recognize faster an entirely
% vanishing row, so that we can put it at the end and ignore it entirely, in
% effect reducing the number of rows by one, and possibly making algorithm
% faster. But for simplicity I just keep algorithm close to the one as in my
% initial answer. We only have to keep track in P, L, Q of the needed
% operations.
defMATpluq@dopiv{%
letMATpluq@rowMATpluq@pivrow
letMATpluq@colMATpluq@pivcol
ifnumMATpluq@row>MATpluq@rowsrelax
MATpluq@done
else
ifnumMATpluq@col>MATpluq@colsrelax
MATpluq@done
else
expandafterexpandafterexpandafterMATpluq@dopiv@i
fi
fi
}
defMATpluq@dopiv@i{%
edefMATpluq@piv@value{@U[MATpluq@row,MATpluq@col]}%
xintifZero{MATpluq@piv@value}
MATpluq@dopiv@steprow
MATpluq@dopiv@ii
}
defMATpluq@dopiv@steprow{%
ifnumMATpluq@row=MATpluq@rowsrelax
par No pivot found in column MATpluq@col.par
letMATpluq@rowMATpluq@pivrow
expandafterMATpluq@dopiv@stepcol
else
edefMATpluq@row{thenumexprMATpluq@row+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
defMATpluq@dopiv@stepcol{%
ifnumMATpluq@col=MATpluq@colsrelax
MATpluq@done
else
edefMATpluq@col{thenumexprMATpluq@col+xint_c_i}%
expandafterMATpluq@dopiv@i
fi
}
% found a pivot
defMATpluq@dopiv@ii{%
Pivot MATpluqprintonevalue{MATpluq@piv@value} at MATpluq@row, MATpluq@col.par
ifnumMATpluq@col>MATpluq@pivcolrelax
Exchange of columns MATpluq@pivcolspace and MATpluq@col.par
MATexchangerow{MATpluq@col}{MATpluq@pivcol}@Q
MATexchangecol{MATpluq@col}{MATpluq@pivcol}@U
[U = MATdisplay@Uqquad Q = MATdisplay@Q]
fi
ifnumMATpluq@pivrow=MATpluq@rowsrelax
edefMATpluq@pivrow{thenumexprMATpluq@pivrow+xint_c_i}%
MATpluq@done
else
expandafterMATpluq@dopiv@iii
fi
}
defMATpluq@dopiv@iii{%
ifnumMATpluq@row>MATpluq@pivrowrelax
Exchange of rows MATpluq@pivrowspace and MATpluq@row.par
MATexchangecol{MATpluq@row}{MATpluq@pivrow}@P
MATexchangerow{MATpluq@row}{MATpluq@pivrow}@U
MATexchangerowspecial{MATpluq@row}{MATpluq@pivrow}@L
[L = MATdisplay@Lqquad U = MATdisplay@U]
[P = MATdisplay@P]
fi
MAT_cntcMATpluq@pivrowrelax% we are guaranteed < nb of rows
xintloop
advanceMAT_cntcxint_c_i
edefMATpluq@entry{@U[MAT_cntc,MATpluq@pivcol]}%
xintifZeroMATpluq@entry
{% nothing to do, the L coeff is already set to zero
}%
{edefMATpluq@ratio
{xintIrr{xintDiv{MATpluq@entry}{MATpluq@piv@value}}[0]}%
expandafterlet
csname MAT@string@L{theMAT_cntc}{MATpluq@pivcol}endcsname
MATpluq@ratio
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATpluqprintonevalue{MATpluq@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATpluq@pivcol}}{0[0]}%
MAT_cntdMATpluq@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATpluq@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATpluq@ratio}{@U[MATpluq@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATpluq@rows=MAT_cntc
repeat
[L = MATdisplay@Lqquad U = MATdisplay@U]
}
defMATpluqprintonevalue{xintPRaw}
%defMATpluqdisplay#1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}pagestyle{empty}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 1/11 ; }
MATpluqMatrixA
See pluqout file.clearpage
MATsetA {
3, -7, 5, 0, 1, 0, 1;
-9, -8, -2, 9, -1, 9, -4;
4, 6, 0, -1, -2, -1, -3;
-5, 2, -6, 7, 8, 7, 8;
-1, -2, -1, -3, 4, 6, 0;
7, 8, 7, 8, -5, 2, -6;
}
MATpluqA
See pluqout file.clearpage
MATsetA {
2, 0, 3, 0;
1, 0, 0, 0;
0, 0, 4, 0;
0, 2, 0, 1;
}
MATpluqA
See pluqout file.clearpage
MATsetMatrixB {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
4, -3, 6, 0, -1, 5, -4, -3, 4;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
3, 6, -2, -1, 8, -2, -6, 7, -7;
4, 6, 3, -9, 1, -5, 0, 5, -3;
}
MATpluqMatrixB
See pluqout file.clearpage
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, 22, -14, 9, 2, 7, -6, -8;
-6, -3, 15, -9, 9, 11, -2, 1, -13;
-5, 8, 2, -18, 7, -1, 8, -7, 0;
4, 6, -14, 2, 1, -5, 6, 5, -3;
-11, 5, 17, -27, 16, 10, 6, -6, -13;
}
MATpluqMatrixC
See pluqout file for the matrices in Maple format.clearpage
immediatecloseoutMATout
end{document}
ROW REDUCTION (INITIAL) ANSWER
I have improved a bit some internal aspects of the code in an edit.
documentclass{article}
usepackage{xintfrac}
usepackage{xinttools}
usepackage{array}
catcode`_ 11
makeatletter
newcountMAT_cnta
newcountMAT_cntb
newcountMAT_cntc
newcountMAT_cntd
newcountMAT_cnte
% Usage: MATsetmyMatrix{semi-colon separated rows of comma separated values}
% example.
% MATsetMatrixA { 1/3 , 1/4, 1/5 ;
% 1/6 , 1/7 , 1/8 ;
% 1/9 , 1/10 , 1/11 ; }
defMATset {defMAT_xintin {xintRaw}MATset_ }%
defMATset_ #1#2{%
defMATset_name{#1}%
edefMAT_tmpa {#2}%
MAT_cnta xint_c_ % sets MAT_cnta to zero
expandafterMATset_a
romannumeral0expandafterxintzapspacesexpandafter{MAT_tmpa};!;%
}%
defMATset_a {futureletXINT_tokenMATset_b }%
defMATset_b #1;{defMAT_tmpa{#1}%
ifxXINT_token;expandafterMATset_w
else
ifxXINT_token!%
expandafterexpandafterexpandafterMATset_x
else
expandafterexpandafterexpandafterMATset_c
fifi }%
defMATset_w !;{MATset_x }%
defMATset_x {expandafterdef
csname MAT@expandafterstringMATset_name {I}expandafterendcsname
expandafter {theMAT_cnta }%
expandafterdef
csname MAT@expandafterstringMATset_name {J}expandafterendcsname
expandafter {theMAT_cntb }%
expandafteredef MATset_name [##1]%
{noexpandcsname MAT@expandafterstringMATset_name
noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
%
defMAT_in #1,#2,{xint_bye #2xint_gobble_ivxint_bye
{thenumexpr #1}{thenumexpr #2}xint_gobble_iii
{xintZapSpaces{#1}}}%
%
defMATset_c {advanceMAT_cnta xint_c_i % row count ++
MAT_cntb xint_c_ % column count initially zero
expandafterMATset_dromannumeral0expandafter
xintzapspacesexpandafter {MAT_tmpa},!,}%
defMATset_d {futureletXINT_tokenMATset_e }%
defMATset_e #1,{ifxXINT_token!expandafterMATset_a
else
advanceMAT_cntb xint_c_i
expandafterdef
csname MAT@expandafterstringMATset_name
{theMAT_cnta}{theMAT_cntb}expandafterendcsname
expandafter{romannumeral-`0MAT_xintin{xintZapSpacesB{#1}}}%
expandafterMATset_dfi
}%
defMATlet #1#2{%
edefMAT@seqI{xintSeq {1}{#2[I]}}%
edefMAT@seqJ{xintSeq {1}{#2[J]}}%
xintFor* ##1 in {MAT@seqI}
do{xintFor* ##2 in {MAT@seqJ}
do{expandafterlet
csname MAT@string#1{##1}{##2}expandafterendcsname
csname MAT@string#2{##1}{##2}endcsname
}}%
expandafteredefcsname MAT@string#1{I}endcsname {#2[I]}%
expandafteredefcsname MAT@string#1{J}endcsname {#2[J]}%
edef #1[##1]%
{noexpandcsname
MAT@string#1noexpandMAT_in ##1,noexpandxint_bye,endcsname }%
}%
defMATrowreduce #1{%
begingroup
edefMATrr@rows{#1[I]}%
edefMATrr@cols{#1[J]}%
defMATrr@pivrow {0}%
defMATrr@pivcol {0}%
MATlet@U #1%
letMATrr@ifcontinueiftrue
Starting the reduction.
MATrrdisplaymatrix@U
xintloop
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}%
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
MATrr@dopiv
MATrr@ifcontinue
repeat
Done. The rank of the matrix is thenumexprMATrr@pivrow-xint_c_i.par
endgroup
}
defMATrr@done {letMATrr@ifcontinueiffalse}
defMATrr@dopiv{%
letMATrr@rowMATrr@pivrow
letMATrr@colMATrr@pivcol
ifnumMATrr@row>MATrr@rowsrelax
MATrr@done
else
ifnumMATrr@col>MATrr@colsrelax
MATrr@done
else
expandafterexpandafterexpandafterMATrr@dopiv@i
fi
fi
}
defMATrr@dopiv@i{%
edefMATrr@piv@value{@U[MATrr@row,MATrr@pivcol]}%
xintifZero{MATrr@piv@value}
MATrr@dopiv@steprow
MATrr@dopiv@ii
}
defMATrr@dopiv@steprow{%
ifnumMATrr@row=MATrr@rowsrelax
letMATrr@rowMATrr@pivrow
par No pivot found in column MATrr@pivcol.par
expandafterMATrr@dopiv@stepcol
else
edefMATrr@row{thenumexprMATrr@row+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@stepcol{%
ifnumMATrr@pivcol=MATrr@colsrelax
MATrr@done
else
edefMATrr@pivcol{thenumexprMATrr@pivcol+xint_c_i}%
expandafterMATrr@dopiv@i
fi
}
defMATrr@dopiv@ii{%
ifnumMATrr@pivrow=MATrr@rowsrelax
edefMATrr@pivrow{thenumexprMATrr@pivrow+xint_c_i}MATrr@done
else
expandafterMATrr@dopiv@iii
fi
}
defMATrr@dopiv@iii{%
Now using the pivot with value MATrrprintonevalue{MATrr@piv@value}
at row MATrr@rowspace and column MATrr@pivcol.par
ifnumMATrr@row>MATrr@pivrowrelax
Exchange of row MATrr@rowspace with row MATrr@pivrow.par
MAT_cntb=MATrr@pivcolrelax
xintloop
expandafterletexpandafterMAT@tmp
csname MAT@string@U{MATrr@row}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@row}{theMAT_cntb}expandafterendcsname
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
expandafterlet
csname MAT@string@U{MATrr@pivrow}{theMAT_cntb}endcsname
MAT@tmp
ifnumMATrr@cols>MAT_cntb
advanceMAT_cntbxint_c_i
repeat
MATrrdisplaymatrix@Upar
fi
MAT_cntcMATrr@pivrow
xintloop
advanceMAT_cntcxint_c_i
edefMATrr@entry{@U[MAT_cntc,MATrr@pivcol]}%
xintifZeroMATrr@entry
{}%
{edefMATrr@ratio{xintIrr{xintDiv{MATrr@entry}{MATrr@piv@value}}[0]}%
Subtract from row theMAT_cntcspace the pivot row multiplied by
MATrrprintonevalue{MATrr@ratio}.par
@namedef{MAT@string@U{theMAT_cntc}{MATrr@pivcol}}{0[0]}%
MAT_cntdMATrr@pivcolrelax
xintloop
advanceMAT_cntdxint_c_i
unlessifnumMATrr@cols<MAT_cntd
expandafteredef
csname MAT@string@U{theMAT_cntc}{theMAT_cntd}endcsname
{xintIrr{%
xintSub{@U[MAT_cntc,MAT_cntd]}
{xintMul{MATrr@ratio}{@U[MATrr@pivrow,MAT_cntd]}}%
}[0]}%
repeat
}%
unlessifnumMATrr@rows=MAT_cntc
repeat
MATrrdisplaymatrix@U
}
defMATrrprintonevalue{xintPRaw}
defMATrrdisplaymatrix #1{[MATdisplay#1]}%
%% MATH MODE MATRIX DISPLAY
makeatother
newcommandMATdisplay [1][1.25]{MATdisplaywith [#1]{MATdisplayone}}
defMATdisplayone {xintSignedFrac}
newcolumntypeMATdisplaycoltype {c}
newcolumntypeMATdisplaypreamble [1]{@{}*{#1[J]}MATdisplaycoltype@{}}
newcommandMATdisplaywith [3][1.25]
{left(defarraystretch{#1}%
begin{array}{MATdisplaypreamble {#3}}
xintListWithSep {\}
{xintApply { MAT_display_row {#2}#3}{xintSeq {1}{#3[I]}}}
end{array}right)%
}%
defMAT_display_row #1#2#3{%
xintListWithSep {&}
{xintApply{ MAT_display_one {#1}#2{#3}}{xintSeq {1}{#2[J]}}}%
}%
defMAT_display_one #1#2#3#4{#1{#2[#3,#4]}}%
catcode`_ 8
begin{document}
MATsetMatrixC {
3, 1, -7, 5, 0, 9, -9, 7, -5;
-9, -4, -8, -2, 9, 2, 8, -6, -8;
-6, -3, -15, 3, 9, 11, -1, 1, -13;
-5, 8, 2, -6, 7, -1, 1, -7, 0;
4, 6, 3, -9, 1, -5, 0, 5, -3;
-11, 5, -13, -3, 16, 10, 0, -6, -13;
}
MATrowreduceMatrixC
end{document}
Entries may be decimal numbers like 37.156
.
defMATrrprintonevalue{xintRound{2}}
defMATrrdisplaymatrix #1{[MATdisplaywith{xintRound{2}}#1]}%
Example (as I add dots, I use truncating rather than rounding):
defMATrrprintonevalue#1{xintTrunc{3}{#1}dots (=xintPRaw{#1})}
defMATrrdisplaymatrix #1{[MATdisplay#1=MATdisplaywith{TruncWithDots{3}}#1]}%
defTruncWithDots #1#2{xintTrunc{#1}{#2}...}
MATsetMatrixA { 1/3 , 1/4, 1/5 ;
1/6 , 1/7 , 1/8 ;
1/9 , 1/10 , 0.09 ; }
MATrowreduceMatrixA
edited Apr 13 '17 at 12:34
Community♦
1
1
answered Mar 23 '17 at 21:11
jfbu
44.5k65143
44.5k65143
It is important to stress relative toPLUQ
(and even to Row Reduction) that I am doing exact computations, thus there is no need for the algorithm to look for the largest possible pivot, with the attendant fact that entries ofL
would be smaller than one in absolute value. Thus I do not follow here the algorithm fashionable in numerical analysis (by the way, it may in exceptional cases have stability problems, theU
matrix containing exponentially big entries respective to the size of the matrices). I just pick up the first available pivot irrespective of its size.
– jfbu
Mar 25 '17 at 14:49
don't you want Bruhat decomposition of invertible matrices ? that will be a challenge to numerical analysis, but no problem for exact computations.
– jfbu
Mar 25 '17 at 14:57
by the way Maple'sLUDecomposition
operates like this: For Matrices with... at least one floating-point entry, pivots ... according to absolute magnitude. For Matrices with ... rational... entries, pivots ... the first nonzero element in the current column.. and indeed this is what I did here. One can check that withp, l, u := LUDecomposition(A);
one does getp=P
,l = L
, andu = U.Q
, whereP, L, U, Q
are computed as above. (Maple16)
– jfbu
Mar 25 '17 at 18:01
At first glance, this looks great. I'll need to give it a more thorough inspection before I reward you with the 500 reputation and risqué pics though. ;)
– Quinn Culver
Mar 25 '17 at 22:30
add a comment |
It is important to stress relative toPLUQ
(and even to Row Reduction) that I am doing exact computations, thus there is no need for the algorithm to look for the largest possible pivot, with the attendant fact that entries ofL
would be smaller than one in absolute value. Thus I do not follow here the algorithm fashionable in numerical analysis (by the way, it may in exceptional cases have stability problems, theU
matrix containing exponentially big entries respective to the size of the matrices). I just pick up the first available pivot irrespective of its size.
– jfbu
Mar 25 '17 at 14:49
don't you want Bruhat decomposition of invertible matrices ? that will be a challenge to numerical analysis, but no problem for exact computations.
– jfbu
Mar 25 '17 at 14:57
by the way Maple'sLUDecomposition
operates like this: For Matrices with... at least one floating-point entry, pivots ... according to absolute magnitude. For Matrices with ... rational... entries, pivots ... the first nonzero element in the current column.. and indeed this is what I did here. One can check that withp, l, u := LUDecomposition(A);
one does getp=P
,l = L
, andu = U.Q
, whereP, L, U, Q
are computed as above. (Maple16)
– jfbu
Mar 25 '17 at 18:01
At first glance, this looks great. I'll need to give it a more thorough inspection before I reward you with the 500 reputation and risqué pics though. ;)
– Quinn Culver
Mar 25 '17 at 22:30
It is important to stress relative to
PLUQ
(and even to Row Reduction) that I am doing exact computations, thus there is no need for the algorithm to look for the largest possible pivot, with the attendant fact that entries of L
would be smaller than one in absolute value. Thus I do not follow here the algorithm fashionable in numerical analysis (by the way, it may in exceptional cases have stability problems, the U
matrix containing exponentially big entries respective to the size of the matrices). I just pick up the first available pivot irrespective of its size.– jfbu
Mar 25 '17 at 14:49
It is important to stress relative to
PLUQ
(and even to Row Reduction) that I am doing exact computations, thus there is no need for the algorithm to look for the largest possible pivot, with the attendant fact that entries of L
would be smaller than one in absolute value. Thus I do not follow here the algorithm fashionable in numerical analysis (by the way, it may in exceptional cases have stability problems, the U
matrix containing exponentially big entries respective to the size of the matrices). I just pick up the first available pivot irrespective of its size.– jfbu
Mar 25 '17 at 14:49
don't you want Bruhat decomposition of invertible matrices ? that will be a challenge to numerical analysis, but no problem for exact computations.
– jfbu
Mar 25 '17 at 14:57
don't you want Bruhat decomposition of invertible matrices ? that will be a challenge to numerical analysis, but no problem for exact computations.
– jfbu
Mar 25 '17 at 14:57
by the way Maple's
LUDecomposition
operates like this: For Matrices with... at least one floating-point entry, pivots ... according to absolute magnitude. For Matrices with ... rational... entries, pivots ... the first nonzero element in the current column.. and indeed this is what I did here. One can check that with p, l, u := LUDecomposition(A);
one does get p=P
, l = L
, and u = U.Q
, where P, L, U, Q
are computed as above. (Maple16)– jfbu
Mar 25 '17 at 18:01
by the way Maple's
LUDecomposition
operates like this: For Matrices with... at least one floating-point entry, pivots ... according to absolute magnitude. For Matrices with ... rational... entries, pivots ... the first nonzero element in the current column.. and indeed this is what I did here. One can check that with p, l, u := LUDecomposition(A);
one does get p=P
, l = L
, and u = U.Q
, where P, L, U, Q
are computed as above. (Maple16)– jfbu
Mar 25 '17 at 18:01
At first glance, this looks great. I'll need to give it a more thorough inspection before I reward you with the 500 reputation and risqué pics though. ;)
– Quinn Culver
Mar 25 '17 at 22:30
At first glance, this looks great. I'll need to give it a more thorough inspection before I reward you with the 500 reputation and risqué pics though. ;)
– Quinn Culver
Mar 25 '17 at 22:30
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f357978%2frow-reduction-macro%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
rref is numerically an ill conditioned algorithm. It becomes even worse with TeX precision. The industry standard is LU factorization.
– percusse
Mar 11 '17 at 16:12
@percusse Good point. But I just want something that can handle the simple matrices the students are required to row reduce in the class I teach. Know what I mean?
– Quinn Culver
Mar 11 '17 at 16:18
1
Doesn't matter, you'll get wrong results in even 3x3 matrices. multiply first row with 2/3 and add to the second row even that would screw up all integer valued entries.
– percusse
Mar 11 '17 at 16:29
1
The
sagetex
package relies on the computer algebra system SAGE to calculate the row echelon form of a matrix, even if it has variables and gives you access to a lot of other commands on matrices. However, it doesn't give the steps to get the answer.– DJP
Mar 11 '17 at 21:44
1
Are you sure you need everything to be done inside TeX itself? Why not rely on an external program for doing all the computation (and communicate with the program from TeX maybe)?
– ShreevatsaR
Mar 22 '17 at 17:07