When a programmer deals with mathematics, one of the most common routines added to his/her math library is matrix inversion.

The following code implements a very efficient in-situ matrix inversion (meaning that no memory is allocated for another matrix to hold the result; the inverse of the matrix is built gradually in the matrix itself!).

The code is based on an algorithm published in Numerical Recipes.

The code can be put in a .vb file (e.g., MatrixInversion.vb).

 

Option Strict On

Option Explicit On

 

Imports System

 

''' <summary>

''' Return code of the in-situ matrix inversion routines.

''' </summary>

Public Enum MatrixInversionResult

   Success = 0

   InvalidArgument = 1

   NotSquare = 2

   Singular = 3

End Enum

 

Public Module MatrixInversion

 

   ' Machine epsilon for Double (2^-52).

   Private Const Eps As Double = 2.2204460492503131E-16

 

   ''' <summary>

   ''' Inverts a square matrix in situ (in place) by Gauss-Jordan elimination

   ''' with full pivoting. On success the input array holds the inverse.

   ''' </summary>

   ''' <param name="a">

   ''' Square, zero-based array, e.g. Dim a(n - 1, n - 1) As Double.

   ''' Overwritten by the inverse. Undefined if the routine fails.

   ''' </param>

   Public Function InvertInPlace(ByRef a(,) As Double) As MatrixInversionResult

      If a Is Nothing Then Return MatrixInversionResult.InvalidArgument

 

      Dim n As Integer = a.GetLength(0)

      If a.GetLength(1) <> n Then Return MatrixInversionResult.NotSquare

      If n < 1 Then Return MatrixInversionResult.InvalidArgument

 

      Return InvertCore(a, a.GetLowerBound(0), a.GetLowerBound(1), n)

   End Function

 

   ''' <summary>

   ''' Backwards-compatible entry point for code ported from VB6, where the

   ''' matrix is dimensioned (gDim, gDim) and only the subscripts 1..gDim are

   ''' used. Row 0 and column 0 are left untouched.

   ''' </summary>

   Public Function MatrixInvertGaussJordan(ByRef mMatrix1(,) As Double,

      ByVal gDim As Integer) As MatrixInversionResult

      If mMatrix1 Is Nothing Then Return MatrixInversionResult.InvalidArgument

      If gDim < 1 Then Return MatrixInversionResult.InvalidArgument

      If mMatrix1.GetUpperBound(0) < gDim OrElse mMatrix1.GetUpperBound(1) < gDim Then

         Return MatrixInversionResult.InvalidArgument

      End If

 

      Return InvertCore(mMatrix1, 1, 1, gDim)

   End Function

 

   ''' <summary>

   ''' Core routine. Inverts, in situ, the n-by-n block of "a" whose first

   ''' element is a(r0, c0). Only O(n) extra memory is used.

   ''' </summary>

   Private Function InvertCore(ByRef a(,) As Double,

      ByVal r0 As Integer,

      ByVal c0 As Integer,

      ByVal n As Integer) As MatrixInversionResult

 

      Dim indxc(n - 1) As Integer ' pivot bookkeeping, for the final unscrambling

      Dim indxr(n - 1) As Integer

      Dim ipiv(n - 1) As Integer ' 0 = column not yet used as pivot

 

      Dim i, j, k, l, ll As Integer

      Dim iRow, iCol As Integer

      Dim big, dum, pivInv As Double

 

      ' Largest magnitude in the matrix: used for a scale-aware singularity

      ' test. A fixed absolute threshold such as 1.0E-15 wrongly rejects

      ' well-conditioned matrices whose entries are simply small.

      Dim aMax As Double = 0.0

      For i = 0 To n - 1

         For j = 0 To n - 1

            Dim v As Double = a(r0 + i, c0 + j)

            If Double.IsNaN(v) OrElse Double.IsInfinity(v) Then

               Return MatrixInversionResult.InvalidArgument

            End If

            v = Math.Abs(v)

            If v > aMax Then aMax = v

         Next

      Next

      If aMax = 0.0 Then Return MatrixInversionResult.Singular

 

      Dim tol As Double = aMax * n * Eps

 

      For i = 0 To n - 1

 

         ' --- search the remaining submatrix for the largest pivot -------

         ' big, iRow and iCol MUST be reset on every pass; otherwise a stale

         ' pivot from the previous pass can be reused.

         big = -1.0

         iRow = -1

         iCol = -1

 

         For j = 0 To n - 1

            If ipiv(j) <> 1 Then

               For k = 0 To n - 1

                  If ipiv(k) = 0 Then

                     Dim v As Double = Math.Abs(a(r0 + j, c0 + k))

                     If v > big Then

                        big = v

                        iRow = j

                        iCol = k

                     End If

                  ElseIf ipiv(k) > 1 Then

                     Return MatrixInversionResult.Singular

                  End If

               Next

            End If

         Next

 

         If iCol < 0 OrElse big <= tol Then Return MatrixInversionResult.Singular

 

         ipiv(iCol) += 1

 

         ' --- interchange rows so that the pivot lands on the diagonal ---

         If iRow <> iCol Then

            For l = 0 To n - 1

               dum = a(r0 + iRow, c0 + l)

               a(r0 + iRow, c0 + l) = a(r0 + iCol, c0 + l)

               a(r0 + iCol, c0 + l) = dum

            Next

         End If

 

         indxr(i) = iRow

         indxc(i) = iCol

 

         ' After the interchange the pivot is a(iCol, iCol), NOT a(iRow, iCol).

         pivInv = 1.0 / a(r0 + iCol, c0 + iCol)

         a(r0 + iCol, c0 + iCol) = 1.0

         For l = 0 To n - 1

            a(r0 + iCol, c0 + l) *= pivInv

         Next

 

         ' --- reduce the remaining rows ---------------------------------

         For ll = 0 To n - 1

            If ll <> iCol Then

               dum = a(r0 + ll, c0 + iCol)

               If dum <> 0.0 Then

                  a(r0 + ll, c0 + iCol) = 0.0

                  For l = 0 To n - 1

                     a(r0 + ll, c0 + l) -= a(r0 + iCol, c0 + l) * dum

                  Next

               End If

            End If

         Next

      Next

 

      ' --- undo the column interchanges, in reverse order -----------------

      For l = n - 1 To 0 Step -1

         If indxr(l) <> indxc(l) Then

            For k = 0 To n - 1

               dum = a(r0 + k, c0 + indxr(l))

               a(r0 + k, c0 + indxr(l)) = a(r0 + k, c0 + indxc(l))

               a(r0 + k, c0 + indxc(l)) = dum

            Next

         End If

      Next

 

      Return MatrixInversionResult.Success

   End Function

 

   End Module