[C#] Matrix Multiplication Helper Class

AceInfinity

Emeritus, Contributor
Joined
Feb 21, 2012
Posts
1,728
Location
Canada
So I wrote this in a console application, but basically it's a class that provides methods to multiply a matrix by a matrix. This is very useful for linear algebra and I am just starting to apply it to a project that uses a Jacobi rotation method for dealing with a matrix.

Code:
private static void TestMatrixMultiply()
{
	MatrixTable matrix1 = new MatrixTable(
			new int[5,  4]
			{
				{ 1,  2,  3,  4},
				{ 5,  6,  7,  8},
				{ 9, 10, 11, 12},
				{13, 14, 15, 16},
				{17, 18, 19, 20}
			}
		);
	MatrixTable matrix2 = new MatrixTable(
			new int[4, 5]
			{
				{ 1,  2,  3,  4,  5},
				{ 6,  7,  8,  9, 10},
				{11, 12, 13, 14, 15},
				{16, 17, 18, 19, 20}
			}
		);

	MatrixTable matrixResult = matrix1 * matrix2;

	DisplayMatrix(matrixResult, "000");
}

private static void DisplayMatrix(MatrixTable table, string formatter)
{
	Console.WriteLine(table.TableDimensions.ToString() + "\n");

	for (int i = 0; i < table.Rows; i++)
	{
		List<int> data = new List<int>();
		for (int j = 0; j < table.Columns; j++)
		{
			data.Add(table[i, j]);
		}
		Console.WriteLine(string.Join(" ", data.Select(k => k.ToString(formatter))));
	}
}

public class MatrixTable
{
	private int[,] _table;

	public MatrixTable(Dimensions dimensions) : this(dimensions.Rows, dimensions.Columns) { }

	public MatrixTable(int rows, int columns) : this(new int[rows, columns]) { }

	public MatrixTable(int[,] table)
	{
		_table = table;
	}

	public int this[int row, int column]
	{
		get
		{
			return _table[row, column];
		}
		set
		{
			_table[row, column] = value;
		}
	}

	public Dimensions TableDimensions
	{
		get
		{
			return new Dimensions(_table.GetLength(0), _table.GetLength(1));
		}
	}

	public int Rows
	{
		get
		{
			return this.TableDimensions.Rows;
		}
	}

	public int Columns
	{
		get
		{
			return this.TableDimensions.Columns;
		}
	}

	private static Dimensions GetDestinationSize(MatrixTable table1, MatrixTable table2)
	{
		int rows;
		if (table1.Rows > table2.Rows)
		{
			rows = table1.Rows;
		}
		else
		{
			rows = table2.Rows;
		}

		int columns;
		if (table1.Columns > table2.Columns)
		{
			columns = table1.Columns;
		}
		else
		{
			columns = table2.Columns;
		}
		return new Dimensions(rows, columns);
	}

	public static MatrixTable operator *(MatrixTable table1, MatrixTable table2)
	{
		MatrixTable result = new MatrixTable(GetDestinationSize(table1, table2));
		for (int i = 0; i < table1.Rows; i++)
		{
			for (int j = 0; j < table2.Columns; j++)
			{
				for (int k = 0; k < table1.Columns; k++)
				{
					result[i, j] += table1[i, k] * table2[k, j];
				}
			}
		}
		return result;
	}
}

public struct Dimensions
{
	public Dimensions(int rows, int columns)
	{
		_rows = rows;
		_columns = columns;
	}
	private int _rows;
	public int Rows
	{
		get
		{
			return _rows;
		}
	}
	private int _columns;
	public int Columns
	{
		get
		{
			return _columns;
		}
	}

	public override string ToString()
	{
		return string.Format("Rows:{0}, Columns:{1}", this._rows, this._columns);
	}
}

EUhhr0t.png
 
Thanks Ace. I have a class I created that does Matrix operations to compute eigenvalues; it also included matrix multiplication, inverses, determinants, QR Decomposition, RREF, etc. I'd be happy to share it here once I find it and can provide some example code to use it... :-}
 
I only got into the multiplication portion because I needed that functionality for the Jacobian in some graphics manipulation that requires a good efficient method of manipulating the data for colors, along with other metric points simulation on a wireframe of a specific model. This rotational method I came up with fits the belt pretty well.

I have heard about inverses and determinants though, I never thought to expand my class further however. But it could be a good idea, since I have kept it pretty "open" to do so. It was a good opportunity to overload the * operator here, but I think I can overload some others for various reasons here too.
 
I have provided a method for defining an Identity matrix:
Code:
public static MatrixTable I(int size)
{
	MatrixTable table = new MatrixTable(size, size);
	for (int i = 0; i < table.Rows; i++)
	{
		for (int j = 0; j < table.Columns; j++)
		{
			table[i, j] = i == j ? 1 : 0;
		}
	}
	return table;
}

Trying to expand on this a bit by getting into determinants and inverses.
 
@Writhziden - For the inverse, did you write your own Fractional struct so that you weren't dealing with inaccurate decimals for fractions with repeating decimals or a ton of them? Or did you not get that far?
 
It depends on how you calculate the inverse. I used RREF to calculate the inverse of the matrix, and all my variables were doubles. What method are you using for your calculation?

Code:
void MyEigen::inverse(double* A, int matH)
{
  int i,j,k;
  double* B = new double[matH*matH];
  double* invA = new double[matH*matH];
  double* invB = new double[matH*matH];
  
  for(i=0; i<matH; i++)
  {
    for(j=0; j<matH; j++)
    {
      B[i*matH+j] = A[i*matH+j];
      if (i==j)
      {
        invA[i*matH+j] = 1.0;
        invB[i*matH+j] = 1.0;
      }
      else
      {
        invA[i*matH+j] = 0.0;
        invB[i*matH+j] = 0.0;
      }
    }
  }
  
  for(j=0; j<matH; j++)
  {
    for(i=j; i<matH; i++)
    {
      if(fabs(A[i*matH+j]) > fabs(B[j*matH+j]))
      {
        //printf("here\n");
        for(k=0; k<matH; k++)
        {   
          B[i*matH+k] = B[j*matH+k];          
          invB[i*matH+k] = invB[j*matH+k];
          B[j*matH+k] = A[i*matH+k];
          invB[j*matH+k] = invA[i*matH+k];
        }
        for(k=0; k<matH; k++)
        {   
          A[i*matH+k] = B[i*matH+k];       
          A[j*matH+k] = B[j*matH+k];
          invA[i*matH+k] = invB[i*matH+k];       
          invA[j*matH+k] = invB[j*matH+k];
        }
      }    
    }
  }
  
  for(i=0; i<matH; i++)
  {
    for(j=0; j<matH; j++)
    {
      for(k=0; k<matH; k++)
      {
        if(i==j)
        {
          A[j*matH+k] = B[j*matH+k] / B[j*matH+j];
          invA[j*matH+k] = invB[j*matH+k] / B[j*matH+j];
        }
        if(i!=j)
        {
          A[j*matH+k] = B[j*matH+k] - B[i*matH+k] * B[j*matH+i] / B[i*matH+i];
          invA[j*matH+k] = invB[j*matH+k] - invB[i*matH+k] * B[j*matH+i] / B[i*matH+i];
        }
      }
      for(k=0; k<matH; k++)
      {        
        B[j*matH+k] = A[j*matH+k];  
        invB[j*matH+k] = invA[j*matH+k];
      }
    }
  }
  for(i=0; i<matH; i++)
  {
    for(j=0; j<matH; j++)
    {
      A[i*matH+j] = invB[i*matH+j];
    }
  }
  delete[] B;
  delete[] invA;
  delete[] invB;
}
 
Last edited:
I could go with any method currently, as I have a method that can deal efficiently with fractions, however although I would like to avoid Fractions, I think it doesn't matter what you do, as a result matrix (if possible) might have a fraction of 1/3, which is 0.3^repeating... No datatype will be able to accurately display that.

The way I'm doing it, is I use the determinant and multiply it by a modified matrix by changing the signs, and swapping diagonals...

Here's the first video I found for the basis of what I've been going off of:
 
I will look into that, very much appreciated Writhziden! Thankyou.

I've already got my class extended to interface with the IEqualityComparer<T> interface, as well as the ICloneable interface... I've got more overloaded operators for ==, !=, addition and subtraction of matrixes, I've got identity matrix calculation, Transpose, matrix scaling, dimensions comparisons...

I just need to work with the Inverse, and Determinants so far... I haven't attempted Determinants yet, but it looks easy.
 
No problem.

Einstein notation and Levi-Civita symbols really helped me with my programming using tensors. It is much easier to see with the summation and dummy indices when setting up the for loops.

I never tried overloading any of the operators since I had to port this to Fortran and C++ and Fortran is a bit simpler than C++ is. Overloading would make matrix manipulations much easier, though.
 
Fortran from what I know is almost built for math. A friend of mine wanted me to try out Haskell because he said it was basically like programming with math equations lol. When I am done I will upload the full class implementation. I'm just finding various Matrix operations via Google to make sure that I don't miss any so perhaps I will miss a couple... As far as I can see though, I'm only missing 2. Tried various math websites to see what kind of algebra can be done with Matrixes, and I haven't found too much more.
 
If it helps, Ace, here is a copy of the index page of my linear algebra textbook - that should give you plenty of inspiration :) Give me a shout if you want any more detail on some of the topics and I'll either do my best to explain it, if we've covered it in lectures, or I'll send you a quick piccie of the page.
 

Attachments

  • Capture.PNG
    Capture.PNG
    138.6 KB · Views: 10
That's amazing :) This will undoubtedly help me expand my class. I should be able to find references to all of this stuff through google though. It's got to be out there somewhere for each of those terms if I don't know what it's referencing. I appreciate the time you took to do that though.

I'm just trying to make my class more complete. I was previously oblivious to some of the things that can be done with Matrix's until writhziden replied for the first time in this thread, but since then my class has began to grow pretty powerful. I've got a lot of stuff in it so far.
 
I had never once realized how much work it would be to make a fully capable and functional Matrix class before lol... Here is my progress so far though:

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class Matrix : EqualityComparer<Matrix>, ICloneable
{
	private decimal[,] _table;
	StringBuilder _tableString = new StringBuilder();

	// Constructors
	public Matrix(Dimensions dimensions) : this(dimensions.Rows, dimensions.Columns) { }

	public Matrix(int rows, int columns) : this(new decimal[rows, columns]) { }

	public Matrix(decimal[,] table)
	{
		_table = table;
	}

	// Indexing
	public decimal this[int row, int column]
	{
		get
		{
			return _table[row, column];
		}
		set
		{
			_table[row, column] = value;
		}
	}

	// Matrix Layout
	public Dimensions TableDimensions
	{
		get
		{
			return new Dimensions(_table.GetLength(0), _table.GetLength(1));
		}
	}

	public int Rows
	{
		get
		{
			return this.TableDimensions.Rows;
		}
	}

	public int Columns
	{
		get
		{
			return this.TableDimensions.Columns;
		}
	}

	public int Rank
	{
		get
		{
			return _table.Rank;
		}
	}

	public static Matrix DefaultComparer
	{
		get
		{
			return new Matrix(0, 0);
		}
	}

	/// <summary>
	/// Gets the destination size of the result matrix for a multiplication operation.
	/// </summary>
	private static Dimensions GetDestMultiplySize(Matrix table1, Matrix table2)
	{
		int rows;
		if (table1.Rows > table2.Rows)
		{
			rows = table1.Rows;
		}
		else
		{
			rows = table2.Rows;
		}

		int columns;
		if (table1.Columns > table2.Columns)
		{
			columns = table1.Columns;
		}
		else
		{
			columns = table2.Columns;
		}
		return new Dimensions(rows, columns);
	}

	/// <summary>
	/// Determine whether a matrix has the same dimensions as another.
	/// </summary>
	public static bool IsSameSize(Matrix matrix1, Matrix matrix2)
	{
		return matrix1.TableDimensions == matrix2.TableDimensions;
	}

	/// <summary>
	/// Determine whether a matrix is a square matrix.
	/// </summary>
	public static bool IsSquareMatrix(Matrix matrix)
	{
		return matrix.Rows == matrix.Columns;
	}

	/// <summary>
	/// Get identity matrix for certain size.
	/// </summary>
	public static Matrix I(int size)
	{
		Matrix table = new Matrix(size, size);
		for (int i = 0; i < table.Rows; i++)
		{
			for (int j = 0; j < table.Columns; j++)
			{
				table[i, j] = i == j ? 1 : 0;
			}
		}
		return table;
	}

	public static IEnumerable<Matrix> PermutationMatrices(Dimensions dimensions)
	{
		throw new NotImplementedException("Permutation Matrices calculation is not implemented yet.");
	}

	/// <summary>
	/// Scales the matrix by a specified multiplication factor.
	/// </summary>
	public static Matrix Scale(decimal scalarQuantity, Matrix table)
	{
		Matrix result = new Matrix(table.TableDimensions);
		for (int i = 0; i < table.Rows; i++)
		{
			for (int j = 0; j < table.Columns; j++)
			{
				result[i, j] = table[i, j] * scalarQuantity;
			}
		}
		return result;
	}

	/// <summary>
	/// Returns the transposed version of the input matrix.
	/// </summary>
	public static Matrix Transpose(Matrix table)
	{
		Matrix result = new Matrix(table.Columns, table.Rows);
		for (int i = 0; i < table.Columns; i++)
		{
			for (int j = 0; j < table.Rows; j++)
			{
				result[i, j] = table[j, i];
			}
		}
		return result;
	}

	/// <summary>
	/// Returns the transposed version of the input matrix.
	/// </summary>
	public static decimal Determinant(Matrix table)
	{
		if (!IsSquareMatrix(table))
		{
			throw new ArgumentException("Determinant can only be calculated with a square matrix.");
		}
		throw new NotImplementedException("Inverse is not implemented yet.");
	}

	/// <summary>
	/// Returns the trace of the input matrix.
	/// </summary>
	public static decimal Trace(Matrix table)
	{
		throw new NotImplementedException("Trace is not implemented yet.");
	}

	/// <summary>
	/// Returns the transposed version of the input matrix.
	/// </summary>
	public static Matrix Inverse(Matrix table)
	{
		if (!IsSquareMatrix(table))
		{
			throw new ArgumentException("Inverse can only be calculated with a square matrix.");
		}
		throw new NotImplementedException("Inverse is not implemented yet.");
	}

	public static Matrix Eigenvalues(Matrix table)
	{
		if (!IsSquareMatrix(table))
		{
			throw new ArgumentException("Eigenvalues can only be calculated with a square matrix.");
		}
		throw new NotImplementedException("Eigenvalues is not implemented yet.");
	}

	public static Matrix Eigenvectors(Matrix table)
	{
		if (!IsSquareMatrix(table))
		{
			throw new ArgumentException("Eigenvectors can only be calculated with a square matrix.");
		}
		throw new NotImplementedException("Eigenvectors is not implemented yet.");
	}

	// Operator Overloading
	public static Matrix operator +(Matrix table1, Matrix table2)
	{
		if (table1.TableDimensions != table2.TableDimensions)
		{
			throw new ArgumentException("Matrix sizes are not the same.");
		}
		Matrix result = new Matrix(table1.TableDimensions);
		for (int i = 0; i < table1.Rows; i++)
		{
			for (int j = 0; j < table2.Columns; j++)
			{
				result[i, j] = table1[i, j] + table2[i, j];
			}
		}
		return result;
	}

	public static Matrix operator -(Matrix table1, Matrix table2)
	{
		if (table1.TableDimensions != table2.TableDimensions)
		{
			throw new ArgumentException("Matrix sizes are not the same.");
		}
		Matrix result = new Matrix(table1.TableDimensions);
		for (int i = 0; i < table1.Rows; i++)
		{
			for (int j = 0; j < table2.Columns; j++)
			{
				result[i, j] = table1[i, j] - table2[i, j];
			}
		}
		return result;
	}

	public static Matrix operator *(Matrix table1, Matrix table2)
	{
		Matrix result = new Matrix(GetDestMultiplySize(table1, table2));
		for (int i = 0; i < table1.Rows; i++)
		{
			for (int j = 0; j < table2.Columns; j++)
			{
				for (int k = 0; k < table1.Columns; k++)
				{
					result[i, j] += table1[i, k] * table2[k, j];
				}
			}
		}
		return result;
	}

	public static bool operator ==(Matrix table1, Matrix table2)
	{
		if (!IsSameSize(table1, table2))
		{
			return false;
		}

		for (int i = 0; i < table1.Rows; i++)
		{
			for (int j = 0; j < table2.Columns; j++)
			{
				if (table1[i, j] != table2[i, j])
				{
					return false;
				}
			}
		}
		return true;
	}

	public static bool operator !=(Matrix table1, Matrix table2)
	{
		if (!IsSameSize(table1, table2))
		{
			return true;
		}

		for (int i = 0; i < table1.Rows; i++)
		{
			for (int j = 0; j < table2.Columns; j++)
			{
				if (table1[i, j] != table2[i, j])
				{
					return true;
				}
			}
		}
		return false;
	}

	public override string ToString()
	{
		return this.ToString(string.Empty);
	}

	public string ToString(string formatter)
	{
		_tableString.Clear();
		_tableString.AppendLine(this.TableDimensions.ToString() + "\n");

		for (int i = 0; i < this.Rows; i++)
		{
			List<decimal> data = new List<decimal>();
			for (int j = 0; j < this.Columns; j++)
			{
				data.Add(this[i, j]);
			}
			_tableString.AppendLine(string.Join(" ", data.Select(k => k.ToString(formatter))));
		}
		return _tableString.ToString();
	}

	public override bool Equals(object obj)
	{
		return base.Equals(obj);
	}

	public override int GetHashCode()
	{
		return _table.GetHashCode();
	}

	// ICloneable Implementation
	public object Clone()
	{
		return new Matrix(_table);
	}

	// EqualityComparer<Matrix> Class Inherit
	public override bool Equals(Matrix x, Matrix y)
	{
		return x == y;
	}

	public override int GetHashCode(Matrix obj)
	{
		return _table.GetHashCode();
	}
}

struct Dimensions
{
	// Constructor
	public Dimensions(int rows, int columns)
	{
		_rows = rows;
		_columns = columns;
	}

	// Properties
	private int _rows;
	public int Rows
	{
		get
		{
			return _rows;
		}
	}
	private int _columns;
	public int Columns
	{
		get
		{
			return _columns;
		}
	}

	// Operator Overloading
	public static bool operator ==(Dimensions dimensions1, Dimensions dimensions2)
	{
		return dimensions1.Rows == dimensions2.Rows && dimensions1.Columns == dimensions2.Columns;
	}

	public static bool operator !=(Dimensions dimensions1, Dimensions dimensions2)
	{
		return dimensions1.Rows != dimensions2.Rows || dimensions1.Columns != dimensions2.Columns;
	}

	// Overrides
	public override string ToString()
	{
		return string.Format("Rows:{0}, Columns:{1}", this._rows, this._columns);
	}

	public override bool Equals(object obj)
	{
		return base.Equals(obj);
	}

	public override int GetHashCode()
	{
		return base.GetHashCode();
	}
}

Right now I have some placeholders, but those methods don't do anything right now. On top of that, I am still missing a whole list of functionality for a Matrix it would seem. This is a project just by itself...

~400 lines of code right now, and not even probably 30% finished.
 
Well, I got a whole bunch of them added, but Inverse is still annoying. Taking the Gauss method, I need to think more about how I would determine what rows to switch, and what numbers to add/subtract, etc... To arrive with the Identity matrix on the left side of the adjoined matricies for A and I to retrieve A^-1 (inverse).

Approx. 100 more lines of code had been added from that previous code.
 
No problem, glad you found it useful. I wouldn't even know where to begin writing something like that! I don't think it would matter which rows you chose though. As long as you can whittle it down to a triangular (upper or lower) matrix (ie the determinant isn't 0) you can then begin to work back to the identity matrix on the right. You might get a few nasty numbers in your output but it should still work :)
 
Common practice is to do partial pivotting (full row swapping) to get the diagonals sorted from highest to lowest going down the diagonal from the top left diagonal. For each row, divide all elements by the diagnoal element so the diagonal element becomes 1.0 for easier transformation to the inverse matrix. You then need to subtract the above row multiplied by the current row elements divided by the above row elements from the current row to get rid of the lower diagnoal. Then subtract the row below multiplied by the current row elements divided by the below row elements from the current row to get rid of the upper diagonal.

Using the exact same fractions starting with an identity matrix and doing the same partial pivoting and subtractions with the same fractions for the multipliers by element, your identity matrix will be transformed into the inverse matrix.

Don't know if any of that made sense... It would be much easier with a blackboard to show you. ;-}


An Example:

Code:
| 5   9|       | 1  0|
|12   2|       | 0  1|

|12  2|       | 0  1|
| 5  9|       | 1  0|

| 1      1/6|    |0    1/12|
| 5/9      1|    |1/9      0|

|1             1/6|    |0               1/12|
|0        1 - 5/54|    |1/9-0*5/9    0-5/108|

|1         0|    |0-1/49           1/12 + 5/588|
|0     49/54|    |1/9                    -5/108|

|1         0|    |-1/49    9/98|
|0         1|    |6/49    -5/98|

Code:
inv(| 5   9|
    |12   2|)
= |-1/49    9/98|
  |6/49    -5/98|
 
Last edited:
2x2 is easy, 3x3 is simple, but for a 4x4 and above there seems to be based on no traditional logic. Just trial and error. There are tricks for 2x2 and 3x3, but from what I have read, nobody teaches for matrices that are larger than 3x3 a method that will always guarantee the inverse...

It would seem that I need to think about this recursively to solve subproblems of the bigger problem by solving inverses of 2x2 sub-matrix tables within a larger matrix itself, based on what I have seen.
 
Last edited:
Ace, the method I showed for 2x2 will work with any size, square matrix. See post #6 for the algorithm I developed to do the same steps in for loops for any matrix height matH. You just recursively update each row after getting rid of the upper and lower diagonal portions of it.

By the way, you should take the determinant of the matrix to make sure an inverse exists prior to trying to actually calculate the inverse.


 
Last edited:

Has Sysnative Forums helped you? Please consider donating to help us support the site!

Back
Top