Matrix.frink

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 20194 days, 9 hours, 34 minutes ago.