Matrix Inverse From Scratch Using C# – Five Different Techniques

One rainy morning before work, I figured I’d dust off some of my C# matrix inverse code. If you have a square matrix A, the inverse, Ai, is a matrix where A * Ai = I, where I is the identity matrix. (Also, Ai * A = I). For example, if A =

  4.00  7.00  2.00
  6.00  0.00  3.00
  8.00  1.00  9.00

The inverse of A is:

   0.014286   0.290476  -0.100000
   0.142857  -0.095238   0.000000
  -0.028571  -0.247619   0.200000

And A * Ai = I which is:

  1.00  0.00  0.00
  0.00  1.00  0.00
  0.00  0.00  1.00

Computing the inverse of a matrix is extremely difficult. Wikipedia lists over a dozen different algorithms, and each algorithm has several variations. The number of different algorithms is a direct indication of the difficulty of matrix inversion.

I put together a demo of matrix inverse with C# using five different algorithms: 1.) LUP (Croat version), 2. QR (Householder version), 3.) SVD (Jacobi version), 4.) Newton (Pan version), 5.) Cholesky (Banachiewicz version).

Here’s the output of my demo:

Begin C# matrix inverse demo

Source matrix A:
  4.00  7.00  2.00
  6.00  0.00  3.00
  8.00  1.00  9.00

==========

Inverse matrix via LUP-Crout:
   0.014286   0.290476  -0.100000
   0.142857  -0.095238   0.000000
  -0.028571  -0.247619   0.200000

Inverse matrix via QR-Householder:
   0.014286   0.290476  -0.100000
   0.142857  -0.095238  -0.000000
  -0.028571  -0.247619   0.200000

Inverse matrix via SVD-Jacobi:
   0.014286   0.290476  -0.100000
   0.142857  -0.095238  -0.000000
  -0.028571  -0.247619   0.200000

Inverse matrix via Newton-Pan:
   0.014286   0.290476  -0.100000
   0.142857  -0.095238  -0.000000
  -0.028571  -0.247619   0.200000

==========

Postive-Definite matrix M:
  1.00  0.20  0.30  0.40
  0.20  1.00  0.50  0.60
  0.30  0.50  1.00  0.70
  0.40  0.60  0.70  1.00

Inverse matrix via Cholesky-Banachiewicz:
   1.195815   0.082212  -0.059791  -0.485800
   0.082212   1.599402  -0.254111  -0.814649
  -0.059791  -0.254111   2.002990  -1.225710
  -0.485800  -0.814649  -1.225710   2.541106
verified correct

End

Not every square matrix has an inverse. There is no one best matrix inverse technique. Each technique has pros and cons. The Cholesky algorithm applies only to symmetric matrices (actually positive-definite) that occur in many algorithms that use kernel matrices, such as kernel ridge regression.

It would literally take an entire book to explain all of these techniques in detail, so I won’t try. The code is here in case someone out there needs it.

Good fun for me on a rainy morning before work.


I like implementing code from scratch instead of using an external library. Using from-scratch code gives me full visibility into the inner workings of the system.

The girl-in-the-glass-tube is a common science fiction theme. Left: “Amazing Stories”, Nov. 1939. Center: “Frozen Limit”, 1950, by John Fearn. Right: “Planet Stories”, Summer 1948.


Demo code. Very long and very complex. I believe it’s all solid. It’s been tested but not exhaustively. Replace “lt” (less than), “gt”, “lte”, “gte” with Boolean operator symbols (my blog editor chokes on symbols).

using System;
using System.IO;
using System.Collections.Generic;

// C# matrix inverse using five different algorithms
// 1. LUP (Crout)
// 2. QR (Householder)
// 3. SVD (Jacobi)
// 4. Newton (Pan)
// 5. Cholesky (Banachiewicz) (positive definite only)

namespace MatrixInverses
{
  internal class MatrixInversesProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("\nBegin C# matrix inverse demo ");

      double[][] A = new double[4][];
      A[0] = new double[] { 4.0, 7.0, 1.0, 2.0 };
      A[1] = new double[] { 6.0, 0.0, 3.0, 5.0 };
      A[2] = new double[] { 8.0, 1.0, 9.0, 2.0 };
      A[3] = new double[] { 2.0, 5.0, 6.0, -3.0 };

      // to load matrix from file:
      // double[][] A = MatLoad("my_data.txt",
      //   new int[] {0, 1, 2, 3}, ',', "#");

      Console.WriteLine("\nSource matrix A: ");
      MatShow(A, 2, 6);

      Console.WriteLine("\n==========");

      double[][] AiLUP = MatInverseLUP(A);
      Console.WriteLine("\nInverse matrix via" +
        " LUP-Crout: ");
      MatShow(AiLUP, 6, 11);

      double[][] AiQR = MatInverseQR(A);
      Console.WriteLine("\nInverse matrix via" +
        " QR-Householder: ");
      MatShow(AiQR, 6, 11);

      double[][] AiSVD = MatInverseSVD(A);
      Console.WriteLine("\nInverse matrix via" +
        " SVD-Jacobi: ");
      MatShow(AiSVD, 6, 11);

      double[][] AiNewton = MatInverseNewton(A);
      Console.WriteLine("\nInverse matrix via" +
        " Newton-Pan: ");
      MatShow(AiNewton, 6, 11);

      double[][] M = new double[4][]; // positive definite
      M[0] = new double[] { 1.0, 0.2, 0.3, 0.4 };
      M[1] = new double[] { 0.2, 1.0, 0.5, 0.6 };
      M[2] = new double[] { 0.3, 0.5, 1.0, 0.7 };
      M[3] = new double[] { 0.4, 0.6, 0.7, 1.0 };

      Console.WriteLine("\n==========");

      Console.WriteLine("\nPostive-Definite matrix M: ");
      MatShow(M, 2, 6);

      double[][] MiC = MatInverseCholesky(M);
      Console.WriteLine("\nInverse matrix via" +
        " Cholesky-Banachiewicz: ");
      MatShow(MiC, 6, 11);

      double[][] check = MatProduct(M, MiC);
      double[][] I = MatIdentity(M.Length);
      if (MatAreEqual(check, I, 1.0e-12) == true)
        Console.WriteLine("verified correct ");

      Console.WriteLine("\nEnd ");
      Console.ReadLine();
    } // Main

    // ------------------------------------------------------

    // helpers for Main(): 

    static double[][] MatIdentity(int n)
    {
      double[][] result = new double[n][];
      for (int i = 0; i "lt" n; ++i)
        result[i] = new double[n];
      for (int i = 0; i "lt" n; ++i)
        result[i][i] = 1.0;
      return result;
    }

    static bool MatAreEqual(double[][] A,
       double[][] B, double eps)
    {
      int nr = A.Length; int nc = A[0].Length;
      for (int i = 0; i "lt" nr; ++i)
        for (int j = 0; j "lt" nc; ++j)
          if (Math.Abs(A[i][j] - B[i][j]) "gt" eps)
            return false;
      return true;
    }

    static void MatShow(double[][] m, int dec, int wid)
    {
      for (int i = 0; i "lt" m.Length; ++i)
      {
        for (int j = 0; j "lt" m[0].Length; ++j)
        {
          double v = m[i][j];
          Console.Write(v.ToString("F" + dec).PadLeft(wid));
        }
        Console.WriteLine("");
      }
    }

    static double[][] MatProduct(double[][] A,
        double[][] B)
    {
      int aRows = A.Length;
      int aCols = A[0].Length;
      int bRows = B.Length;
      int bCols = B[0].Length;
      if (aCols != bRows)
        throw new Exception("Non-conformable matrices");

      double[][] result = new double[aRows][];
      for (int i = 0; i "lt" aRows; ++i)
        result[i] = new double[bCols];

      for (int i = 0; i "lt" aRows; ++i) // each row of A
        for (int j = 0; j "lt" bCols; ++j) // each col of B
          for (int k = 0; k "lt" aCols; ++k) 
            result[i][j] += A[i][k] * B[k][j];

      return result;
    }

    static double[][] MatLoad(string fn, int[] usecols,
      char sep, string comment)
    {
      List"lt"double[]"gt" result = 
        new List"lt"double[]"gt"();
      string line = "";
      FileStream ifs = new FileStream(fn, FileMode.Open);
      StreamReader sr = new StreamReader(ifs);
      while ((line = sr.ReadLine()) != null)
      {
        if (line.StartsWith(comment) == true)
          continue;
        string[] tokens = line.Split(sep);
        List"lt"double"gt" lst = new List"lt"double"gt"();
        for (int j = 0; j "lt" usecols.Length; ++j)
          lst.Add(double.Parse(tokens[usecols[j]]));
        double[] row = lst.ToArray();
        result.Add(row);
      }
      sr.Close(); ifs.Close();
      return result.ToArray();
    }

    // ======================================================
    // 1. LUP - Crout
    // ======================================================

    static double[][] MatInverseLUP(double[][] M)
    {
      // LUP using Crout's algorithm
      // A = P * L * U
      // Ainv = inv(U) * inv(L) * inv(P)
      // note: inv(P) = P
      int n = M.Length;
      double[][] lum;
      int[] perm;
      int sgn = MatDecomposeLUP(M, out lum, out perm);

      // check if determinant is 0
      double det = (double)sgn;
      for (int i = 0; i "lt" n; ++i)
        det *= lum[i][i];
      if (det == 0.0)
      {
        Console.WriteLine("Inverse doesn't exist ");
        Console.ReadLine();
      }

      // call two helper functions . . 
      double[][] L = ExtractLower(lum);  // 1s on diag
      double[][] U = ExtractUpper(lum);  // upper tri

      // convert perm vector to P matrix
      double[][] P = MatMake(n, n);
      for (int i = 0; i "lt" n; ++i)
        P[i][perm[i]] = 1.0;

      // compute Li and Ui via helpers
      double[][] Li = MatInverseLowerTri(L);
      double[][] Ui = MatInverseUpperTri(U);

      // compute inverse Ai = Ui * Li * Pi
      double[][] result = MatProduct(Ui, MatProduct(Li, P));
      return result;

      // ----------------------------------------------------
      // helpers: MatDecomposeLUP, MatMake, ExtractLower,
      // ExtractUpper, MatInverseLowerTri,
      // MatInverseUpperTri, MatIdentity, MatProduct
      // ----------------------------------------------------

      static int MatDecomposeLUP(double[][] m,
        out double[][] lum, out int[] perm)
      {
        // Crout's LU decomposition for matrix determinant
        // and inverse
        // stores combined lower and upper in lum[][]
        // stores row permuations into perm[]
        // returns +1 or -1 according to even or odd
        // number of row permutations
        // lower gets dummy 1.0s on diagonal (0.0s above)
        // upper gets lum values on diagonal (0.0s below)

        int toggle = +1; // even (+1) or odd (-1) perms
        int n = m.Length;

        // make a copy of m[][] into result lu[][]
        lum = MatMake(n, n);
        for (int i = 0; i "lt" n; ++i)
          for (int j = 0; j "lt" n; ++j)
            lum[i][j] = m[i][j];

        // make perm[]
        perm = new int[n];
        for (int i = 0; i "lt" n; ++i)
          perm[i] = i;

        for (int j = 0; j "lt" n - 1; ++j) 
        {
          double max = Math.Abs(lum[j][j]);
          int piv = j;

          for (int i = j + 1; i "lt" n; ++i) // pivot index
          {
            double xij = Math.Abs(lum[i][j]);
            if (xij "gt" max)
            {
              max = xij;
              piv = i;
            }
          } // i

          if (piv != j)
          {
            double[] tmp = lum[piv]; // swap rows j, piv
            lum[piv] = lum[j];
            lum[j] = tmp;

            int t = perm[piv]; // swap perm elements
            perm[piv] = perm[j];
            perm[j] = t;

            toggle = -toggle;
          }

          double xjj = lum[j][j];
          if (xjj != 0.0)
          {
            for (int i = j + 1; i "lt" n; ++i)
            {
              double xij = lum[i][j] / xjj;
              lum[i][j] = xij;
              for (int k = j + 1; k "lt" n; ++k)
                lum[i][k] -= xij * lum[j][k];
            }
          }

        } // j

        return toggle;  // for determinant
      } // MatDecomposeHelp

      static double[][] MatMake(int nRows, int nCols)
      {
        double[][] result = new double[nRows][];
        for (int i = 0; i "lt" nRows; ++i)
          result[i] = new double[nCols];
        return result;
      }

      static double[][] ExtractLower(double[][] lum)
      {
        // lower part of an LU Crout's decomposition
        // (dummy 1.0s on diagonal, 0.0s above)
        int n = lum.Length;
        double[][] result = MatMake(n, n);
        for (int i = 0; i "lt" n; ++i)
        {
          for (int j = 0; j "lt" n; ++j)
          {
            if (i == j)
              result[i][j] = 1.0;
            else if (i "gt" j)
              result[i][j] = lum[i][j];
          }
        }
        return result;
      }

      static double[][] ExtractUpper(double[][] lum)
      {
        // upper part of an LU (lu values on diagional
        // and above, 0.0s below)
        int n = lum.Length;
        double[][] result = MatMake(n, n);
        for (int i = 0; i "lt" n; ++i)
        {
          for (int j = 0; j "lt" n; ++j)
          {
            if (i "lte" j)
              result[i][j] = lum[i][j];
          }
        }
        return result;
      }

      static double[][] MatInverseLowerTri(double[][] lower)
      {
        // inverse of lower triangular non-fancy version
        int n = lower.Length;  // must be square matrix
        double[][] result = MatIdentity(n);

        for (int k = 0; k "lt" n; ++k)
        {
          for (int j = 0; j "lt" n; ++j)
          {
            for (int i = 0; i "lt" k; ++i)
            {
              result[k][j] -= result[i][j] * lower[k][i];
            }
            result[k][j] /= lower[k][k];
          }
        }
        return result;
      }

      static double[][] MatInverseUpperTri(double[][] U)
      {
        int n = U.Length;  // must be square matrix
        double[][] result = MatIdentity(n);

        for (int k = 0; k "lt" n; ++k)
        {
          for (int j = 0; j "lt" n; ++j)
          {
            for (int i = 0; i "lt" k; ++i)
            {
              result[j][k] -= result[j][i] * U[i][k];
            }
            result[j][k] /= (U[k][k] + 1.0e-8);
          }
        }
        return result;
      }

      static double[][] MatIdentity(int n)
      {
        double[][] result = MatMake(n, n);
        for (int i = 0; i "lt" n; ++i)
          result[i][i] = 1.0;
        return result;
      }

      static double[][] MatProduct(double[][] A,
        double[][] B)
      {
        int aRows = A.Length;
        int aCols = A[0].Length;
        int bRows = B.Length;
        int bCols = B[0].Length;
        if (aCols != bRows)
          throw new Exception("Non-conformable matrices");

        double[][] result = new double[aRows][];
        for (int i = 0; i "lt" aRows; ++i)
          result[i] = new double[bCols];

        for (int i = 0; i "lt" aRows; ++i) // each row of A
          for (int j = 0; j "lt" bCols; ++j) // each col of B
            for (int k = 0; k "lt" aCols; ++k)
              result[i][j] += A[i][k] * B[k][j];

        return result;
      }

    } // MatInverseLUP

    // ======================================================
    // 2. QR - Householder
    // ======================================================

    static double[][] MatInverseQR(double[][] M)
    {
      // inverse using QR decomp (Householder algorithm)
      double[][] Q;
      double[][] R;
      MatDecompQR(M, out Q, out R, true);

      // TODO: check if determinant is zero (no inverse)

      double[][] Rinv = MatInverseUpperTri(R); // std algo
      double[][] Qtrans = MatTranspose(Q);  // is inv(Q)
      return MatProduct(Rinv, Qtrans);

      // ----------------------------------------------------
      // helpers: MatDecomposeQR, MatMake, MatTranspose,
      // MatInverseUpperTri, MatIdentity, MatCopy, VecNorm,
      // VecDot, VecToMat, MatProduct
      // ----------------------------------------------------

      static void MatDecompQR(double[][] M,
         out double[][] Q, out double[][] R, bool reduced)
      {
        // QR decomposition, Householder algorithm.
        // no helper functions except matrix multiply
        int m = M.Length;
        int n = M[0].Length;

        if (m "lt" n)
          throw new Exception("Cannot do rows less than cols");

        // double[][] QQ = MatIdentity(m); // working Q
        double[][] QQ = new double[m][];
        for (int ii = 0; ii "lt" m; ++ii)
          QQ[ii] = new double[m];
        for (int ii = 0; ii "lt" m; ++ii)
          QQ[ii][ii] = 1.0;

        //double[][] RR = MatCopy(M); // working R
        double[][] RR = new double[m][];
        for (int ii = 0; ii "lt" m; ++ii)
          RR[ii] = new double[n];
        for (int ii = 0; ii "lt" m; ++ii)
          for (int jj = 0; jj "lt" n; ++jj)
            RR[ii][jj] = M[ii][jj];

        int end;
        if (m == n) end = n - 1;
        else end = n;

        for (int i = 0; i "lt" end; ++i)
        {
          // double[][] H = MatIdentity(m);
          double[][] H = new double[m][];
          for (int ii = 0; ii "lt" m; ++ii)
            H[ii] = new double[m];
          for (int ii = 0; ii "lt" m; ++ii)
            H[ii][ii] = 1.0;

          double[] a = new double[m - i]; // corr
          int k = 0;
          for (int ii = i; ii "lt" m; ++ii) // corr
            a[k++] = RR[ii][i];

          // double normA = VecNorm(a);
          double sum = 0.0;
          for (int ii = 0; ii "lt" a.Length; ++ii)
            sum += a[ii] * a[ii];
          double normA = Math.Sqrt(sum);

          if (a[0] "lt" 0.0 && normA "gt" 0.0) // corr
            normA = -normA;
          else if (a[0] "gt" 0.0 && normA "lt" 0.0)
            normA = -normA;

          double[] v = new double[a.Length];
          for (int j = 0; j "lt" v.Length; ++j)
            v[j] = a[j] / (a[0] + normA);
          v[0] = 1.0;

          // Householder algorithm

          // double[][] h = MatIdentity(a.Length);
          double[][] h = new double[a.Length][];
          for (int ii = 0; ii "lt" a.Length; ++ii)
            h[ii] = new double[a.Length];
          for (int ii = 0; ii "lt" a.Length; ++ii)
            h[ii][ii] = 1.0;

          // double vvDot = VecDot(v, v);
          double vvDot = 0.0;
          for (int ii = 0; ii "lt" v.Length; ++ii)
            vvDot += v[ii] * v[ii];

          // double[][] A = VecToMat(v, v.Length, 1
          double[][] A = new double[v.Length][];
          int ka = 0;
          for (int ii = 0; ii "lt" v.Length; ++ii)
            A[ii] = new double[1];

          for (int ii = 0; ii "lt" A.Length; ++ii)
            for (int jj = 0; jj "lt" A[0].Length; ++jj)
              A[ii][jj] = v[ka++];

          // double[][] B = VecToMat(v, 1, v.Length);
          double[][] B = new double[1][];
          for (int ii = 0; ii "lt" 1; ++ii)
            B[ii] = new double[v.Length];
          int kb = 0;
          for (int ii = 0; ii "lt" B.Length; ++ii)
            for (int jj = 0; jj "lt" B[0].Length; ++jj)
              B[ii][jj] = v[kb++];

          double[][] AB = MatMultInternal(A, B);

          for (int ii = 0; ii "lt" h.Length; ++ii)
            for (int jj = 0; jj "lt" h[0].Length; ++jj)
              h[ii][jj] -= (2.0 / vvDot) * AB[ii][jj];

          // copy h[][] into lower right corner of H[][]
          int d = m - h.Length; // corr
          for (int ii = 0; ii "lt" h.Length; ++ii)
            for (int jj = 0; jj "lt" h[0].Length; ++jj)
              H[ii + d][jj + d] = h[ii][jj];

          QQ = MatMultInternal(QQ, H);
          RR = MatMultInternal(H, RR);

        } // i

        if (reduced == false)
        {
          Q = QQ; // working results into the out params
          R = RR;
          return;
        }
        //else if (reduced == true)
        {
          int qRows = QQ.Length; int qCols = QQ[0].Length;
          int rRows = RR.Length; int rCols = RR[0].Length;
          // assumes m "gte" n !!

          // square-up R
          int dim = Math.Min(rRows, rCols); // always n
          // double[][] Rsquared = MatMake(dim, dim);
          double[][] Rsquared = new double[dim][];
          for (int ii = 0; ii "lt" dim; ++ii)
            Rsquared[ii] = new double[dim];
          for (int i = 0; i "lt" dim; ++i)
            for (int j = 0; j "lt" dim; ++j)
              Rsquared[i][j] = RR[i][j];

          // Q needs same number columns as R
          // so that inv(R) * trans(Q) works
          // double[][] Qtrimmed = MatMake(qRows, dim);
          double[][] Qtrimmed = new double[qRows][];
          for (int ii = 0; ii "lt" qRows; ++ii)
            Qtrimmed[ii] = new double[dim];
          for (int i = 0; i "lt" qRows; ++i)
            for (int j = 0; j "lt" dim; ++j)
              Qtrimmed[i][j] = QQ[i][j];

          Q = Qtrimmed;
          R = Rsquared;
          return;
        }

        // --------------------------------------------------
        // nested helper MatMultInternal
        // --------------------------------------------------

        static double[][] MatMultInternal(double[][] A,
          double[][] B)
        {
          int aRows = A.Length;
          int aCols = A[0].Length;
          int bRows = B.Length;
          int bCols = B[0].Length;
          if (aCols != bRows)
            throw new Exception("Non-conformable matrices");

          // double[][] result = MatMake(aRows, bCols);
          double[][] result = new double[aRows][];
          for (int i = 0; i "lt" aRows; ++i)
            result[i] = new double[bCols];

          for (int i = 0; i "lt" aRows; ++i) // each row of A
            for (int j = 0; j "lt" bCols; ++j) // each col of B
              for (int k = 0; k "lt" aCols; ++k)
                result[i][j] += A[i][k] * B[k][j];

          return result;
        }

      } // MatDecompQR()

      static double[][] MatInverseUpperTri(double[][] U)
      {
        int n = U.Length;  // must be square matrix
        double[][] result = MatIdentity(n);

        for (int k = 0; k "lt" n; ++k)
        {
          for (int j = 0; j "lt" n; ++j)
          {
            for (int i = 0; i "lt" k; ++i)
            {
              result[j][k] -= result[j][i] * U[i][k];
            }
            result[j][k] /= (U[k][k] + 1.0e-8); // no 0
          }
        }
        return result;
      }

      static double[][] MatTranspose(double[][] m)
      {
        int nr = m.Length;
        int nc = m[0].Length;
        double[][] result = MatMake(nc, nr);  // note
        for (int i = 0; i "lt" nr; ++i)
          for (int j = 0; j "lt" nc; ++j)
            result[j][i] = m[i][j];
        return result;
      }

      static double[][] MatMake(int nRows, int nCols)
      {
        double[][] result = new double[nRows][];
        for (int i = 0; i "lt" nRows; ++i)
          result[i] = new double[nCols];
        return result;
      }

      static double[][] MatIdentity(int n)
      {
        double[][] result = MatMake(n, n);
        for (int i = 0; i "lt" n; ++i)
          result[i][i] = 1.0;
        return result;
      }
    } // MatInverseQR

    // ======================================================
    // 3. SVD - Jacobi
    // ======================================================

    static double[][] MatInverseSVD(double[][] M)
    {
      // SVD Jacobi algorithm
      // A = U * S * Vh
      // inv(A) = tr(Vh) * inv(S) * tr(U)
      double[][] U; double[][] Vh; double[] s;
      MatDecomposeSVD(M, out U, out Vh, out s);

      // TODO: check if determinant is zero (no inverse)
      // convert s vector to Sinv matrix
      int n = M.Length;
      double[][] Sinv = MatMake(n, n);
      for (int i = 0; i "lt" n; ++i)
        Sinv[i][i] = 1.0 / s[i];  // check s[i] != 0

      double[][] V = MatTranspose(Vh);
      double[][] Uh = MatTranspose(U);
      double[][] result = MatProduct(V,
        MatProduct(Sinv, Uh));
      return result;

      // ----------------------------------------------------

      static void MatDecomposeSVD(double[][] mat,
        out double[][] U, out double[][] Vh,
        out double[] s)
      {
        // assumes source matrix is square
        // see github.com/ampl/gsl/blob/master/linalg/svd.c 
        // "full_matrices=False" version
        double EPSILON = 1.0e-15;

        double[][] A = MatCopy(mat); // working U
        int m = A.Length;
        int n = A[0].Length;  // check m == n
        double[][] Q = MatIdentity(n); // working V
        double[] t = new double[n];  // working s

        int ct = 1;  // rotation counter
        int pass = 0;
        double tol = 10 * n * EPSILON; // heuristic

        int passMax = 5 * n;
        if (passMax "lt" 15) passMax = 15; // heuristic

        // save the column error estimates
        for (int j = 0; j "lt" n; ++j)
        {
          double[] cj = MatGetColumn(A, j);
          double sj = VecNorm(cj);
          t[j] = EPSILON * sj;
        }

        while (ct "gt" 0 && pass "lte" passMax)
        {
          ct = n * (n - 1) / 2;  // rotation counter
          for (int j = 0; j "lt" n - 1; ++j)
          {
            for (int k = j + 1; k "lt" n; ++k)
            {
              double sin; double cos;

              double[] cj = MatGetColumn(A, j);
              double[] ck = MatGetColumn(A, k);

              double p = 2.0 * VecDot(cj, ck);
              double a = VecNorm(cj);
              double b = VecNorm(ck);

              double q = a * a - b * b;
              double v = Hypot(p, q);

              double errorA = t[j];
              double errorB = t[k];

              bool sorted = false;
              if (a "gte" b) sorted = true;

              bool orthog = false;
              if (Math.Abs(p) "lte"
                tol * (a * b)) orthog = true;

              bool badA = false;
              if (a "lt" errorA) badA = true;
              bool badB = false;
              if (b "lt" errorB) badB = true;

              if (sorted == true && (orthog == true ||
                badA == true || badB == true))
              {
                --ct;
                continue;
              }

              // compute rotation angles
              if (v == 0 || sorted == false)
              {
                cos = 0.0;
                sin = 1.0;
              }
              else
              {
                cos = Math.Sqrt((v + q) / (2.0 * v));
                sin = p / (2.0 * v * cos);
              }

              // apply rotation to A (U)
              for (int i = 0; i "lt" m; ++i)
              {
                double Aik = A[i][k];
                double Aij = A[i][j];
                A[i][j] = Aij * cos + Aik * sin;
                A[i][k] = -Aij * sin + Aik * cos;
              }

              // update singular values
              t[j] = Math.Abs(cos) * errorA +
                Math.Abs(sin) * errorB;
              t[k] = Math.Abs(sin) * errorA +
                Math.Abs(cos) * errorB;

              // apply rotation to Q (V)
              for (int i = 0; i "lt" n; ++i)
              {
                double Qij = Q[i][j];
                double Qik = Q[i][k];
                Q[i][j] = Qij * cos + Qik * sin;
                Q[i][k] = -Qij * sin + Qik * cos;
              } // i
            } // k
          } // j

          ++pass;
        } // while

        //  compute singular values
        double prevNorm = -1.0;
        for (int j = 0; j "lt" n; ++j)
        {
          double[] column = MatGetColumn(A, j);
          double norm = VecNorm(column);

          // check if singular value is zero
          if (norm == 0.0 || prevNorm == 0.0
            || (j "gt" 0 && norm "lte" tol * prevNorm))
          {
            t[j] = 0.0;
            for (int i = 0; i "lt" m; ++i)
              A[i][j] = 0.0;
            prevNorm = 0.0;
          }
          else
          {
            t[j] = norm;
            for (int i = 0; i "lt" m; ++i)
              A[i][j] = A[i][j] * 1.0 / norm;
            prevNorm = norm;
          }
        }

        if (ct "gt" 0)
        {
          Console.WriteLine("Jacobi iterations did not" +
            " converge");
        }

        U = A;
        Vh = MatTranspose(Q);
        s = t;

        // assume M is square so no need
        // to sync with default np.linalg.svd() shapes:
        // if (m "lt" n)
        // {
        //   U = MatExtractFirstColumns(U, m);
        //   s = VecExtractFirst(s, m);
        //   Vh = MatExtractFirstRows(Vh, m);
        // }

      } // MatDecomposeSVD

      // helpers for MatDecomposeSVD():
      //
      // MatMake(), MatCopy(), MatIdentity(),
      // MatGetColumn(), MatExtractFirstColumns(),
      // MatExtractFirstRows(), MatTranspose(),
      // MatProduct(), VecNorm(), VecDot(),
      // Hypot(), VecExtractFirst()
      //

      static double[][] MatMake(int nr, int nc)
      {
        double[][] result = new double[nr][];
        for (int i = 0; i "lt" nr; ++i)
          result[i] = new double[nc];
        return result;
      }

      static double[][] MatCopy(double[][] M)
      {
        int nr = M.Length; int nc = M[0].Length;
        double[][] result = MatMake(nr, nc);
        for (int i = 0; i "lt" nr; ++i)
          for (int j = 0; j "lt" nc; ++j)
            result[i][j] = M[i][j];
        return result;
      }

      static double[][] MatIdentity(int n)
      {
        double[][] result = MatMake(n, n);
        for (int i = 0; i "lt" n; ++i)
          result[i][i] = 1.0;
        return result;
      }

      static double[] MatGetColumn(double[][] M, int j)
      {
        int nRows = M.Length;
        double[] result = new double[nRows];
        for (int i = 0; i "lt" nRows; ++i)
          result[i] = M[i][j];
        return result;
      }

      static double[][] MatExtractFirstColumns(double[][] M,
        int n)
      {
        int nRows = M.Length;
        // int nCols = src[0].Length;
        double[][] result = MatMake(nRows, n);
        for (int i = 0; i "lt" nRows; ++i)
          for (int j = 0; j "lt" n; ++j)
            result[i][j] = M[i][j];
        return result;
      }

      static double[][] MatExtractFirstRows(double[][] M,
        int n)
      {
        // int nRows = src.Length;
        int nCols = M[0].Length;
        double[][] result = MatMake(n, nCols);
        for (int i = 0; i "lt" n; ++i)
          for (int j = 0; j "lt" nCols; ++j)
            result[i][j] = M[i][j];
        return result;
      }

      static double[][] MatTranspose(double[][] M)
      {
        int nr = M.Length;
        int nc = M[0].Length;
        double[][] result = MatMake(nc, nr);
        for (int i = 0; i "lt" nr; ++i)
          for (int j = 0; j "lt" nc; ++j)
            result[j][i] = M[i][j];
        return result;
      }

      static double[][] MatProduct(double[][] A,
        double[][] B)
      {
        int aRows = A.Length;
        int aCols = A[0].Length;
        int bRows = B.Length;
        int bCols = B[0].Length;
        if (aCols != bRows)
          throw new Exception("Non-conformable matrices");

        double[][] result = MatMake(aRows, bCols);

        for (int i = 0; i "lt" aRows; ++i)
          for (int j = 0; j "lt" bCols; ++j)
            for (int k = 0; k "lt" aCols; ++k)
              result[i][j] += A[i][k] * B[k][j];

        return result;
      }

      static double VecNorm(double[] vec)
      {
        double sum = 0.0;
        int n = vec.Length;
        for (int i = 0; i "lt" n; ++i)
          sum += vec[i] * vec[i];
        return Math.Sqrt(sum);
      }

      static double VecDot(double[] v1, double[] v2)
      {
        int n = v1.Length;
        double sum = 0.0;
        for (int i = 0; i "lt" n; ++i)
          sum += v1[i] * v2[i];
        return sum;
      }

      static double Hypot(double x, double y)
      {
        // fancy sqrt(x^2 + y^2)
        double xabs = Math.Abs(x);
        double yabs = Math.Abs(y);
        double min, max;

        if (xabs "lt" yabs)
        {
          min = xabs; max = yabs;
        }
        else
        {
          min = yabs; max = xabs;
        }

        if (min == 0)
          return max;

        double u = min / max;
        return max * Math.Sqrt(1 + u * u);
      }

      static double[] VecExtractFirst(double[] vec, int n)
      {
        double[] result = new double[n];
        for (int i = 0; i "lt" n; ++i)
          result[i] = vec[i];
        return result;
      }

    } // MatInverseSVD

    // ======================================================
    // 4. Newton - Pan
    // ======================================================

    static double[][] MatInverseNewton(double[][] M,
      int maxIter = 1000, double epsilon = 1.0e-8)
    {
      // Newton's method with Pan initialization
      // X_k+1 = X_k * (2I - A*X_k)

      int n = M.Length; // must be square martix

      double[][] Xprev = MatStart(M);  // Pan algorithm
      double[][] Xnew = MatMake(n, n, 0.0);
      double[][] I = MatIdentity(n);
      double[][] I2 = MatScalarMult(I, 2.0);

      int iter = 0;
      while (iter "lt" maxIter)
      {
        Xnew =
          MatProduct(Xprev, MatSubtract(I2,
          MatProduct(M, Xprev)));
        Xprev = MatCopyOf(Xnew);

        if (iter % 10 == 0)
        {
          double[][] check = MatProduct(M, Xnew); // 
          if (MatAreEqual(check, I, epsilon) == true)
            return Xnew;
        }

        ++iter;
      } // while
      Console.WriteLine("WARN: no converge ");
      return Xnew;

      // ----------------------------------------------------
      // nested helpers: MatMake(), MatStart(), MatCopyOf(),
      // MatTranspose(), MatAreEqual(), MatProduct(),
      // MatSubtract(), MatIdentity(), MatScalarMult()
      // ----------------------------------------------------

      static double[][] MatMake(int nRows, int nCols,
        double v)
      {
        double[][] result = new double[nRows][];
        for (int i = 0; i "lt" nRows; ++i)
          result[i] = new double[nCols];
        for (int i = 0; i "lt" nRows; ++i)
          for (int j = 0; j "lt" nCols; ++j)
            result[i][j] = v;
        return result;
      }

      // ----------------------------------------------------

      static double[][] MatStart(double[][] m)
      {
        int n = m.Length;
        double maxRowSum = 0.0;
        double maxColSum = 0.0;
        for (int i = 0; i "lt" n; ++i)
        {
          double rowSum = 0.0;
          for (int j = 0; j "lt" n; ++j)
            rowSum += Math.Abs(m[i][j]);

          if (rowSum "gt" maxRowSum)
            maxRowSum = rowSum;
        }
        for (int j = 0; j "lt" n; ++j)
        {
          double colSum = 0.0;
          for (int i = 0; i "lt" n; ++i)
            colSum += Math.Abs(m[i][j]);
          if (colSum "gt" maxColSum)
            maxColSum = colSum;
        }

        double[][] result = MatTranspose(m);
        double t = 1.0 / (maxRowSum * maxRowSum + 1.0e-8);
        for (int i = 0; i "lt" m.Length; ++i)
          for (int j = 0; j "lt" m.Length; ++j)
            result[i][j] *= t;
        return result;

      }

      // ----------------------------------------------------

      static double[][] MatTranspose(double[][] m)
      {
        int nr = m.Length; int nc = m[0].Length;
        double[][] result = MatMake(nc, nr, 0.0);  // note
        for (int i = 0; i "lt" nr; ++i)
          for (int j = 0; j "lt" nc; ++j)
            result[j][i] = m[i][j];
        return result;
      }

      // ------------------------------------------------------

      static double[][] MatCopyOf(double[][] m)
      {
        int nRows = m.Length; int nCols = m[0].Length;
        double[][] result = MatMake(nRows, nCols, 0.0);
        for (int i = 0; i "lt" nRows; ++i)
          for (int j = 0; j "lt" nCols; ++j)
            result[i][j] = m[i][j];
        return result;
      }

      // ------------------------------------------------------

      static bool MatAreEqual(double[][] matA,
       double[][] matB, double eps)
      {
        int nr = matA.Length; int nc = matB[0].Length;
        for (int i = 0; i "lt" nr; ++i)
          for (int j = 0; j "lt" nc; ++j)
            if (Math.Abs(matA[i][j] - matB[i][j]) "gt" eps)
              return false;
        return true;
      }

      // ----------------------------------------------------

      static double[][] MatProduct(double[][] matA,
      double[][] matB)
      {
        int aRows = matA.Length;
        int aCols = matA[0].Length;
        int bRows = matB.Length;
        int bCols = matB[0].Length;
        if (aCols != bRows)
          throw new Exception("Non-conformable matrices");

        double[][] result = MatMake(aRows, bCols, 0.0);

        for (int i = 0; i "lt" aRows; ++i) // each row of A
          for (int j = 0; j "lt" bCols; ++j) // each col of B
            for (int k = 0; k "lt" aCols; ++k)
              result[i][j] += matA[i][k] * matB[k][j];

        return result;
      }

      // ------------------------------------------------------

      static double[][] MatSubtract(double[][] matA,
        double[][] matB)
      {
        // matA - matB
        int nRows = matA.Length; int nCols = matA[0].Length;
        double[][] result = MatMake(nRows, nCols, 0.0);
        for (int i = 0; i "lt" nRows; ++i)
          for (int j = 0; j "lt" nCols; ++j)
            result[i][j] = matA[i][j] - matB[i][j];
        return result;
      }

      // ----------------------------------------------------

      static double[][] MatIdentity(int n)
      {
        double[][] result = MatMake(n, n, 0.0);
        for (int i = 0; i "lt" n; ++i)
          result[i][i] = 1.0;
        return result;
      }

      // ----------------------------------------------------

      static double[][] MatScalarMult(double[][] m, double u)
      {
        int nRows = m.Length; int nCols = m[0].Length;
        double[][] result = MatMake(nRows, nCols, 0.0);
        for (int i = 0; i "lt" nRows; ++i)
          for (int j = 0; j "lt" nCols; ++j)
            result[i][j] = u * m[i][j];
        return result;
      }

    } // MatInverseNewton()

    // ======================================================
    // 5. Cholesky - Banachiewicz
    // ======================================================

    static double[][] MatInverseCholesky(double[][] M)
    {
      double[][] L = MatDecompCholesky(M);
      double[][] result = MatInverseFromCholesky(L);
      return result;

      // ----------------------------------------------------
      // helpers: MatDecompCholesky,
      // MatInverseFromCholesky, MatIdentity, MatMake
      // ----------------------------------------------------

      static double[][] MatDecompCholesky(double[][] m)
      {
        // Cholesky decomposition (Banachiewicz algorithm)
        // m is square, symmetric, positive definite
        int n = m.Length;
        double[][] result = MatMake(n, n);  // all 0.0
        for (int i = 0; i "lt" n; ++i)
        {
          for (int j = 0; j "lte" i; ++j)
          {
            double sum = 0.0;
            for (int k = 0; k "lt" j; ++k)
              sum += result[i][k] * result[j][k];
            if (i == j)
            {
              double tmp = m[i][i] - sum;
              if (tmp "lt" 0.0)
                throw new
                  Exception("MatDecompCholesky fatal");
              result[i][j] = Math.Sqrt(tmp);
            }
            else
            {
              if (result[j][j] == 0.0)
                throw new
                  Exception("MatDecompCholesky fatal ");
              result[i][j] =
                (1.0 / result[j][j] * (m[i][j] - sum));
            }
          } // j
        } // i
        return result;
      } // MatDecompCholesky

      // ----------------------------------------------------

      static double[][] MatInverseFromCholesky(double[][] L)
      {
        // L is a lower triangular result of Cholesky decomp
        // direct version
        int n = L.Length;
        double[][] result = MatIdentity(n);

        for (int k = 0; k "lt" n; ++k)
        {
          for (int j = 0; j "lt" n; j++)
          {
            for (int i = 0; i "lt" k; i++)
            {
              result[k][j] -= result[i][j] * L[k][i];
            }
            result[k][j] /= L[k][k];
          }
        }

        for (int k = n - 1; k "gte" 0; --k)
        {
          for (int j = 0; j "lt" n; j++)
          {
            for (int i = k + 1; i "lt" n; i++)
            {
              result[k][j] -= result[i][j] * L[i][k];
            }
            result[k][j] /= L[k][k];
          }
        }
        return result;
      }

      static double[][] MatIdentity(int n)
      {
        double[][] result = MatMake(n, n);
        for (int i = 0; i "lt" n; ++i)
          result[i][i] = 1.0;
        return result;
      }

      static double[][] MatMake(int rows, int cols)
      {
        double[][] result = new double[rows][];
        for (int i = 0; i "lt" rows; ++i)
          result[i] = new double[cols];
        return result;
      }

    } // MatInverseCholesky

    // ======================================================

  } // Program

} // ns
This entry was posted in Machine Learning. Bookmark the permalink.

Leave a Reply