Inverting Small Matrices Using C#

Many classical machine learning algorithms use matrix inversion, but inverting a matrix is a very difficult problem. I have a personal C# library of matrix functions, including a MatInverse() function.

My MatInverse() function uses Crout’s decomposition, which works for any size square matrix. However, decomposition is a bit of overkill if the source matrix is 2×2 or 3×3. For those special cases, it’s easier to implement a direct solution. If the source matrix is 4×4, a direct solution is possible but a bit ugly. See the demo code below.

The 2×2 case is:

public static double[][] MatInverse2x2(double[][] m)
{
  double[][] result = MatCreate(2, 2);
  double det = (m[0][0] * m[1][1]) -
    (m[0][1] * m[1][0]);  // determinant

  result[0][0] = m[1][1] / det;
  result[0][1] = -1 * m[0][1] / det;
  result[1][0] = -1 * m[1][0] / det;
  result[1][1] = m[0][0] / det;

  return result;
}

where

public static double[][] MatCreate(int rows, int cols)
{
  double[][] result = new double[rows][];
  for (int i = 0; i "lt"; rows; ++i) // "less-than"
    result[i] = new double[cols];
  return result;
}

I rarely include the special case matrix inverse functions in my programs because for the problems I work with, 2×2 and 3×3 matrices are somewhat rare. The 4×4 case is more common but direct computation of the inverse has a lot of code.



In aircraft models, the most common small scales I built when I was a young man were 1/72, 1/48, 1/32. Here are three examples of beautiful WWII airplane models I found on the Internet. Left: A Bell P-39 Airacobra in 1/72 scale. I loved the mid-engine design and tricycle landing gear. Center: A North American P-51 Mustang in 1/32 scale. I loved the ventral air scoop and raised bubble canopy. Right: A Vought F4U Corsair in 1/32 scale. I loved the inverted gull wing and short rear fuselage.


Demo program. Replace “lt”, “gt”, “lte”, “gte” with Boolean operator symbols.

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

      double[][] A = Utils.MatRandom(2, 2, -1.0, 1.0, 0);
      Console.WriteLine("\nSource matrix: ");
      Utils.MatShow(A, 4, 8);

      double[][] Ainv1 = Utils.MatInverse(A);
      Console.WriteLine("\nInverted using decomposition: ");
      Utils.MatShow(Ainv1, 8, 12);

      double[][] Ainv2 = Utils.MatInverse2x2(A);
      Console.WriteLine("\nInverted using 2x2 specific: ");
      Utils.MatShow(Ainv2, 8, 12);

      Console.WriteLine("---------------------");

      double[][] B = Utils.MatRandom(3, 3, -1.0, 1.0, 1);
      Console.WriteLine("\nSource matrix: ");
      Utils.MatShow(B, 4, 8);

      double[][] Binv1 = Utils.MatInverse(B);
      Console.WriteLine("\nInverted using decomposition: ");
      Utils.MatShow(Binv1, 8, 12);

      double[][] Binv2 = Utils.MatInverse3x3(B);
      Console.WriteLine("\nInverted using 3x3 specific: ");
      Utils.MatShow(Binv2, 8, 12);

      Console.WriteLine("---------------------");

      double[][] C = Utils.MatRandom(4, 4, -1.0, 1.0, 2);
      Console.WriteLine("\nSource matrix: ");
      Utils.MatShow(C, 4, 8);

      double[][] Cinv1 = Utils.MatInverse(C);
      Console.WriteLine("\nInverted using decomposition: ");
      Utils.MatShow(Cinv1, 8, 12);

      double[][] Cinv2 = Utils.MatInverse4x4(C);
      Console.WriteLine("\nInverted using 4x4 specific: ");
      Utils.MatShow(Cinv2, 8, 12);

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

  } // Program

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

    public static double[][] MatRandom(int rows, int cols,
      double lo, double hi, int seed)
    {
      Random rnd = new Random(seed);
      double[][] result = MatCreate(rows, cols);
      for (int i = 0; i "lt" rows; ++i)
        for (int j = 0; j "lt" cols; ++j)
          result[i][j] = (hi - lo) * rnd.NextDouble() + lo;
      return result;
    }

    public static double[][] MatInverse(double[][] m)
    {
      // assumes determinant is not 0
      // that is, the matrix does have an inverse
      int n = m.Length;
      double[][] result = MatCreate(n, n); // make a copy
      for (int i = 0; i "lt" n; ++i)
        for (int j = 0; j "lt" n; ++j)
          result[i][j] = m[i][j];

      double[][] lum; // combined lower & upper
      int[] perm;  // out parameter
      MatDecompose(m, out lum, out perm);  // ignore return

      double[] b = new double[n];
      for (int i = 0; i "lt" n; ++i)
      {
        for (int j = 0; j "lt" n; ++j)
          if (i == perm[j])
            b[j] = 1.0;
          else
            b[j] = 0.0;

        double[] x = Reduce(lum, b); // 
        for (int j = 0; j "lt" n; ++j)
          result[j][i] = x[j];
      }
      return result;
    }

    static int MatDecompose(double[][] m,
      out double[][] lum, out int[] perm)
    {
      // Crout's LU decomposition for matrix determinant
      // and inverse.
      // stores combined lower & 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)

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

      // make a copy of m[][] into result lu[][]
      lum = MatCreate(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) // note n-1 
      {
        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
    } // MatDecompose

    static double[] Reduce(double[][] luMatrix,
      double[] b) // helper
    {
      int n = luMatrix.Length;
      double[] x = new double[n];
      //b.CopyTo(x, 0);
      for (int i = 0; i "lt" n; ++i)
        x[i] = b[i];

      for (int i = 1; i "lt" n; ++i)
      {
        double sum = x[i];
        for (int j = 0; j "lt" i; ++j)
          sum -= luMatrix[i][j] * x[j];
        x[i] = sum;
      }

      x[n - 1] /= luMatrix[n - 1][n - 1];
      for (int i = n - 2; i "gte" 0; --i)
      {
        double sum = x[i];
        for (int j = i + 1; j "lt" n; ++j)
          sum -= luMatrix[i][j] * x[j];
        x[i] = sum / luMatrix[i][i];
      }

      return x;
    } // Reduce

    //public static double MatDeterminant(double[][] m)
    //{
    //  double[][] lum;
    //  int[] perm;
    //  double result = MatDecompose(m, out lum, out perm);
    //  for (int i = 0; i "lt" lum.Length; ++i)
    //    result *= lum[i][i];
    //  return result;
    //}

    // -------

    public static double[][] MatInverse2x2(double[][] m)
    {
      double[][] result = MatCreate(2, 2);
      double det = (m[0][0] * m[1][1]) - 
        (m[0][1] * m[1][0]);  // determinant
      result[0][0] = m[1][1] / det;
      result[0][1] = -1 * m[0][1] / det;
      result[1][0] = -1 * m[1][0] / det;
      result[1][1] = m[0][0] / det;

      return result;
    }

    public static double[][] MatInverse3x3(double[][] m)
    {
      // assumes determinant is not 0
      // that is, the matrix does have an inverse
      double[][] result = MatCreate(3, 3);

      double det = (m[0][0] * m[1][1] * m[2][2]) +
        (m[0][1] * m[1][2] * m[2][0]) +
        (m[0][2] * m[1][0] * m[2][1]) -
        (m[0][2] * m[1][1] * m[2][0]) -
        (m[0][1] * m[1][0] * m[2][2]) -
        (m[0][0] * m[1][2] * m[2][1]);  // determinant


      result[0][0] = 1 * (m[1][1] * m[2][2] - m[1][2] * m[2][1]);
      result[0][1] = -1 * (m[0][1] * m[2][2] - m[0][2] * m[2][1]);
      result[0][2] = 1 * (m[0][1] * m[1][2] - m[0][2] * m[1][1]);

      result[1][0] = -1 * (m[1][0] * m[2][2] - m[1][2] * m[2][0]);
      result[1][1] = 1 * (m[0][0] * m[2][2] - m[0][2] * m[2][0]);
      result[1][2] = -1 * (m[0][0] * m[1][2] - m[0][2] * m[1][0]);

      result[2][0] = 1 * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
      result[2][1] = -1 * (m[0][0] * m[2][1] - m[0][1] * m[2][0]);
      result[2][2] = 1 * (m[0][0] * m[1][1] - m[0][1] * m[1][0]);

      for (int i = 0; i "lt" 3; ++i)
        for (int j = 0; j "lt" 3; ++j)
          result[i][j] /= det;

      return result;
    } // 3x3

    public static double[][] MatInverse4x4(double[][] m)
    {
      // assumes determinant is not 0
      // that is, the matrix does have an inverse
      double[][] result = MatCreate(4, 4);

      // double det =
      //m[0][0] * m[1][1] * m[2][2] * m[3][3] +
      //m[0][0] * m[1][2] * m[2][3] * m[3][1] +
      //m[0][0] * m[1][3] * m[2][1] * m[3][2] -
      //m[0][0] * m[1][3] * m[2][2] * m[3][1] -
      //m[0][0] * m[1][2] * m[2][1] * m[3][3] -
      //m[0][0] * m[1][1] * m[2][3] * m[3][2] -
      //m[0][1] * m[1][0] * m[2][2] * m[3][3] -
      //m[0][2] * m[1][0] * m[2][3] * m[3][1] -
      //m[0][3] * m[1][0] * m[2][1] * m[3][2] +
      //m[0][3] * m[1][0] * m[2][2] * m[3][1] +
      //m[0][2] * m[1][0] * m[2][1] * m[3][3] +
      //m[0][1] * m[1][0] * m[2][3] * m[3][2] +
      //m[0][1] * m[1][2] * m[2][0] * m[3][3] +
      //m[0][2] * m[1][3] * m[2][0] * m[3][1] +
      //m[0][3] * m[1][1] * m[2][0] * m[3][2] -
      //m[0][3] * m[1][2] * m[2][0] * m[3][1] -
      //m[0][2] * m[1][1] * m[2][0] * m[3][3] -
      //m[0][1] * m[1][3] * m[2][0] * m[3][2] -
      //m[0][1] * m[1][2] * m[2][3] * m[3][0] -
      //m[0][2] * m[1][3] * m[2][1] * m[3][0] -
      //m[0][3] * m[1][1] * m[2][2] * m[3][0] +
      //m[0][3] * m[1][2] * m[2][1] * m[3][0] +
      //m[0][2] * m[1][1] * m[2][3] * m[3][0] +
      //m[0][1] * m[1][3] * m[2][2] * m[3][0];

// slightly more efficient version
      double det =
m[0][0] * 
(m[1][1] * m[2][2] * m[3][3] +
m[1][2] * m[2][3] * m[3][1] +
m[1][3] * m[2][1] * m[3][2] -
m[1][3] * m[2][2] * m[3][1] -
m[1][2] * m[2][1] * m[3][3] -
m[1][1] * m[2][3] * m[3][2])
- m[1][0] *
(m[0][1]  * m[2][2] * m[3][3] +
m[0][2]  * m[2][3] * m[3][1] +
m[0][3]  * m[2][1] * m[3][2] -
m[0][3]  * m[2][2] * m[3][1] -
m[0][2]  * m[2][1] * m[3][3] -
m[0][1]  * m[2][3] * m[3][2])
+ m[2][0] *
(m[0][1] * m[1][2]  * m[3][3] +
m[0][2] * m[1][3]  * m[3][1] +
m[0][3] * m[1][1]  * m[3][2] -
m[0][3] * m[1][2]  * m[3][1] -
m[0][2] * m[1][1]  * m[3][3] -
m[0][1] * m[1][3]  * m[3][2])
- m[3][0] *
(m[0][1] * m[1][2] * m[2][3]  +
m[0][2] * m[1][3] * m[2][1]  +
m[0][3] * m[1][1] * m[2][2]  -
m[0][3] * m[1][2] * m[2][1]  -
m[0][2] * m[1][1] * m[2][3]  -
m[0][1] * m[1][3] * m[2][2]);

      result[0][0] = m[1][1] * m[2][2] * m[3][3] +
        m[1][2] * m[2][3] * m[3][1] +
        m[1][3] * m[2][1] * m[3][2] - 
        m[1][3] * m[2][2] * m[3][1] - 
        m[1][2] * m[2][1] * m[3][3] - 
        m[1][1] * m[2][3] * m[3][2];

      result[0][1] = -m[0][1] * m[2][2] * m[3][3] -
        m[0][2] * m[2][3] * m[3][1] -
        m[0][3] * m[2][1] * m[3][2] +
        m[0][3] * m[2][2] * m[3][1] +
        m[0][2] * m[2][1] * m[3][3] +
        m[0][1] * m[2][3] * m[3][2];

      result[0][2] = m[0][1] * m[1][2] * m[3][3] +
        m[0][2] * m[1][3] * m[3][1] +
        m[0][3] * m[1][1] * m[3][2] -
        m[0][3] * m[1][2] * m[3][1] -
        m[0][2] * m[1][1] * m[3][3] -
        m[0][1] * m[1][3] * m[3][2];

      result[0][3] = -m[0][1] * m[1][2] * m[2][3] -
        m[0][2] * m[1][3] * m[2][1] -
        m[0][3] * m[1][1] * m[2][2] +
        m[0][3] * m[1][2] * m[2][1] +
        m[0][2] * m[1][1] * m[2][3] +
        m[0][1] * m[1][3] * m[2][2];

      result[1][0] = -m[1][0] * m[2][2] * m[3][3] -
        m[1][2] * m[2][3] * m[3][0] - 
        m[1][3] * m[2][0] * m[3][2] + 
        m[1][3] * m[2][2] * m[3][0] +
        m[1][2] * m[2][0] * m[3][3] +
        m[1][0] * m[2][3] * m[3][2];

      result[1][1] = m[0][0] * m[2][2] * m[3][3] + 
        m[0][2] * m[2][3] * m[3][0] +
        m[0][3] * m[2][0] * m[3][2] -
        m[0][3] * m[2][2] * m[3][0] - 
        m[0][2] * m[2][0] * m[3][3] - 
        m[0][0] * m[2][3] * m[3][2];

      result[1][2] = -m[0][0] * m[1][2] * m[3][3] -
        m[0][2] * m[1][3] * m[3][0] - 
        m[0][3] * m[1][0] * m[3][2] +
        m[0][3] * m[1][2] * m[3][0] +
        m[0][2] * m[1][0] * m[3][3] +
        m[0][0] * m[1][3] * m[3][2];

      result[1][3] = m[0][0] * m[1][2] * m[2][3] +
        m[0][2] * m[1][3] * m[2][0] + 
        m[0][3] * m[1][0] * m[2][2] - 
        m[0][3] * m[1][2] * m[2][0] -
        m[0][2] * m[1][0] * m[2][3] - 
        m[0][0] * m[1][3] * m[2][2];

      result[2][0] = m[1][0] * m[2][1] * m[3][3] +
        m[1][1] * m[2][3] * m[3][0] +
        m[1][3] * m[2][0] * m[3][1] - 
        m[1][3] * m[2][1] * m[3][0] -
        m[1][1] * m[2][0] * m[3][3] -
        m[1][0] * m[2][3] * m[3][1];

      result[2][1] = -m[0][0] * m[2][1] * m[3][3] -
        m[0][1] * m[2][3] * m[3][0] - 
        m[0][3] * m[2][0] * m[3][1] + 
        m[0][3] * m[2][1] * m[3][0] +
        m[0][1] * m[2][0] * m[3][3] +
        m[0][0] * m[2][3] * m[3][1];

      result[2][2] = m[0][0] * m[1][1] * m[3][3] + 
        m[0][1] * m[1][3] * m[3][0] + 
        m[0][3] * m[1][0] * m[3][1] - 
        m[0][3] * m[1][1] * m[3][0] -
        m[0][1] * m[1][0] * m[3][3] -
        m[0][0] * m[1][3] * m[3][1];

      result[2][3] = -m[0][0] * m[1][1] * m[2][3] -
        m[0][1] * m[1][3] * m[2][0] -
        m[0][3] * m[1][0] * m[2][1] +
        m[0][3] * m[1][1] * m[2][0] +
        m[0][1] * m[1][0] * m[2][3] +
        m[0][0] * m[1][3] * m[2][1];

      result[3][0] = -m[1][0] * m[2][1] * m[3][2] -
        m[1][1] * m[2][2] * m[3][0] -
        m[1][2] * m[2][0] * m[3][1] +
        m[1][2] * m[2][1] * m[3][0] + 
        m[1][1] * m[2][0] * m[3][2] + 
        m[1][0] * m[2][2] * m[3][1];

      result[3][1] = m[0][0] * m[2][1] * m[3][2] +
        m[0][1] * m[2][2] * m[3][0] + 
        m[0][2] * m[2][0] * m[3][1] - 
        m[0][2] * m[2][1] * m[3][0] -
        m[0][1] * m[2][0] * m[3][2] -
        m[0][0] * m[2][2] * m[3][1];

      result[3][2] = -m[0][0] * m[1][1] * m[3][2] -
        m[0][1] * m[1][2] * m[3][0] - 
        m[0][2] * m[1][0] * m[3][1] +
        m[0][2] * m[1][1] * m[3][0] +
        m[0][1] * m[1][0] * m[3][2] +
        m[0][0] * m[1][2] * m[3][1];

      result[3][3] = m[0][0] * m[1][1] * m[2][2] +
        m[0][1] * m[1][2] * m[2][0] +
        m[0][2] * m[1][0] * m[2][1] - 
        m[0][2] * m[1][1] * m[2][0] -
        m[0][1] * m[1][0] * m[2][2] -
        m[0][0] * m[1][2] * m[2][1];

      for (int i = 0; i "lt" 4; ++i)
        for (int j = 0; j "lt" 4; ++j)
          result[i][j] /= det;

      return result;
    } // 4x4

    public 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];
          if (Math.Abs(v) "lt" 1.0e-8) v = 0.0; // hack
          Console.Write(v.ToString("F" +
            dec).PadLeft(wid));
        }
        Console.WriteLine("");
      }
    }
    
  } // class Utils

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