Download or view Matrix.frink in plain text format
/** This is an incomplete class that implements matrix operations. Please
feel free to implement missing functions! */
class Matrix
{
/** The array that will actually store the matrix data. Note that this
is a raw 2-dimensional Frink array, and the indices are zero-based in
contrast with most matrix notation which is one-based. The class should
try to enforce that this is a rectangular 2-dimensional array, but the
code doesn't currently enforce that with some constructors. */
var array
/** The number of rows in the matrix. */
var rows
/** The number of columns in the matrix. */
var cols
/** Construct a new Matrix with the specified number of rows and columns
and the specified default value for each element. */
new[rowCount, colCount, default=0] :=
{
rows = rowCount
cols = colCount
array = makeArray[[rows, cols], default]
}
/** Construct a new Matrix from a rectangular 2-dimensional array. This
currently does not verify that the array is rectangular. */
new[a is array] :=
{
rows = length[a]
cols = length[a@0]
array = a
}
/** Sets the value of the specified element, using 1-based coordinates, as
is common in matrix notation. Stupid mathematicians. */
set[row, col, val] := array@(row-1)@(col-1) = val
/** Gets the value of the specified element, using 1-based coordinates, as
is common in matrix notation. */
get[row, col] := array@(row-1)@(col-1)
/** Multiply two matrices. The number of columns in column a should equal
the number of rows in column b. The resulting matrix will have the
same number of rows as matrix a and the same number of columns as
matrix b.
TODO: Use a faster algorithm for large arrays? This is O(n^3).
TODO: Optimize this for small arrays by precalculating.
*/
class multiply[a, b] :=
{
if a.cols != b.rows
return undef
else
items = a.cols
resRows = a.rows
resCols = b.cols
resultArray = makeArray[[resRows, resCols], 0]
aa = a.array
bb = b.array
for row = 0 to resRows-1
for col = 0 to resCols-1
{
sum = 0
for k = 0 to items-1
sum = sum + aa@row@k * bb@k@col
resultArray@row@col = sum
}
return new Matrix[resultArray]
}
/** Multiply this matrix by the specified matrix and return the result. */
multiply[b] := multiply[this, b]
/** Multiplies all elements of a matrix by a scalar. */
multiplyByScalar[s] :=
{
a = makeArray[[rows, cols]]
for rowNum = 0 to rows-1
{
row = array@rowNum
for colNum = 0 to cols-1
a@rowNum@colNum = row@colNum * s
}
return new Matrix[a]
}
/** Transposes the Matrix and returns the new transposed Matrix. This
uses the built-in array.transpose[] method.
*/
transpose[] := new Matrix[array.transpose[]]
/** Returns true if this Matrix is square, false otherwise. */
isSquare[] := rows == cols
/** Exchange the specified-numbered rows in the matrix. The rows are
1-indexed to match standard matrix notation. */
exchangeRows[r1, r2] :=
{
temp = array@(r2-1)
array@(r2-1) = array@(r1-1)
array@(r1-1) = temp
}
/** Exchange the specified-numbered columns in the matrix. The columns are
1-indexed to match standard matrix notation. */
exchangeCols[c1, c2] :=
{
for r = 0 to rows-1
{
temp = array@r@(c2-1)
array@r@(c2-1) = array@r@(c1-1)
array@r@(c1-1) = temp
}
}
/** Returns a matrix as a string with rows separated with newlines. */
toString[] :=
{
result = ""
for r = 0 to rows-1
result = result + " " + array@r + "\n"
return result
}
/** Returns the matrix formatted by formatTable with rows separated with
newlines. */
toTable[] :=
{
return formatTable[array]
}
/** Formats a matrix with Unicode box drawing characters. The result is a
single string. */
formatMatrix[] :=
{
return joinln[formatMatrixLines[]]
}
/** Returns the matrix formatted as a matrix with Unicode box drawing
characters. The result is an array of lines that can be further
formatted.
*/
formatMatrixLines[] :=
{
m = formatTableLines[array]
rows = length[m]
width = length[m@0]
result = new array
result.push["\u250c" + repeat[" ", width] + "\u2510"]
for n=0 to rows-1
result.push["\u2502" + m@n + "\u2502"]
result.push["\u2514" + repeat[" ", width] + "\u2518"]
return result
}
/** Gets the specified (1-based) column as a row array. */
getColumnAsArray[col] :=
{
return deepCopy[array.getColumn[col-1]]
}
/** Gets the specified (1-based) row as a row array. */
getRowAsArray[row] :=
{
return deepCopy[array@(row-1)]
}
/** Creates the LU (lower-upper) decomposition of the matrix.
This creates two matrices, L and U, where L has the value 1 on the
diagonal from top left to bottom right, like:
1 0 0
L = L21 1 0
L31 L32 1
U11 U12 U13
U = 0 U22 U23
0 0 U33
This is basically like solving equations by Gaussian elimination. */
LUDecompose[] :=
{
return LUDecomposeCrout[]
}
/** This uses Crout's method to decompose a square matrix into an lower and
upper triangular matrix.
See:
https://en.wikipedia.org/wiki/Crout_matrix_decomposition
This creates two matrices, L and U, where U has the value 1 on the
diagonal from top left to bottom right, like:
L11 0 0
L = L21 L22 0
L31 L32 L33
1 U12 U13
U = 0 1 U23
0 0 1
returns [L, U]
The original matrix can be obtained as L.multiply[U]
Note that the Crout algorithm fails on certain matrices, for example
m = new Matrix[[[1,1,1,-1],[1,1,-1,1],[1,-1,1,1],[-1,1,1,1]]]
See:
https://semath.info/src/inverse-cofactor-ex4.html
for more on that matrix.
https://en.wikipedia.org/wiki/Crout_matrix_decomposition
*/
LUDecomposeCrout[] :=
{
n = rows
L = new array[[rows, cols], 0]
U = new array[[rows, cols], 0]
sum = 0
for i=0 to n-1
U@i@i = 1
for j=0 to n-1
{
for i=j to n-1
{
sum = 0
for k=0 to j-1
sum = sum + L@i@k * U@k@j
L@i@j = array@i@j - sum
}
for i=j to n-1
{
sum = 0
for k=0 to j-1
sum = sum + L@j@k * U@k@i
if L@j@j == 0
{
println["Matrix.LUDecomposeCrout: det(L) is zero. Can't divide by zero."]
return undef
}
U@j@i = (array@j@i - sum) / L@j@j
}
}
return [new Matrix[L], new Matrix[U]]
}
/** Another algorithm for Crout LU decomposition. See:
http://www.mosismath.com/Matrix_LU/Martix_LU.html
This returns [L, U] as matrices which may be transposes of the matrices
returned by LUDecomposeCrout[] . See its notes for more about this
decomposition and its properties.
*/
LUDecomposeCrout2[] :=
{
if ! isSquare[]
{
println["Matrix.LUDecomposeCrout2 only works on square arrays."]
return undef
}
n = rows
L = new array[[n,n], 0]
U = new array[[n,n], 0]
a = 0
for i = 0 to n-1
L@i@i = 1
for j = 0 to n-1
{
for i = 0 to n-1
{
if i >= j
{
U@j@i = array@j@i
for k = 0 to j-1
{
U@j@i = U@j@i - U@k@i * L@j@k
}
}
if i > j
{
L@i@j = array@i@j
for k = 0 to j-1
L@i@j = L@i@j - U@k@j * L@i@k
diag = U@j@j
if diag == 0
{
println["Matrix.LUDecomposeCrout2: det(L) is zero. Can't divide by zero."]
println[formatMatrix[L]]
println[formatMatrix[U]]
return undef
}
L@i@j = L@i@j / diag
}
}
}
return [new Matrix[L], new Matrix[U]]
}
/** This uses the Cholesky-Banachiewicz algorithm to decompose a square
Hermitian matrix into a lower triangular matrix. If you transpose the
lower triangular matrix L by L.transpose[], you get an upper triangular
matrix which is symmetrical to the lower triangular matrix.
Multiplying the lower triangular matrix by the upper triangular matrix,
that is,
L.multiply[L.transpose[]]
gives you back the original matrix!
See: https://en.m.wikipedia.org/wiki/Cholesky_decomposition
*/
CholeskyB[] :=
{
if ! isHermitian[]
{
println["Matrix.CholeskyB only works on square Hermitian matrices."]
return undef
}
n = rows
L = new array[[n,n], 0]
for i = 0 to n-1
for j = 0 to i
{
sum = 0
for k = 0 to j-1
sum = sum + L@i@k * L@j@k
if i == j
L@i@j = sqrt[array@i@i - sum]
else
L@i@j = (1 / L@j@j * (array@i@j -sum))
}
return new Matrix[L]
}
/** This uses the Cholesky-Crout algorithm to decompose a square Hermitian
matrix into a lower triangular matrix. If you transpose the lower
triangular matrix L by L.transpose[], you get an upper triangular
matrix which is symmetrical to the lower triangular matrix.
Multiplying the lower triangular matrix by the upper triangular matrix,
that is,
L.multiply[L.transpose[]]
gives you back the original matrix!
See: https://en.m.wikipedia.org/wiki/Cholesky_decomposition
*/
CholeskyCrout[] :=
{
if ! isHermitian[]
{
println["Matrix.CholeskyCrout only works on square Hermitian matrices."]
return undef
}
n = rows
L = new array[[n,n], 0]
for j = 0 to n-1
{
sum = 0
for k = 0 to j-1
sum = sum + (L@j@k)^2
L@j@j = sqrt[array@j@j - sum]
for i = j+1 to n-1
{
sum = 0
for k = 0 to j-1
sum = sum + L@i@k * L@j@k
L@i@j = (1 / L@j@j * (array@i@j -sum))
}
}
return new Matrix[L]
}
/** This uses Doolittle's method to decompose a square matrix into an lower
and upper triangular matrix.
This creates two matrices, L and U, where L has the value 1 on the
diagonal from top left to bottom right, like:
1 0 0
L = L21 1 0
L31 L32 1
U11 U12 U13
U = 0 U22 U23
0 0 U33
The original matrix can be obtained as L.multiply[U]
Note that the Doolittle algorithm fails on certain matrices, for example
m = new Matrix[[[1,1,1,-1],[1,1,-1,1],[1,-1,1,1],[-1,1,1,1]]]
See:
https://semath.info/src/inverse-cofactor-ex4.html
for more on that matrix.
See:
https://www.geeksforgeeks.org/doolittle-algorithm-lu-decomposition/
*/
LUDecomposeDoolittle[] :=
{
if ! isSquare[]
{
println["Matrix.LUDecomposeDoolittle only works on square arrays."]
return undef
}
n = rows
L = new array[[n,n], 0]
U = new array[[n,n], 0]
// Decomposing matrix into Upper and Lower
// triangular matrix
for i = 0 to n-1
{
// Upper Triangular
for k = i to n-1
{
// Summation of L(i, j) * U(j, k)
sum = 0
for j = 0 to i-1
sum = sum + (L@i@j * U@j@k)
// Evaluating U(i, k)
U@i@k = array@i@k - sum
}
// Lower Triangular
for k = i to n-1
{
if i == k
L@i@i = 1 // Diagonal as 1
else
{
// Summation of L(k, j) * U(j, i)
sum = 0
for j = 0 to i-1
sum = sum + L@k@j * U@j@i
diag = U@i@i
if (diag == 0)
{
println["Matrix.LUDecomposeDoolittle: U@$i@$i is zero when solving for L@$k@$i. Can't divide by zero."]
println[formatMatrix[L]]
println[formatMatrix[U]]
return undef
}
// Evaluating L(k, i)
L@k@i = (array@k@i - sum) / diag
}
}
}
return [new Matrix[L], new Matrix[U]]
}
/** Returns the determinant of a square matrix. */
det[] :=
{
if ! isSquare[]
{
println["Determinants are only defined for square matrices! Size was $rows x $cols"]
return undef
}
// It's much faster to use precalculated determinant formulas for small
// matrices than, to, say LUDecompose the things.
if rows == 1
return array@0@0
if rows == 2
return array@0@0 array@1@1 - array@0@1 array@1@0 // ad - bc
if rows == 3
return -array@0@2 array@1@1 array@2@0 +
array@0@1 array@1@2 array@2@0 +
array@0@2 array@1@0 array@2@1 -
array@0@0 array@1@2 array@2@1 -
array@0@1 array@1@0 array@2@2 +
array@0@0 array@1@1 array@2@2
if rows == 4
return array@0@1 array@1@3 array@2@2 array@3@0 -
array@0@1 array@1@2 array@2@3 array@3@0 -
array@0@0 array@1@3 array@2@2 array@3@1 +
array@0@0 array@1@2 array@2@3 array@3@1 -
array@0@1 array@1@3 array@2@0 array@3@2 +
array@0@0 array@1@3 array@2@1 array@3@2 +
array@0@1 array@1@0 array@2@3 array@3@2 -
array@0@0 array@1@1 array@2@3 array@3@2 +
array@0@3 (array@1@2 array@2@1 array@3@0 -
array@1@1 array@2@2 array@3@0 -
array@1@2 array@2@0 array@3@1 +
array@1@0 array@2@2 array@3@1 +
array@1@1 array@2@0 array@3@2 -
array@1@0 array@2@1 array@3@2) +
(array@0@1 array@1@2 array@2@0 -
array@0@0 array@1@2 array@2@1 -
array@0@1 array@1@0 array@2@2 +
array@0@0 array@1@1 array@2@2) array@3@3 +
array@0@2 (-array@1@3 array@2@1 array@3@0 +
array@1@1 array@2@3 array@3@0 +
array@1@3 array@2@0 array@3@1 -
array@1@0 array@2@3 array@3@1 -
array@1@1 array@2@0 array@3@3 +
array@1@0 array@2@1 array@3@3)
if rows == 5
return array@0@2 array@1@4 array@2@3 array@3@1 array@4@0 -
array@0@2 array@1@3 array@2@4 array@3@1 array@4@0 -
array@0@1 array@1@4 array@2@3 array@3@2 array@4@0 +
array@0@1 array@1@3 array@2@4 array@3@2 array@4@0 -
array@0@2 array@1@4 array@2@1 array@3@3 array@4@0 +
array@0@1 array@1@4 array@2@2 array@3@3 array@4@0 +
array@0@2 array@1@1 array@2@4 array@3@3 array@4@0 -
array@0@1 array@1@2 array@2@4 array@3@3 array@4@0 +
array@0@2 array@1@3 array@2@1 array@3@4 array@4@0 -
array@0@1 array@1@3 array@2@2 array@3@4 array@4@0 -
array@0@2 array@1@1 array@2@3 array@3@4 array@4@0 +
array@0@1 array@1@2 array@2@3 array@3@4 array@4@0 -
array@0@2 array@1@4 array@2@3 array@3@0 array@4@1 +
array@0@2 array@1@3 array@2@4 array@3@0 array@4@1 +
array@0@0 array@1@4 array@2@3 array@3@2 array@4@1 -
array@0@0 array@1@3 array@2@4 array@3@2 array@4@1 +
array@0@2 array@1@4 array@2@0 array@3@3 array@4@1 -
array@0@0 array@1@4 array@2@2 array@3@3 array@4@1 -
array@0@2 array@1@0 array@2@4 array@3@3 array@4@1 +
array@0@0 array@1@2 array@2@4 array@3@3 array@4@1 -
array@0@2 array@1@3 array@2@0 array@3@4 array@4@1 +
array@0@0 array@1@3 array@2@2 array@3@4 array@4@1 +
array@0@2 array@1@0 array@2@3 array@3@4 array@4@1 -
array@0@0 array@1@2 array@2@3 array@3@4 array@4@1 +
array@0@1 array@1@4 array@2@3 array@3@0 array@4@2 -
array@0@1 array@1@3 array@2@4 array@3@0 array@4@2 -
array@0@0 array@1@4 array@2@3 array@3@1 array@4@2 +
array@0@0 array@1@3 array@2@4 array@3@1 array@4@2 -
array@0@1 array@1@4 array@2@0 array@3@3 array@4@2 +
array@0@0 array@1@4 array@2@1 array@3@3 array@4@2 +
array@0@1 array@1@0 array@2@4 array@3@3 array@4@2 -
array@0@0 array@1@1 array@2@4 array@3@3 array@4@2 +
array@0@1 array@1@3 array@2@0 array@3@4 array@4@2 -
array@0@0 array@1@3 array@2@1 array@3@4 array@4@2 -
array@0@1 array@1@0 array@2@3 array@3@4 array@4@2 +
array@0@0 array@1@1 array@2@3 array@3@4 array@4@2 +
array@0@2 array@1@4 array@2@1 array@3@0 array@4@3 -
array@0@1 array@1@4 array@2@2 array@3@0 array@4@3 -
array@0@2 array@1@1 array@2@4 array@3@0 array@4@3 +
array@0@1 array@1@2 array@2@4 array@3@0 array@4@3 -
array@0@2 array@1@4 array@2@0 array@3@1 array@4@3 +
array@0@0 array@1@4 array@2@2 array@3@1 array@4@3 +
array@0@2 array@1@0 array@2@4 array@3@1 array@4@3 -
array@0@0 array@1@2 array@2@4 array@3@1 array@4@3 +
array@0@1 array@1@4 array@2@0 array@3@2 array@4@3 -
array@0@0 array@1@4 array@2@1 array@3@2 array@4@3 -
array@0@1 array@1@0 array@2@4 array@3@2 array@4@3 +
array@0@0 array@1@1 array@2@4 array@3@2 array@4@3 +
array@0@2 array@1@1 array@2@0 array@3@4 array@4@3 -
array@0@1 array@1@2 array@2@0 array@3@4 array@4@3 -
array@0@2 array@1@0 array@2@1 array@3@4 array@4@3 +
array@0@0 array@1@2 array@2@1 array@3@4 array@4@3 +
array@0@1 array@1@0 array@2@2 array@3@4 array@4@3 -
array@0@0 array@1@1 array@2@2 array@3@4 array@4@3 +
array@0@4 (array@1@1 array@2@3 array@3@2 array@4@0 -
array@1@1 array@2@2 array@3@3 array@4@0 -
array@1@0 array@2@3 array@3@2 array@4@1 +
array@1@0 array@2@2 array@3@3 array@4@1 -
array@1@1 array@2@3 array@3@0 array@4@2 +
array@1@0 array@2@3 array@3@1 array@4@2 +
array@1@1 array@2@0 array@3@3 array@4@2 -
array@1@0 array@2@1 array@3@3 array@4@2 +
array@1@3 (array@2@2 array@3@1 array@4@0 -
array@2@1 array@3@2 array@4@0 -
array@2@2 array@3@0 array@4@1 +
array@2@0 array@3@2 array@4@1 +
array@2@1 array@3@0 array@4@2 -
array@2@0 array@3@1 array@4@2) +
(array@1@1 array@2@2 array@3@0 -
array@1@0 array@2@2 array@3@1 -
array@1@1 array@2@0 array@3@2 +
array@1@0 array@2@1 array@3@2) array@4@3 +
array@1@2 (-array@2@3 array@3@1 array@4@0 +
array@2@1 array@3@3 array@4@0 +
array@2@3 array@3@0 array@4@1 -
array@2@0 array@3@3 array@4@1 -
array@2@1 array@3@0 array@4@3 +
array@2@0 array@3@1 array@4@3)) +
(array@0@2 (-array@1@3 array@2@1 array@3@0 +
array@1@1 array@2@3 array@3@0 +
array@1@3 array@2@0 array@3@1 -
array@1@0 array@2@3 array@3@1 -
array@1@1 array@2@0 array@3@3 +
array@1@0 array@2@1 array@3@3) +
array@0@1 (array@1@3 array@2@2 array@3@0 -
array@1@2 array@2@3 array@3@0 -
array@1@3 array@2@0 array@3@2 +
array@1@0 array@2@3 array@3@2 +
array@1@2 array@2@0 array@3@3 -
array@1@0 array@2@2 array@3@3) +
array@0@0 (-array@1@3 array@2@2 array@3@1 +
array@1@2 array@2@3 array@3@1 +
array@1@3 array@2@1 array@3@2 -
array@1@1 array@2@3 array@3@2 -
array@1@2 array@2@1 array@3@3 +
array@1@1 array@2@2 array@3@3)) array@4@4 +
array@0@3 (-array@1@1 array@2@4 array@3@2 array@4@0 +
array@1@1 array@2@2 array@3@4 array@4@0 +
array@1@0 array@2@4 array@3@2 array@4@1 -
array@1@0 array@2@2 array@3@4 array@4@1 +
array@1@1 array@2@4 array@3@0 array@4@2 -
array@1@0 array@2@4 array@3@1 array@4@2 -
array@1@1 array@2@0 array@3@4 array@4@2 +
array@1@0 array@2@1 array@3@4 array@4@2 +
array@1@4 (-array@2@2 array@3@1 array@4@0 +
array@2@1 array@3@2 array@4@0 +
array@2@2 array@3@0 array@4@1 -
array@2@0 array@3@2 array@4@1 -
array@2@1 array@3@0 array@4@2 +
array@2@0 array@3@1 array@4@2) -
array@1@1 array@2@2 array@3@0 array@4@4 +
array@1@0 array@2@2 array@3@1 array@4@4 +
array@1@1 array@2@0 array@3@2 array@4@4 -
array@1@0 array@2@1 array@3@2 array@4@4 +
array@1@2 (array@2@4 array@3@1 array@4@0 -
array@2@1 array@3@4 array@4@0 -
array@2@4 array@3@0 array@4@1 +
array@2@0 array@3@4 array@4@1 +
array@2@1 array@3@0 array@4@4 -
array@2@0 array@3@1 array@4@4))
// If we fell through to here, we have a larger matrix and have to try
// to find its determinant through more inefficent methods.
// TODO: Calculate determinant in terms of other determinants instead of
// using LUDecompose when LUDecompose doesn't work.
product = 1
[L, U] = LUDecomposeDoolittle[]
// This will happen if the matrix is singular.
if U == undef
return undef
// Multiply diagonals of the lower triangular matrix. The
// upper triangular matrix has all ones on the diagonal.
// Should there be a permutation matrix here to get the signs
// right?
for i=0 to rows-1
product = product * (U.array)@i@i
return product
}
/** Create a square identity matrix with the specified number of rows and
columns. The elements on the diagonal will be set to 1, the rest to
zero. This requires Frink 2020-04-19 or later. */
class makeIdentity[dimension] :=
{
return new Matrix[makeArray[[dimension, dimension], {|a,b| a==b ? 1 : 0}]]
}
/** Makes a diagonal matrix. This is passed in an array of elements (e.g.
[1,2,3] that will make up the diagonal elements. This requires Frink
2020-04-22 or later.
*/
class makeDiagonal[array] :=
{
d = length[array]
return new Matrix[makeArray[[d,d], {|a,b,data| a==b ? data@a : 0}, array]]
}
/** Returns a matrix with the specified row and column removed. Row and
column numbers are 1-indexed. This is used in many matrix operations
including calculation of determinant or of the adjugate/adjoint matrix.
*/
removeRowColumn[rowToRemove, colToRemove] :=
{
a = makeArray[[rows-1, cols-1]]
newRow = 0
ROW:
for origRow = 0 to rows-1
{
if origRow == rowToRemove-1
next ROW
newCol = 0
COL:
for origCol = 0 to rows-1
{
if origCol == colToRemove-1
next COL
a@newRow@newCol = array@origRow@origCol
newCol = newCol + 1
}
newRow = newRow + 1
}
return new Matrix[a]
}
/** Returns the adjugate or adjoint matrix. See:
https://semath.info/src/inverse-cofactor-ex4.html
TODO: Calculate hardcoded adjugate matrices for small matrices because
this is expensive
*/
adjugate[] :=
{
a = makeArray[[rows,cols]]
for i = 0 to rows-1
for j = 0 to cols-1
a@i@j = (-1)^((i+1)+(j+1)) * removeRowColumn[j+1, i+1].det[]
return new Matrix[a]
}
/** Returns the inverse of a matrix. See:
https://semath.info/src/inverse-cofactor-ex4.html
TODO: Calculate hardcoded inverse matrices for small matrices because
this is expensive
*/
inverse[] :=
{
if ! isSquare[]
{
println["Matrix.inverse only works on square arrays."]
return undef
}
// It's much faster to calculate inverse matrices with hard-coded
// equations for small matrices.
if rows == 1
return 1 / array@0@0
if rows == 2
{
invdet = 1/det[]
// [ d -b ]
// [ -c a ] / det
return (new Matrix[[[array@1@1, -array@0@1],
[-array@1@0, array@0@0]]]).multiplyByScalar[invdet]
}
if rows == 3
{
invdet = 1/det[]
return (new Matrix[[[-array@1@2 array@2@1 + array@1@1 array@2@2,
array@0@2 array@2@1 - array@0@1 array@2@2,
-array@0@2 array@1@1 + array@0@1 array@1@2],
[ array@1@2 array@2@0 - array@1@0 array@2@2,
-array@0@2 array@2@0 + array@0@0 array@2@2,
array@0@2 array@1@0 - array@0@0 array@1@2],
[-array@1@1 array@2@0 + array@1@0 array@2@1,
array@0@1 array@2@0 - array@0@0 array@2@1,
-array@0@1 array@1@0 + array@0@0 array@1@1]]]).multiplyByScalar[invdet]
}
if rows == 4
{
invdet = 1/det[]
return (new Matrix[[[-array@1@3 array@2@2 array@3@1 +
array@1@2 array@2@3 array@3@1 +
array@1@3 array@2@1 array@3@2 -
array@1@1 array@2@3 array@3@2 -
array@1@2 array@2@1 array@3@3 +
array@1@1 array@2@2 array@3@3,
array@0@3 array@2@2 array@3@1 -
array@0@2 array@2@3 array@3@1 -
array@0@3 array@2@1 array@3@2 +
array@0@1 array@2@3 array@3@2 +
array@0@2 array@2@1 array@3@3 -
array@0@1 array@2@2 array@3@3,
-array@0@3 array@1@2 array@3@1 +
array@0@2 array@1@3 array@3@1 +
array@0@3 array@1@1 array@3@2 -
array@0@1 array@1@3 array@3@2 -
array@0@2 array@1@1 array@3@3 +
array@0@1 array@1@2 array@3@3,
array@0@3 array@1@2 array@2@1 -
array@0@2 array@1@3 array@2@1 -
array@0@3 array@1@1 array@2@2 +
array@0@1 array@1@3 array@2@2 +
array@0@2 array@1@1 array@2@3 -
array@0@1 array@1@2 array@2@3],
[array@1@3 array@2@2 array@3@0 -
array@1@2 array@2@3 array@3@0 -
array@1@3 array@2@0 array@3@2 +
array@1@0 array@2@3 array@3@2 +
array@1@2 array@2@0 array@3@3 -
array@1@0 array@2@2 array@3@3,
-array@0@3 array@2@2 array@3@0 +
array@0@2 array@2@3 array@3@0 +
array@0@3 array@2@0 array@3@2 -
array@0@0 array@2@3 array@3@2 -
array@0@2 array@2@0 array@3@3 +
array@0@0 array@2@2 array@3@3,
array@0@3 array@1@2 array@3@0 -
array@0@2 array@1@3 array@3@0 -
array@0@3 array@1@0 array@3@2 +
array@0@0 array@1@3 array@3@2 +
array@0@2 array@1@0 array@3@3 -
array@0@0 array@1@2 array@3@3,
-array@0@3 array@1@2 array@2@0 +
array@0@2 array@1@3 array@2@0 +
array@0@3 array@1@0 array@2@2 -
array@0@0 array@1@3 array@2@2 -
array@0@2 array@1@0 array@2@3 +
array@0@0 array@1@2 array@2@3],
[-array@1@3 array@2@1 array@3@0 +
array@1@1 array@2@3 array@3@0 +
array@1@3 array@2@0 array@3@1 -
array@1@0 array@2@3 array@3@1 -
array@1@1 array@2@0 array@3@3 +
array@1@0 array@2@1 array@3@3,
array@0@3 array@2@1 array@3@0 -
array@0@1 array@2@3 array@3@0 -
array@0@3 array@2@0 array@3@1 +
array@0@0 array@2@3 array@3@1 +
array@0@1 array@2@0 array@3@3 -
array@0@0 array@2@1 array@3@3,
-array@0@3 array@1@1 array@3@0 +
array@0@1 array@1@3 array@3@0 +
array@0@3 array@1@0 array@3@1 -
array@0@0 array@1@3 array@3@1 -
array@0@1 array@1@0 array@3@3 +
array@0@0 array@1@1 array@3@3,
array@0@3 array@1@1 array@2@0 -
array@0@1 array@1@3 array@2@0 -
array@0@3 array@1@0 array@2@1 +
array@0@0 array@1@3 array@2@1 +
array@0@1 array@1@0 array@2@3 -
array@0@0 array@1@1 array@2@3],
[array@1@2 array@2@1 array@3@0 -
array@1@1 array@2@2 array@3@0 -
array@1@2 array@2@0 array@3@1 +
array@1@0 array@2@2 array@3@1 +
array@1@1 array@2@0 array@3@2 -
array@1@0 array@2@1 array@3@2,
-array@0@2 array@2@1 array@3@0 +
array@0@1 array@2@2 array@3@0 +
array@0@2 array@2@0 array@3@1 -
array@0@0 array@2@2 array@3@1 -
array@0@1 array@2@0 array@3@2 +
array@0@0 array@2@1 array@3@2,
array@0@2 array@1@1 array@3@0 -
array@0@1 array@1@2 array@3@0 -
array@0@2 array@1@0 array@3@1 +
array@0@0 array@1@2 array@3@1 +
array@0@1 array@1@0 array@3@2 -
array@0@0 array@1@1 array@3@2,
-array@0@2 array@1@1 array@2@0 +
array@0@1 array@1@2 array@2@0 +
array@0@2 array@1@0 array@2@1 -
array@0@0 array@1@2 array@2@1 -
array@0@1 array@1@0 array@2@2 +
array@0@0 array@1@1 array@2@2]]]).multiplyByScalar[invdet]
}
// Otherwise use the adjugate function.
return adjugate[].multiplyByScalar[1/det[]]
}
/** Returns the Kronecker product of a and b.
https://en.wikipedia.org/wiki/Kronecker_product
*/
class KroneckerProduct[a is Matrix, b is Matrix] :=
{
m = a.rows
n = a.cols
p = b.rows
q = b.cols
newRows = m p
newCols = n q
n = new array[[newRows, newCols], 0]
for i=0 to newRows-1
for j=0 to newCols-1
n@i@j = (a.array)@(i div p)@(j div q) * (b.array)@(i mod p)@(j mod q)
return new Matrix[n]
}
/** Return the Kronecker product of this matrix and b. */
KroneckerProduct[b is Matrix] := KroneckerProduct[this, b]
/** Returns true if both matrices are equal (that is, they have the same
dimensions and all array elements are equal.)
*/
equals[other is Matrix] :=
{
return rows==other.rows and cols==other.cols and array==other.array
}
/** Returns a new Matrix where each element is the complex conjugate of
the corresponding element in original Matrix. */
conjugate[] :=
{
c = makeArray[[rows, cols], {|r,c,data| conjugate[data@r@c]}, array]
return new Matrix[c]
}
/** Returns a new Matrix where the Matrix is first transposed and then
each element is the complex conjugate of
the corresponding element in the transposed Matrix. */
conjugateTranspose[] :=
{
t = array.transpose[]
c = makeArray[[rows, cols], {|r,c,data| conjugate[data@r@c]}, t]
return new Matrix[c]
}
/** Returns true if this matrix is "Hermitian", or "self-adjoint", that is,
the matrix must be square and equal to its own conjugate transpose. In
other words, for all elements, array@i@j == conjugate[array@j@i] where
conjugate is the complex conjugate of a number. */
isHermitian[] :=
{
if rows != cols
return false
for i=0 to rows-1
for j = i+1 to rows-1
if array@i@j != conjugate[array@j@i]
return false
return true
}
/** Performs the QR decomposition of a matrix.
This is based on the (real-only) implementation at:
https://github.com/fiji/Jama/blob/master/src/main/java/Jama/QRDecomposition.java
This is an internal implementation, primarily for use by the leastSquares
method, that returns raw arrays [QR, rdiag]. If you're solving for
least squares, use that routine directly instead of this.
*/
QRDecomposeInternal[] :=
{
QR = deepCopy[array] // Copy the original matrix's array
rdiag = new array[[cols], 0] // Stores the diagonal
for k = 0 to cols-1
{
// Compute 2-norm of k-th column
nrm = 0 QR@0@k // Make units work right
for i = k to rows-1
nrm = sqrt[nrm^2 + (QR@i@k)^2]
if nrm != 0 QR@0@k // Make units work right
{
// Form k-th Householder vector
if isNegativeUnit[QR@k@k]
nrm = -nrm
for i = k to rows-1
QR@i@k = QR@i@k / nrm
QR@k@k = QR@k@k + 1
// Apply transformation to remaining columns
for j = k+1 to cols-1
{
s = undef
for i=k to rows-1
{
if s == undef
s = QR@i@k * QR@i@j
else
s = s + QR@i@k * QR@i@j
}
s = -s / QR@k@k
for i = k to rows-1
QR@i@j = QR@i@j + s * QR@i@k
}
}
rdiag@k = -nrm
}
return [QR, rdiag]
}
/** This performs a QR decomposition of this matrix and returns [Q, R]
as new matrices.
Q is the orthogonal factor.
R is the upper triangular factor.
If you are doing a least-squares solve, call leastSquares directly
instead.
THINK ABOUT: Should this return the Householder vectors and rdiag?
*/
QRDecompose[] :=
{
// The Q and R matrices are packed into the results of the following.
[QR, rdiag] = QRDecomposeInternal[]
// Extract the Q matrix
Q = new array[[rows, cols], 0]
for k = cols-1 to 0 step -1
{
for i = 0 to rows-1
Q@i@k = 0
Q@k@k = 1
for j = k to cols-1
{
if QR@k@k != 0 QR@k@k // Make units work right
{
s = 0 (QR@0@k) * Q@0@j // Make units work right
for i = k to rows-1
s = s + QR@i@k * Q@i@j
s = -s / QR@k@k
for i = k to rows-1
Q@i@j = Q@i@j + s * QR@i@k
}
}
}
// Extract the R matrix
R = new array[[cols, cols], 0]
for i = 0 to cols-1
{
for j = 0 to cols-1
{
if i < j
R@i@j = QR@i@j
else
if i == j
R@i@j = rdiag@i
else
R@i@j = 0
}
}
return [new Matrix[Q], new Matrix[R]]
}
/** Return the least-squares solution (called X) of the system
A * X = B
where this Matrix is A and parameter B is B.
This can be performed on an overdetermined system, where there are
more measurements than equations.
TODO: Try to solve exactly using x = A.inverse[] * b when the system
is not overdetermined?
See MatrixQRTest.frink for examples.
This is the best discussion I've seen of least-squares fitting:
https://www.aleksandrhovhannisyan.com/blog/the-method-of-least-squares/
https://www.aleksandrhovhannisyan.com/blog/least-squares-fitting/
*/
leastSquares[B is Matrix] :=
{
if rows != B.rows
{
println["Matrix.leastSquares: Matrix row dimensions must agree."]
return undef
}
[QR, rdiag] = QRDecomposeInternal[]
// Check if the matrix is "full rank," that is, that each row is
// independent (not a multiple of) other rows.
for j = 0 to cols-1
{
if rdiag@j == 0 rdiag@j
{
println["Matrix.leastSquares: Row entries are not all independent."]
return undef
}
}
X = deepCopy[B.array] // Copy B's array
// Compute Y = Q.transpose * B
for k = 0 to cols-1
{
for j = 0 to B.cols-1
{
s = undef
for i = k to rows-1
if s == undef
s = QR@i@k * X@i@j
else
s = s + QR@i@k * X@i@j
s = -s / QR@k@k
for i = k to rows-1
X@i@j = X@i@j + s * QR@i@k
}
}
// Solve R * X = y
for k = cols-1 to 0 step -1
{
for j = 0 to B.cols-1
X@k@j = X@k@j / rdiag@k
for i = 0 to k-1
for j = 0 to B.cols-1
X@i@j = X@i@j - X@k@j * QR@i@k
}
X1 = new Matrix[X]
return X1.getSubMatrix[0, cols-1, 0, B.cols-1]
}
/** Gets a submatrix of this matrix. */
getSubMatrix[fromRow, toRow, fromCol, toCol] :=
{
a = new array[[toRow-fromRow+1, toCol-fromCol+1], 0]
for row = fromRow to toRow
for col = fromCol to toCol
a@(row-fromRow)@(col-fromCol) = array@row@col
// println["a is $a"]
return new Matrix[a]
}
/** Rounds values to the nearest integer if they are less than the specified
relative error away from an integer and returns a new array. */
roundToInt[relerror = 1e-14] :=
{
ret = new array[[rows, cols]]
for r = 0 to rows-1
for c = 0 to cols-1
{
v = array@r@c
rnd = round[v]
if abs[(rnd-v)/rnd] <= relerror
ret@r@c = rnd
else
ret@r@c = v
}
return new Matrix[ret]
}
}
"class Matrix loaded OK."
Download or view Matrix.frink in plain text format
This is a program written in the programming language Frink.
For more information, view the Frink
Documentation or see More Sample Frink Programs.
Alan Eliasen was born 20217 days, 15 hours, 57 minutes ago.