The Matrix class provides a simpler way to interact with matrices in C#. It is basically a 2-dimensional array that utilizes an indexer to store and retrieve elements in a matrix object. In addition, operator overloads allows matrices to be treated in a more natural with performing elementary mathematical operations such as adding and subtracting matrices. You can find the Transpose, do Reduced Row Echelon Form, find the Inverse, etc.
See the class diagram for the list of features provided by this class.
/*********************************************
* Author Name : Surujlal 'Sparky' Dasrath *
* Date : August 17 2007 *
* Contact : sdasrath@gmail.com *
* *******************************************
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace Matrix
{//start namespace
// Version: 1.0.0
// Member List:
// [1] Types:
// Matrix
// MatrixException
// [2] Private Fields:
// row - intialize number of rows in a matrix object
// columns - intialize number of columns in a matrix object
// [3] Indexer:
// this[row, column] - retreive/insert elements into a matrix object
// [3] Accessor Methods/Properties:
// Row - read only property which returns the number of rows in a matrix
// Column - read only property which returns the number of columns in a matrix
// IsSquare - read only Boolean that returns true if the matrix is a square matrix
// IsSymmetric - read only Boolean that returns true if matrix is
// symmetric (A == A.Transpose)
// [4] Constructors:
// Matrix(Int32 row, In32 column) - takes the number of rows and columns, intialize the
// private fields, create empty matrix with dimensions of row x column
// [5] Operaror Overload:
// - negatation
// +,- add,subtract two matricies of same dimension
// * multiplication of
// (i) scalar and matrix
// (ii) matrix and scalar
// (iii) matrix and matrix
// / division of a matrix by a scalar
// == test two matrices for equality
// != test two matrices for inequality
// [6] Methods
// Equals() - override of Object.Equals
// Copy() - make a copy of the current matrix
// GetHashCode() - override of Object.GetHashCode()
// Dimension() - returns an integer array with the number of rows and columns in the matrix
// ZeroMatrix() - zeroes out all the elements in a matrix
// Transpose() - returns the transposition of any matrix
// Idenitity() - returns the identity matrix of any square matrix
// Diagonal() - returns all elements along main diagonal of a square matrix
// Trace() - returns the sum of all elements along main diagonal of a square matrix
// GetRow() - returns a specific row
// GetColumn() - returns a specific column
// SwapRows() - swap two rows
// SwapColumns() - swap two columns
// HorizontalConcat() - concatenate two matrices horizontally
// VerticalConcat() - concatenate two matrices vertically
// RowEchelonForm() - brings matrix to row echelon form
// ReducedRoeEchForm() - brings matrix to reduced row echelon form
// Inverse() - find the inverse of a square matrix
// ToUpperTriangular() - converts any size matrix to upper triangular form using row operations
// Determinant() - find the determinant of a square matrix
// Print() - prints the matrix to the console in a user friendly manner
//==================
// MATRIX CLASS
//==================
/// <summary>
/// Class that provides methods and properties for manipulating 2D matrices.
/// </summary>
public class Matrix
{//start class matrix01
//==================
// FIELDS
//==================
#region Fields
/// <summary>Number of rows in a matrix.</summary>
private Int32 row;
/// <summary>Number of columns in a matrix.</summary>
private Int32 column;
/// <summary>A 2-dimensional array representing a matrix.</summary>
private Double[,] matrix;
#endregion
//==================
// ACCESSOR METHODS
//==================
#region Accessor Methods/Properties
#region Row
/// <summary>
/// Gets the number of rows in the matrix.
/// </summary>
/// <returns>A 32-bit signed integer containing the number of rows in the matrix.</returns>
public Int32 Row
{//start Row property
get { return this.row; }
//set { this.row = value; }
}//end Row property
#endregion
#region Column
/// <summary>
/// Gets the number of columns in the matrix.
/// </summary>
/// <returns>A 32-bit signed integer containing the number of columns in the matrix.</returns>
public Int32 Column
{//start Column property
get { return this.column; }
//set { this.column = value; }
}//end Column property
#endregion
#region IsSquare
/// <summary>
/// Returns true if the matrix is square or false otherwise.
/// </summary>
public Boolean IsSquare
{//start IsSquare property
get
{//start get
if (this.row == this.column)
return true;
else
return false;
}//end get
}//end IsSquare property
#endregion
#region IsSymmetric
/// <summary>
/// Read only property that returns true if the matrix is symmetric and false otherwise. A symmetric matrix is one that can be flipped across the diagonal without changing the elements.
/// </summary>
public Boolean IsSymmetric
{//start IsSymmetric property
get
{//start get
if (!this.IsSquare)
{//start if
throw new MatrixException("Matrix must be square to perform this operation.");
}//end if
else
{//start else
// create a temporary matrix of the same dimensions of the test matrix to
// perform the comparison A = A' where A' denotes matrix transpose
Matrix temp = new Matrix(this.row, this.column);
temp = this.Transpose();
//create a variable to hold the number of items that match up
Int32 symmetricElements = 0;
// iterate through the matrices and compare each test value
for (Int32 i = 0; i < this.row; i++)
{//start for with i=0
for (Int32 j = 0; j < this.column; j++)
{//start for with j=0
if (this[i,j] == temp[i,j])
{//start if
symmetricElements++;
}//end if
else
{//start else
break;
}//end else
}//end for with j=0
}//end for with i=0
if (symmetricElements == (this.row * this.column))
{//start if
return true;
}
else
{//start else
return false;
}//end else
}//end else
}//end get
}//end IsSymmetric property
#endregion
#region --------REDO Skew Symmetric
/*
public Boolean IsSkewSymmetric
{//start IsSkewSymmetric
get
{//start get
// create matrices to compare
// 'negMatrix' is the negative of the test matrix
Matrix negMatrix = new Matrix(this.row, this.column);
//negMatrix = -1 * this;
Console.WriteLine("negmatrix");
//Print(negMatrix);
// 'tposeMatrix' is the transpose of the test matrix
Matrix tposeMatrix = new Matrix(this.row, this.column);
tposeMatrix = this.Transpose();
Int32 skewElements = 0; // keep track of all the elements that match in the comparison
if (!this.IsSquare)
{//start if
throw new MatrixException("Matrix must be square to perform this operation.");
}//end if
}//end get
}//end IsSkewSymmetric
*/
#endregion
#endregion
//==================
// INDEXER
//==================
#region Indexer
/// <summary>
/// Indexer that gets and sets the values for each element of the matrix.
/// </summary>
/// <param name="row">A 32-bit signed integer representing the index of the row.</param>
/// <param name="column">A 32-bit signed integer representing the index of the column.</param>
/// <returns>Value located in the matrix given my indices 'row' and 'column'.</returns>
/// <example>
/// Given a 2 x 3 matrix M, such that [1 2 3;4 5 6], then M[0,2] will return 3</example>
public Double this[Int32 row, Int32 column]
{//start indexer
get { return this.matrix[row, column]; }
set { this.matrix[row, column] = (Double)value; }
}//end indexer
#endregion
//==================
// CONSTRUCTOR
//==================
#region Constructors
//==================
// CONSTRUCTORS
//==================
/// <summary>
/// Initializes a new instance of the Matrix class to the specified number of rows and columns. It uses the values of rows and columns to create an empty matrix of Double value types.
/// </summary>
/// <param name="row">Initialize the number of rows the matrix contains.</param>
/// <param name="column">Initialize the number of columns the matrix contains.</param>
public Matrix(Int32 row, Int32 column)
{
this.row = row; //initialize row field
this.column = column; // initialize colum field
this.matrix = new Double[row, column]; // create an empty matrix based on row and column values
}
#endregion
//====================
// OPERATOR OVERLOADS
//====================
#region + operator Overload
//[1] + operator
// (i) Matrix + Matrix
/// <summary>
/// Operator overload of binary + to add two matrices together.
/// </summary>
/// <param name="M1">First matrix to be added.</param>
/// <param name="M2">Second matrix to be added.</param>
/// <returns>Returns the result of adding two matrices.</returns>
public static Matrix operator +(Matrix M1, Matrix M2)
{//start overload of +
// test if the matricies have the same dimension and throw excepetion if they do not
if ((M1.row != M2.row) || (M1.column != M2.column))
{ throw new MatrixException("Arrays must have same dimension"); }
else
{
// create local matrix to hold results
Matrix m = new Matrix(M1.row, M1.column);
for (Int32 r = 0; r < m.row; r++)
{//start for with r=0
for (Int32 c = 0; c < m.column; c++)
{//start for with c=0
m[r, c] = (Double)(M1[r, c] + M2[r, c]);
}//end for with c=0
}//end for with r=0
return m;
}
}//end overload of +
#endregion
#region - operator Overload
//[2] - operator
// (i) Negate the elements of a given matrix
/// <summary>
/// Overload of unary - to negate a given matrix.
/// </summary>
/// <param name="M">Input matrix.</param>
/// <returns>Returns the negative of the input matrix.</returns>
public static Matrix operator -(Matrix M)
{//start overload of unary -
for (Int32 i = 0; i < M.row; i++)
{
for (Int32 j = 0; j < M.column; j++)
{ M[i, j] = -1 * M[i, j]; }
}
return M;
}//end overload of unary -
// - operator
// (ii) Subtract two matrices
/// <summary>
/// Operator overload of binary - to subtract two matrices together.
/// </summary>
/// <param name="M1">First matrix to be subtracted.</param>
/// <param name="M2">Second matrix to be subtracted.</param>
/// <returns>Returns the result of subtracting two matrices.</returns>
public static Matrix operator -(Matrix M1, Matrix M2)
{//start overload of -
// test if the matricies have the same dimension and throw excepetion if they do not
if ((M1.row != M2.row) || (M1.column != M2.column))
{ throw new MatrixException("Arrays must have same dimension"); }
else
{
// create local matrix to hold results
Matrix m = new Matrix(M1.row, M1.column);
for (Int32 r = 0; r < m.row; r++)
{//start for with r=0
for (Int32 c = 0; c < m.column; c++)
{//start for with c=0
m[r, c] = (Double)(M1[r, c] - M2[r, c]);
}//end for with c=0
}//end for with r=0
return m;
}
}//end overload of -
#endregion
#region * operator Overload
//[3] * multiplication
// (i) Scalar multiplication (scalar * matrix)
/// <summary>
/// Scalar multiplication of a real number and matrix.
/// </summary>
/// <param name="scalar">Scalar to multiply to a matrix.</param>
/// <param name="M">Matrix to be multiplied by a scalar.</param>
/// <returns>Returns the result of the multiplication of a scalar and a matrix.</returns>
public static Matrix operator *(Double scalar, Matrix M)
{//start overload of * (scalar * matrix)
// create local matrix to hold result
Matrix m = new Matrix(M.row, M.column);
for (Int32 r = 0; r < m.row; r++)
{//start for with r=0
for (Int32 c = 0; c < m.column; c++)
{//start for with c=0
m[r, c] = (Double)(scalar * M[r, c]) ;
}//end for with c=0
}//end for with r=0
return m;
}//end overload of * (scalar * matrix)
// * multiplication
// (ii) Scalar multiplication (matrix * scalar)
/// <summary>
/// Scalar multiplication of a real number and matrix.
/// </summary>
/// <param name="M">Matrix to be multiplied by a scalar.</param>
/// <param name="scalar">Scalar to multiply to a matrix.</param>
/// <returns>Returns the result of the multiplication of a scalar and a matrix</returns>
public static Matrix operator *(Matrix M, Double scalar)
{//start overload of * (matrix * scalar)
// create local matrix to hold result
Matrix m = new Matrix(M.row, M.column);
for (Int32 r = 0; r < m.row; r++)
{//start for with r=0
for (Int32 c = 0; c < m.column; c++)
{//start for with c=0
m[r, c] = (Double)(scalar * M[r, c]);
}//end for with c=0
}//end for with r=0
return m;
}//end overload of * (matrix * scalar)
// * multiplication
// (ii) Scalar multiplication (matrix * matrix)
/// <summary>
/// Element wise multiplication of two matrices where the number of columns of the first matrix must be the same as the number of rows in the second matrix.
/// </summary>
/// <param name="M1">First matrix to be multiplied.</param>
/// <param name="M2">Second matrix to me multiplied.</param>
/// <returns>Result of the multiplication of two matrices.</returns>
public static Matrix operator *(Matrix M1, Matrix M2)
{//start overload of * (matrix * matrix)
if (M1.column != M2.row)
{//start if
throw new MatrixException
("Number of columns in the first matrix must be the same as the number of rows in the second matrix");
}//end it
else
{//start else
//create new matrix to hold result which has dimensions of M1.row by M2.columns
Matrix result = new Matrix(M1.row, M2.column);
for (Int32 r = 0; r < M1.row; r++)
{//start for with M1.row=0
//variable to hold the number of columns in the 2nd matrix to iterate through
Int32 m2_cols = 0;
do
{//start do/while loop
//variable to hold the result as each r * c is added successively
Double temp_sum = 0;
for (Int32 c = 0; c < M2.row; c++)
{//start for with c=0
temp_sum += (Double)(M1[r, c] * M2[c, m2_cols]); //compute and store result of each
// pair of multiplication operations
}//end for with c=0
result[r, m2_cols] = temp_sum; //insert each successive conputation
// into the result matrix
m2_cols++; //increment the internal counter to advance to the
// next column of the second matrix
} while (m2_cols < M2.column);//end do/while loop
}//end for with M1.row=0
return result;
}//end else
}//end overload of * (matrix * matrix)
#endregion
#region / operator Overload
//[4] / division
// (i) scalar division (matrix / scalar)
// (ii) matrix division (matrix / matrix) - DOES NOT EXIST
/// <summary>
/// Element wise division of a matrix by a non-zero scalar
/// </summary>
/// <param name="M">Matrix to be divided</param>
/// <param name="scalar">Scalar that divides into matrix</param>
/// <returns>Returns the results of a matrix divided by a non-zero scalar</returns>
public static Matrix operator /(Matrix M, Double scalar)
{//start overload of /
Matrix result = new Matrix(M.row, M.column); //to store result
if (scalar == 0)
throw new MatrixException("Cannot divide by zero");
else
{//start else
for (Int32 i = 0; i < M.row; i++)
{//start for with i=0
for (Int32 j = 0; j < M.column; j++)
{//start for with j=0
result[i, j] = (Double)(M[i, j] / scalar);
}//end for with j=0
}//end for with i=0
return result;
}//end else
}//end overload of /
#endregion
#region == operator Overload
/// <summary>
/// Overload of the == operator to compare two matrices of the same dimension for equality.
/// </summary>
/// <param name="M1">Left hand matrix used in the comparison.</param>
/// <param name="M2">Right hand matrix used in the comparison.</param>
/// <returns>Returns true if the matrices are equal, false otherwise.</returns>
public static Boolean operator ==(Matrix M1, Matrix M2)
{//start overload of ==
// check that the dimensions are the same for each matrix
if ((M1.row != M2.row) || (M1.column != M2.column))
throw new MatrixException("Matrices must have the same dimensions");
else
{//start else
for (Int32 i = 0; i < M1.row; i++)
{//start for when i=0
for (Int32 j = 0; j < M1.column; j++)
{//start for with j=0
if (M1[i, j] == M2[i, j])
continue; // if the elements compared are the same and satisfy the Boolean
// test, then skip the rest of the statements and jump back
// to the top of the for loop with the 'j' index
else
{ return false; } //otherwise, stop checking and return
}//end for with j=0
}//end for with i=0
return true;
}//end else
//return true;
}//end overload of ==
#endregion
#region != operator Overload
/// <summary>
/// Overload the != operator to compare two matrices of the same dimension for non-equality.
/// </summary>
/// <param name="M1">Left hand matrix used in the comparison.</param>
/// <param name="M2">Right hand matrix used in the comparison.</param>
/// <returns>Returns true if the matrices are not equal and false if they are.</returns>
public static Boolean operator !=(Matrix M1, Matrix M2)
{//start overload of !=
// check that the dimensions are the same for each matrix
if ((M1.row != M2.row) || (M1.column != M2.column))
throw new MatrixException("Matrices must have the same dimensions");
else
{//start else
for (Int32 i = 0; i < M1.row; i++)
{//start for when i=0
for (Int32 j = 0; j < M1.column; j++)
{//start for with j=0
if (M1[i, j] != M2[i, j])
return true; // as soon as 1 pair of elements are not equal, return
else
{ continue; } // otherwise, continue to process
}//end for with j=0
}//end for with i=0
return false;
}//end else
}//end overload of !=
#endregion
//====================
// METHODS
//====================
#region Equals()
/// <summary>
/// Override of the System.Object.Equals() method.
/// </summary>
/// <param name="obj">Object type to be compared to.</param>
/// <returns>Returns true if the current object and the argument object are equal and false if not.</returns>
public override Boolean Equals(Object obj)
{//start Equals(Object)
if (obj == null) // test if the object is valid
return false;
if (obj is Matrix) // check if the current object is compatible with the Matrix type
{ return this == (Matrix)obj; } //return true if the current object and test object are equal
else
{ return false; }
}//end Equals(Object)
#endregion
#region Copy()
/// <summary>
/// Creates a copy of the current matrix object.
/// </summary>
/// <returns>A new matrix object that is a copy of the current matrix object.</returns>
public Matrix Copy()
{//start Copy()
Matrix copy = new Matrix(this.row, this.column);
for (Int32 i = 0; i < this.row; i++)
{//start for with i=0
for (Int32 j = 0; j < this.column; j++)
{//start for with j=0
copy[i, j] = this[i, j];
}//end for with j=0
}//end for with i=0
// return results
return copy;
}//end Copy()
#endregion
#region GetHashCode()
/// <summary>
/// Override of the System.Object.GetHashCode() method.
/// </summary>
/// <returns>Returns a 32-bit signed integer representing the hash for the object.</returns>
public override Int32 GetHashCode()
{
return base.GetHashCode();
}
#endregion
#region Dimension()
// helper method to retreive and store dimension of a matrix.
/// <summary>
/// Retreives the dimension of a matrix and stores the results in an array where the first position is the number of rows and the second position is the number of columns in the matrix.
/// </summary>
/// <returns>
/// An 32-bit integer array containing the number of rows and columns in a matrix.</returns>
public Int32[] Dimension()
{//start Dimension()
Int32[] dim = new Int32[2]; // create array to store rows and columns
dim[0] = this.row; // assign rows to first position of array
dim[1] = this.column; // assign columns to second position of array
return dim;
}//end Dimension()
#endregion
#region ZeroMatrix()
/// <summary>
/// Zero out all the elements of a given matrix.
/// </summary>
/// <returns>A similar dimension matrix with all elements equal to zero.</returns>
public Matrix ZeroMatrix()
{//start ZeroMatrix()
Matrix zeroMatrix = new Matrix(this.row, this.column);// create matrix to store result
for (Int32 i = 0; i < this.row; i++)
{//start for with i=0
for (Int32 j = 0; j < this.column; j++)
{//start for with j=0
zeroMatrix[i,j] = 0;
}//end for with j=0
}//end for with i=0
return zeroMatrix;
}//end ZeroMatrix()
#endregion
#region Transpose()
/// <summary>
/// Performs transposition on a matrix.
/// </summary>
/// <returns>Returns the transposed form of the matrix.</returns>
public Matrix Transpose()
{//start Transpose()
Matrix result = new Matrix(this.column, this.row); //reverse row/column to accomodate transpose
for (Int32 i = 0; i < result.row; i++)
{//start for with i=0
for (Int32 j = 0; j < result.column; j++)
{//start for with j=0
result[i, j] = this[j, i];
}//end for with j=0
}//end for with i=0
return result;
}//end Transpose()
#endregion
#region Identity()
/// <summary>
/// Computes the identity matrix of a given matrix.
/// </summary>
/// <returns>Returns the identity matrix of a square matrix.</returns>
public Matrix Identity()
{//start Identity()
Matrix identityMatrix = new Matrix(this.row, this.column);// matrix to hold result
if (!this.IsSquare)
throw new MatrixException("Identity operation can only be done on square matrices");
else
{//start else
this.ZeroMatrix(); // call ZeroMatrix() and fill all elements with 0
for (Int32 i = 0; i < this.row; i++)
{//start for with i=0
for (Int32 j = 0; j < this.column; j++)
{//start for with j=0
if (i == j) // the elements of the main diagonal is 1
identityMatrix[i, j] = 1;
}//end for with j=0
}//end for with i=0
}//end else
return identityMatrix;
}//end Identity()
#endregion
#region Diagonal()
/// <summary>
/// Finds all the diagonal elements of a square matrix.
/// </summary>
/// <returns>An array holding the values of the diagonal elements of a square matrix.</returns>
public Double[] Diagonal()
{//start Diagonal()
if (!this.IsSquare)
throw new MatrixException("Matrix must be square to get diagonal elements");
else
{//start else
// create an array to hold the diagonal elements
Double[] diaElements = new Double[this.row];
for (Int32 i = 0; i < this.row; i++)
{//start for with i=0
for (Int32 j = 0; j < this.column; j++)
{//start for with j=0
if (i == j) // select only the diagonal elements and
diaElements[i] = this[i, j]; // store in array
}//end for with j=0
}//end for with i=0
return diaElements;
}//end else
}//end Diagonal()
#endregion
#region Trace()
/// <summary>
/// Compute the trace of a square matrix, which is the sum of its diagonal elements.
/// </summary>
/// <returns>A 32-bit double representing the trace of a square matrix.</returns>
public Double Trace()
{//start Trace()
if (!this.IsSquare)
throw new MatrixException("Matrix must be square to find its trace");
else
{//start else
Double trace = 0; // local variable to hold the trace
Double[] diagonal = this.Diagonal(); // use Diagonal() method to store diagonal elements in an array
foreach (Double d in diagonal)
{//start foreach
trace += d; // add successive values of the main diagonal together
}//end foreach
return trace;
}//end else
}//end Trace()
#endregion
#region GetRow()
/// <summary>
/// Find and returns a particular row based on the row input number. All row indicies start at 0.
/// </summary>
/// <param name="rowNumber">A 32-bit integer representation of the row to be returned.</param>
/// <returns>Returns a Double array that stores the contents of the specified row of the matrix.</returns>
public Double[] GetRow(Int32 rowNumber)
{//start GetRow(Int32)
// if the user enters an invalid row, throw exception
// Invalid values: row numbers less than 0 or greater than the
// total number of rows in the matrix
if ((rowNumber < 0) || (rowNumber > this.row - 1))
{//start if
throw new MatrixException("Row does not exist. Row index starts at 0.");
}//end if
else
{//start else
// create an array to store the values of the row
Double[] returnedRow = new Double[this.column];
for (Int32 i = 0; i < this.column; i++)
{//start for
// fill the array by iterating through the specific/given/wanted row
// of the matrix and all the columns of that row
returnedRow[i] = this[rowNumber, i];
}//end for
return returnedRow; // return the filled array to the caller
}//end else
}//end GetRow(Int32)
#endregion
#region GetColumn()
/// <summary>
/// Find and returns a particular column based on the column input number. All column indicies start at 0.
/// </summary>
/// <param name="columnNumber">A 32-bit integer representation of the column to be returned.</param>
/// <returns>Returns a Double array that stores the contents of the specified column of the matrix.</returns>
public Double[] GetColumn(Int32 columnNumber)
{//start GetColumn(Int32)
// if the user enters an invalid column, throw exception
// Invalid values: column numbers less than 0 or greater than the
// total number of columns in the matrix
if ((columnNumber < 0) || (columnNumber > this.column - 1))
{//start if
throw new MatrixException("Column does not exist. Column index starts at 0.");
}//end if
else
{//start else
// create an array to store the values of the column
Double[] returnedColumn = new Double[this.row];
for (Int32 i = 0; i < this.row; i++)
{//start for
// fill the array by iterating through the specific/given/wanted column
// of the matrix and all the rows of that column
returnedColumn[i] = this[i, columnNumber];
}//end for
return returnedColumn;
}//end else
}//end GetColumn(Int32)
#endregion
#region SwapRows()
/// <summary>
/// Swaps/interchanges two rows.
/// </summary>
/// <param name="firstRow">A 32-bit integer representing the index of the first row.</param>
/// <param name="secondRow">A 32-bit integer representing the index of the second row.</param>
/// <returns>A Matrix object with the rows swapped.</returns>
public Matrix SwapRows(Int32 firstRow, Int32 secondRow)
{//start SwapRow(Int32, Int32)
// if the user enters an invalid row, throw exception
// Invalid values: row numbers less than 0 or greater than the
// total number of rows in the matrix
if (((firstRow < 0) || (firstRow > this.row)) || ((secondRow < 0) || (secondRow > this.row)))
{//start if
throw new MatrixException("Row does not exist. Row index starts at 0.");
}//end if
else
{//start else
// use the Copy() method to create a copy of the current matrix object
// then returned after the rows have been swapped
Matrix result = this.Copy();
// call the GetRow method to store the interested rows in arrays
// NOTE: I did not use a try..catch block here since I tested the validity of the rows above already
Double[] r1 = GetRow(firstRow);
Double[] r2 = GetRow(secondRow);
// iterate through the particular row and each of its columns
for (Int32 i = 0; i < this.column; i++)
{//start for with i=0
result[firstRow, i] = r2[i];
result[secondRow, i] = r1[i];
}//end for with i=0
//return the result matrix
return result;
}//end else
}//end SwapRow(Int32, Int32)
#endregion
#region SwapColumns()
/// <summary>
/// Swaps/interchanges two columns.
/// </summary>
/// <param name="firstColumn">A 32-bit integer representing the index of the first column.</param>
/// <param name="secondColumn">A 32-bit integer representing the index of the second column.</param>
/// <returns>A Matrix object with the columns swapped.</returns>
public Matrix SwapColumns(Int32 firstColumn, Int32 secondColumn)
{//start SwapColumns(Int32, Int32)
// if the user enters an invalid row, throw exception
// Invalid values: row numbers less than 0 or greater than the
// total number of columns in the matrix
if (((firstColumn < 0) || (firstColumn > this.column)) || ((secondColumn < 0) || (secondColumn > this.column)))
{//start if
throw new MatrixException("Column does not exist. Column index starts at 0.");
}//end if
else
{//start else
// use the Copy() method to create a copy of the current matrix object
// then returned after the columns have been swapped
Matrix result = this.Copy();
// call the GetColumn method to store the interested columns in arrays
// NOTE: I did not use a try..catch block here since I tested the validity of the columns above already
Double[] c1 = GetColumn(firstColumn);
Double[] c2 = GetColumn(secondColumn);
// iterate through the particular column and each of its rows
for (Int32 i = 0; i < this.row; i++)
{//start for with i=0
result[i, firstColumn] = c2[i];
result[i, secondColumn] = c1[i];
}//end for with i=0
//return the result matrix
return result;
}//end else
}//end SwapColumns(Int32, Int32)
#endregion
#region HorizontalConcat()
/// <summary>
/// Horizontally concatenate/append two matrices with the same number of rows together.
/// </summary>
/// <param name="lhs">First matrix to be used in the horizontal concatenation.</param>
/// <param name="rhs">Second matrix to be used in the horizontal concatenation. This will come after the first matrix.</param>
/// <returns>Returns a matrix that has the same number of rows as the input matrices and its column is the sum of the columns of the input matrices.</returns>
public Matrix HorizontalConcat(Matrix lhs, Matrix rhs)
{//start HorizontalConcat(Matrix, Matrix)
// to concatenate two matrices horizontally, they both have to have the same
// number of rows; test row condition
if (lhs.row != rhs.row)
{//start if
throw new MatrixException("Both matrices must have equal number of rows.");
}//end if
else
{//start else
// once the row condition is met, create a new matrix that has the same number of rows
// as the two input matrices and its column is the sum of the columns of lhs and rhs
Matrix result = new Matrix(lhs.row, (lhs.column + rhs.column));
// the outer for loop is used to iterate through the rows of both input matrices since
// they both share that property
for (Int32 i = 0; i < lhs.row; i++)
{//start for with i=0
// step through the columns of the first input matrix and update result matrix
for (Int32 j1 = 0; j1 < lhs.column; j1++)
{//start for with j1=0
// insert the first matrix into the result matrix
result[i,j1] = lhs[i,j1];
}//end for with j1=0
// step through the columns of the second input matrix and update result matrix
for (Int32 j2 = 0; j2 < rhs.column; j2++)
{//start for with j2=0
// append to the result matrix where the first matrix left off
result[i, (j2 + lhs.column)] = rhs[i,j2];
}//end for with j2=0
}//end for with i=0
// return the result matrix
return result;
}//end else
}// end HorizontalConcat(Matrix, Matrix)
#endregion
#region VerticalConcat()
/// <summary>
/// Vertically concatenate/append two matrices with the same number of columns together.
/// </summary>
/// <param name="top">First matrix to be used in the vertical concatenation.</param>
/// <param name="bottom">Second matrix to be used in the vertical concatenation. This will come below the first matrix</param>
/// <returns>Returns a matrix that has the same number of columns as the input matrices and its row is the sum of the rows of the input matrices.</returns>
public Matrix VerticalConcat(Matrix top, Matrix bottom)
{//start VerticalConcat(Matrix, Matrix)
// to concatenate matrices verically, they have to have the same number of columns
// test column condition
if (top.column != bottom.column)
{//start if
throw new MatrixException("Both matrices must have equal number of columns.");
}//end if
else
{//start else
// once the column condition is met, create a new matrix that has the same number of
// columns as the two input matrices and its row is the sum of the
// rows of the top and bottom matrices
Matrix result = new Matrix((top.row + bottom.row),top.column);
// the outer for loop is used to iterate through the columns of both input matrices since
// they both share that property
for (Int32 i = 0; i < top.column; i++)
{//start for with i=0
// step through the rows of the first input matrix and update result matrix
for (Int32 j1 = 0; j1 < top.row; j1++)
{//start for with j1=0
result[j1, i] = top[j1, i];
}//end for with j1=0
// step through the rows of the second input matrix and update result matrix
for (Int32 j2 = 0; j2 < bottom.row; j2++)
{//start for with j2=0
result[(j2 + bottom.row), i] = bottom[j2, i];
}//end for with j2=0
}//end for with i=0
// return the result matrix
return result;
}//end else
}//end VerticalConcat(Matrix, Matrix)
#endregion
#region RowEchelonForm()
/// <summary>
/// Brings a matrix into Row Echelon Form.
/// </summary>
/// <param name="lhs">The left hand side/matrix representing the coefficient of each variable in a set of linear equations.</param>
/// <param name="rhs">The right hand side/vector representing the solution to each linear equation.</param>
/// <returns>An augmented matrix in row echelon form where backward substitution can be used to solve for each variable in the set of linear equations.</returns>
public Matrix RowEchelonForm(Matrix lhs, Matrix rhs)
{//start RowEchForm(Matrix)
// m1 is a 'n x n' matrix representing the coefficients of each variable
// in set of linear equations; left hand side of the equation basically
// m2 is a 'n x 1' matrix represeting the right hand side of the linear equations
// [1] Call HorizontalConcat(Matrix, Matrix) to concatenate the 'lhs' and 'rhs'
// matrices such that the result looks like 'lhs|rhs' where | is used to
// indicate a new augmented matrix. This method will also test the two input
// matrices to make sure they have the same number of rows before peforming
// the concatentation
Int32 resultRows = lhs.row;
Int32 resultColumns = lhs.column + rhs.column;
Matrix result = new Matrix(resultRows, resultColumns);
result = HorizontalConcat(lhs, rhs);
// [2] Check and see if any of the rows in the lhs matrix has all zero elements
// If any row has all zeros, call RowSwap(Int32, Int32) and move the entire
// row (lhs|rhs) to the bottom of the result matrix. Increment a counter to
// keep track of how many zero rows there are. If another one is found, that
// value can be used to offset where the next zero row swap should take place
Int32 zeroRows = 0; // used to keep track of the number rows that have all zero elements
Int32 noOfZeros = 0;// keep track of the number of zeros in one row
for (Int32 i = 0; i < result.row; i++)
{//start for with i=0
for (Int32 j = 0; j < result.column - 1; j++) // do 'result.column - 1' to skip the rhs column
{//start for with j=0
// keep track of how many zeros there are in a particular row
if (result[i, j] == 0)
{//start if
noOfZeros++;
continue; // if there is a zero at this position, jump to the next
// increment of 'j'
}//end if
}//end for with j=0
// this tests if a whole row has zeros
// if it does, swap it with the last row and move up from the last row if there
// there is more than one zero row
if (noOfZeros == lhs.column)
{//start if
// if all the elements in a row is zero, increment the counter that keeps track
// of how many zero rows there are
zeroRows++;
result = result.SwapRows(i, resultColumns - 1 - zeroRows);
}//end if
else
{//start else
// reset the 'noOfZeros' variable to take into account that any other rows may
// contain zeros that add up
// Example: for a matrix that looks like: {1 0; 0 0; 2 2}, by the time the
// counter gets to the end of the second row, it will have 3 zeros in it
// thus ignoring the 'if' statement above and SwapRows() is never called.
// By resetting the 'noOfZeros' variable, when the counter reaches at the end
// of row 2, it will determine that only 2 zeros, hence 1 zero row exists
noOfZeros = 0;
}//end else
}//end for with i=0
Print(result);
// [3] Examine each leading element in the main diagonal
// Check if each diagonal element is 1 and for each column under test:
// - if it is NOT equal to 1, check all the elements in the test column
// to see if any has a value of 1. If so, switch it with the leading diagonal
// - if it IS equal to 1, then for each element below the diagonal, add/subtract
// multiples of the diagonal element to zero out each position
Int32 noOfRows = 0; // count though the number of rows in a column
Int32 rowsUsed = 0; // count number of rows processed so far
//Int32 noOfRemainingRows = 0; // count rows not processed so far
// this loop is used to swap any the rows where there is a 1 below the diagonal element
for (Int32 j = 0; j < lhs.column; j++)
{//start for with j=0
while (noOfRows < lhs.row)
{//start while
// calculate number of processed and unprocessed rows
rowsUsed = j + 1;
//noOfRemainingRows = lhs.row - (j + 1);
// if the diagonal element is not 1, go down the column and see if any other
// rows has a 1 under the diagonal element that is being checked
if ((noOfRows == j) && (result[noOfRows, j] != 1))
{//start if #1
for (Int32 i = rowsUsed; i < lhs.row; i++)
{//start for
// check if any element under the diagonal element is equal to 1
if (result[i, j] == 1)
{//start if
// if it is equal to 1, swap the current row with the diagonal element row and ignore the rest
result = result.SwapRows(rowsUsed, noOfRows);
}//end if
else
{//start else
continue; // go to next row and repeat the check
}//end else
}//end for
//break;
}//end if #1
noOfRows++; // increment the number of rows tested so far
}//end while
noOfRows = 0; // reset 'noOfRows' to zero or else the while loop will fail on
// on the 2nd pass of the for loop
}//end for with j=0
Int32 pivR = 0;
Int32 pivC = 0;
for (Int32 a = 0; a < result.row; a++)
{//start for with a=0
// get each pivot element along the main diagonal
Double pivValue = result[pivR, pivC];
for (Int32 b = 0; b < result.column; b++)
{//start for with b=0
result[a, b] = result[a,b] / pivValue;
}//end for with b=0
pivC++;
pivR++;
// go through the elements below the main diagonal element and zero out
for (Int32 c = a + 1; c < result.row; c++)
{//start for with c=a+1
Double[] commonRow = result.GetRow(a); // get row that was normalized
Double[] commonCol = result.GetColumn(a); // get column to operate on
Double comMult = commonCol[c]; // get the coefficient of each element below
// the leading element to zero out
Double[] modRow = new Double[result.column]; // create storage for each test row
// multiply the row with the leading diagonal by the
// negative coeficient of the element below it
for (Int32 e = 0; e < result.column; e++)
{//start for with e=0
modRow[e] = commonRow[e] * comMult * -1;
}//end for with e=0
// add multiples of the leading row to rows below it so as to zero out the
// elements under the leading diagonal element
for (Int32 d = 0; d < result.column; d++)
{//start for with d=0
result[c,d] = result[c,d] + modRow[d];
}//end for with d=0
}//end for with c=a+1
}//end for with a=0
return result;
}//end RowEchForm(Matrix)
#endregion
#region ReducedRowEchForm()
/// <summary>
/// Gauss-Jordan elimination that brings a matrix into Reduced Row Echelon Form.
/// </summary>
/// <param name="lhs">The left hand side/matrix representing the coefficient of each variable in a set of linear equations.</param>
/// <param name="rhs">The right hand side/vector representing the solution to each linear equation.</param>
/// <returns>An augmented matrix in reduced row form where backward substitution can be used to solve for each variable in the set of linear equations.</returns>
public Matrix ReducedRowEchForm(Matrix lhs, Matrix rhs)
{//start ReducedRowEchForm(Matrix, Matrix)
// m1 is a 'n x n' matrix representing the coefficients of each variable
// in set of linear equations; left hand side of the equation basically
// m2 is a 'n x 1' matrix represeting the right hand side of the linear equations
// [1] Call HorizontalConcat(Matrix, Matrix) to concatenate the 'lhs' and 'rhs'
// matrices such that the result looks like 'lhs|rhs' where | is used to
// indicate a new augmented matrix. This method will also test the two input
// matrices to make sure they have the same number of rows before peforming
// the concatentation
Int32 resultRows = lhs.row;
Int32 resultColumns = lhs.column + rhs.column;
Matrix result = new Matrix(resultRows, resultColumns);
result = HorizontalConcat(lhs, rhs);
// [2] Check and see if any of the rows in the lhs matrix has all zero elements
// If any row has all zeros, call RowSwap(Int32, Int32) and move the entire
// row (lhs|rhs) to the bottom of the result matrix. Increment a counter to
// keep track of how many zero rows there are. If another one is found, that
// value can be used to offset where the next zero row swap should take place
Int32 zeroRows = 0; // used to keep track of the number rows that have all zero elements
Int32 noOfZeros = 0;// keep track of the number of zeros in one row
for (Int32 i = 0; i < result.row; i++)
{//start for with i=0
for (Int32 j = 0; j < result.column - 1; j++) // do 'result.column - 1' to skip the rhs column
{//start for with j=0
// keep track of how many zeros there are in a particular row
if (result[i, j] == 0)
{//start if
noOfZeros++;
continue; // if there is a zero at this position, jump to the next
// increment of 'j'
}//end if
}//end for with j=0
// this tests if a whole row has zeros
// if it does, swap it with the last row and move up from the last row if there
// there is more than one zero row
if (noOfZeros == lhs.column)
{//start if
// if all the elements in a row is zero, increment the counter that keeps track
// of how many zero rows there are
zeroRows++;
result = result.SwapRows(i, resultColumns - 1 - zeroRows);
}//end if
else
{//start else
// reset the 'noOfZeros' variable to take into account that any other rows may
// contain zeros that add up
// Example: for a matrix that looks like: {1 0; 0 0; 2 2}, by the time the
// counter gets to the end of the second row, it will have 3 zeros in it
// thus ignoring the 'if' statement above and SwapRows() is never called.
// By resetting the 'noOfZeros' variable, when the counter reaches at the end
// of row 2, it will determine that only 2 zeros, hence 1 zero row exists
noOfZeros = 0;
}//end else
}//end for with i=0
// [3] Examine each leading element in the main diagonal
// Check if each diagonal element is 1 and for each column under test:
// - if it is NOT equal to 1, check all the elements in the test column
// to see if any has a value of 1. If so, switch it with the leading diagonal
// - if it IS equal to 1, then for each element below the diagonal, add/subtract
// multiples of the diagonal element to zero out each position
Int32 noOfRows = 0; // count though the number of rows in a column
Int32 rowsUsed = 0; // count number of rows processed so far
//Int32 noOfRemainingRows = 0; // count rows not processed so far
// this loop is used to swap any the rows where there is a 1 below the diagonal element
for (Int32 j = 0; j < lhs.column; j++)
{//start for with j=0
while (noOfRows < lhs.row)
{//start while
// calculate number of processed and unprocessed rows
rowsUsed = j + 1;
//noOfRemainingRows = lhs.row - (j + 1);
// if the diagonal element is not 1, go down the column and see if any other
// rows has a 1 under the diagonal element that is being checked
if ((noOfRows == j) && (result[noOfRows, j] != 1))
{//start if #1
for (Int32 i = rowsUsed; i < lhs.row; i++)
{//start for
// check if any element under the diagonal element is equal to 1
if (result[i, j] == 1)
{//start if
// if it is equal to 1, swap the current row with the diagonal element row and ignore the rest
result = result.SwapRows(rowsUsed, noOfRows);
}//end if
else
{//start else
continue; // go to next row and repeat the check
}//end else
}//end for
//break;
}//end if #1
noOfRows++; // increment the number of rows tested so far
}//end while
noOfRows = 0; // reset 'noOfRows' to zero or else the while loop will fail on
// on the 2nd pass of the for loop
}//end for with j=0
Int32 pivR = 0;
Int32 pivC = 0;
for (Int32 a = 0; a < result.row; a++)
{//start for with a=0
// get each pivot element along the main diagonal
Double pivValue = result[pivR, pivC];
// divide each element in the row by the pivot element
for (Int32 b = 0; b < result.column; b++)
{//start for with b=0
result[a, b] = result[a, b] / pivValue;
}//end for with b=0
pivC++;
pivR++;
// go through the elements below the main diagonal element and zero out
for (Int32 c = a + 1; c < result.row; c++)
{//start for with c=a+1
Double[] commonRow = result.GetRow(a); // get row that was normalized
Double[] commonCol = result.GetColumn(a); // get column to operate on
Double comMult = commonCol[c]; // get the coefficient of each element below
// the leading element to zero out
Double[] modRow = new Double[result.column]; // create storage for each test row
// multiply the row with the leading diagonal by the
// negative coeficient of the element below it
for (Int32 e = 0; e < result.column; e++)
{//start for with e=0
modRow[e] = -1 * commonRow[e] * comMult;
}//end for with e=0
// add multiples of the leading row to rows below it so as to zero out the
// elements under the leading diagonal element
for (Int32 d = 0; d < result.column; d++)
{//start for with d=0
result[c, d] = result[c, d] + modRow[d];
}//end for with d=0
}//end for with c=a+1
}//end for with a=0
for (Int32 f = result.row - 1; f > -1; f--)
{//start for with f=result.row-1
// at this stage in the algorithm, all the leading diagonal elements have a
// value of 1
// start at the bottom right of the augmented lhs matrix and zero out elements
// above the leading diagonal
for (Int32 g = f - 1; g > -1; g--)
{//start for with g=f-1
Double[] commonRow2 = result.GetRow(f); // get the normalized row starting
// at the bottom
Double[] commonCol2 = result.GetColumn(f); // get column to operate on
Double commonMult2 = commonCol2[g]; // get the coefficient of the element
// above the leading diagonal
Double[] modRow2 = new Double[result.column]; // storage for each test row
// multiply the row with the leading diagonal by the commonMult2
for (Int32 m = 0; m < result.column; m++)
{//start for with m=0
modRow2[m] = -1 * commonRow2[m] * commonMult2;
}//end for with m=0
// add multiples of the test row to each row above the leading diagonal
for (Int32 n = 0; n < result.column; n++)
{//start for with n=0
result[g, n] = result[g, n] + modRow2[n];
if (Math.Abs(result[g, n]) < 0.000000001)
{
result[g, n] = 0;
}
}//end for with n=0
}//end for with g=f-1
}//end for with f=result.row-1
return result;
}//end ReducedRowEchForm(Matrix, Matrix)
#endregion
#region Inverse()
/// <summary>
/// Find the inverse of a square matrix.
/// </summary>
/// <returns>Returns the inverse of a square matrix (if it exists).</returns>
public Matrix Inverse()
{//start Inverse()
if (!this.IsSquare)
{//start if
throw new MatrixException("Matrix must be square to determine its inverse.");
}//end if
else
{//start else
// create storage for the result inverse matrix, the right hand side
// identity matrix that will be transformed into the inverse and
// a temporary matrix with twice the number of columns to hold
// the augmented matrix of the orginal matrix to transform and its identity
Matrix inverseMatrix = new Matrix(this.row, this.column);
Matrix rhs = new Matrix(this.row, this.column);
Matrix temp = new Matrix(this.row, (2 * this.column));
// create the 'rhs' identity matrix
rhs = rhs.Identity();
// do Gauss-Jordan eliminaton (Reduced Row Echelon Form) on the 'temp'
// and 'rhs' matrices
temp = temp.ReducedRowEchForm(this, rhs);
// select only the inverse part of the RREF result and return that
for (Int32 i = 0; i < inverseMatrix.row; i++)
{//start for with i=0
for (Int32 j = 0; j < inverseMatrix.column; j++)
{//start for with j=0
inverseMatrix[i,j] = temp[i, (j + inverseMatrix.column)];
}//end for with j=0
}//end for with i=0
return inverseMatrix;
}//end else
}//end Inverse()
#endregion
#region ToUpperTriangular()
/// <summary>
/// Converts a matrix to Upper Triangular form using reduced row operations.
/// </summary>
/// <returns>A matrix in upper triangular form.</returns>
public Matrix ToUpperTriangular()
{//start ToUpperTriangular()
//!!!!!!!!!!!!!!!!!!!!!!!!!
//TO FIX: I did not take into account that the pivot may be zero and should
// look see if elements below can be swapped instead
// whether the matrix is square or not, it can be converted by elementary row operations
// for Upper Triangular, all elements below the leading diagonal is 0
Matrix upTri = this; // store the original matrix and operate on 'upTri'
for (Int32 i = 0; i < upTri.row; i++)
{//start for with i=0
for (Int32 j = 0; j < upTri.column; j++)
{//start for with j=0
if (i == j)
{//start if
Double[] pivotRow = upTri.GetRow(i); // get the entire row containing the pivot element
Double[] pivotColumn = upTri.GetColumn(i); // get the pivot column
Double pivot = upTri[i, j]; // get the pivot element for each leading element
// normalize the pivot row to be used later in the zero out process for elements
// below the pivot element
if (pivot == 0)
{//start if
break; // don't want to divide by 0, so break out of this iteration
}//end if
else
{//start else
for (Int32 k = 0; k < pivotRow.Length; k++)
{//start for with k=0
pivotRow[k] = pivotRow[k] / pivot;
}//end for with k=0
}//end else
// now add mulitplies of the normalized pivot row rows below it to zero
// out the elements
for (Int32 m = i + 1; m < upTri.row; m++)
{//start for with mi+1
// get the coefficient of the element to zero out
Double coeffTozero = upTri[m, j];
// create a temporary row to store the results of
// multiplying the coefficient to zero out and the pivot row
Double[] tempRow = new Double[upTri.column];
// multiply the coefficient to the pivot row
for (Int32 n = 0; n < tempRow.Length; n++)
{//start for with n=0
tempRow[n] = -1 * coeffTozero * pivotRow[n];
// add the temp row to the upTri matrix to zero out
// elements below the leading diagonal
upTri[m, n] = upTri[m, n] + tempRow[n];
}//end for with n=0
}//end for with m=i+1
}//end if
else
{//start else
break;
}//end else
}//end for with j=0
}//end for with i=0
return upTri;
}//end ToUpperTriangular()
#endregion
#region Determinant()
/// <summary>
/// Find the determinant of a matrix.
/// </summary>
/// <returns>A Double value representing the determinant of a matrix.</returns>
public Double Determinant()
{//start Determinant()
Double determinant = 1; // initalize the variable to hold the value of the determiant
if (!this.IsSquare)
{//start if
throw new MatrixException("Matrix must be square.");
}//end if
// if the matrix is 1 x 1, then the determinant is just the single element
else if ((this.row == 1) && (this.column == 1))
{//start else/if
determinant = this[1, 1];
}//end else/if
else
{//start else
// convert the matrix to upper triangular
Matrix det = new Matrix(this.row, this.column);
det = this.ToUpperTriangular();
// multiply each of the leading diagonal elements to get the determinant
for (Int32 i = 0; i < det.row; i++)
{//start for with i=0
for (Int32 j = 0; j < det.column; j++)
{//start for with j=0
if (i == j)
{//start if
determinant = determinant * det[i, j];
}//end if
else
{//start else
continue;
}//end else
}//end for with j=0
}//end for with i=0
}//end else
return determinant;
}//end Determinant()
#endregion
#region Print()
/// <summary>
/// Prints out the matrix in a user friendly form to the console.
/// </summary>
/// <param name="matrix">Matrix to be printed to the screen.</param>
public static void Print(Matrix matrix)
{//start Print()
Console.WriteLine("\n");
for (Int32 i = 0; i < matrix.Row; i++)
{//start for with i=0
for (Int32 j = 0; j < matrix.Column; j++)
{//start for with j=0
Console.Write(matrix[i, j] + "\t");
}//end for with j=0
Console.WriteLine("\n");
}//end for with i=0
}//end Print()
#endregion
}//end class matrix01
#region CLASS MatrixException
/// <summary>
/// Custom exception class that inherits from the Exception base class
/// </summary>
public class MatrixException : Exception
{// start class MatrixException
/// <summary>Default constructor that inherits the base class default constructor as well
/// </summary>
public MatrixException() : base() { }
/// <summary>Constructor for writing to the message property of the base class</summary>
/// <param name="message">Sets the message property of the base class</param>
public MatrixException(String message) : base(message) { }
}// end class MatrixException
#endregion
}//end namespace
No comments:
Post a Comment