# My C# Implementation Of Basic Linear Algebra Concepts

## Overview

Today, I will be sharing with you my C# implementation of basic linear algebra concepts. This code has been posted to GitHub under a MIT license, so feel free to modify and deal with code without any restrictions or limitations (no guarantees of any kind.) And please let me know your feedback, comments, suggestions, and corrections.

## Introduction

The library has been built using C# and has been posted to GitHub ang NuGet.

It covers the following,

Matrices
• Matrix concatenation / shrinking / extraction.
• Matrix reduction and elimination (Gauss and Gauss-Jordan).
• Matrix rank, solution, and solution state (unique, infinite, and no solution.)
• Determinant calculation using Laprace Expansion method.
• Invertibility and inverses.
• Matrix mirrors.
• Matrix multiplication and powers.
• Transposes.
• Identity matrices.
• Transformation matrices.
• Projection matrices.
• Reflection matrices.
• 2D and 3D rotation matrices.
• 2D and 3D shearing matrices.
• Cloning and equality comparers.
Vectors
• Angel between vectors.
• Vector normalization and magnitude calculator.
• Dot product and cross product.
• Vector scaling.
• Projection onto other vectors and subspaces.
• Cloning and equality comparers.
The design is based on raw functions that operate on Array objects. Wrapper classes for matrices and vectors (Matrix and Vector, respectively) have been created to hide those keen functions. Static initializers and operators have been added to simplify the use of the wrapper classes.

I will not go into a discussion of how a function mathematically works. Despite this, feel free to correct me whenever you want.

Let’s have a brief look at some of the highlights of the code.

## Matrices

Matrix Elimination

This function demonstrates Gaussian and Gauss-Jordan elimination. It can be used to reduce a matrix to the row-echelon form (REF / Gaussian) or reduced row-echelon form (RREF / Gauss-Jordan.) It can be used to solve a matrix for a number of augmented columns and to calculate matrix rank.
1. /// <summary>
2. /// Reduces matrix to row-echelon (REF/Gauss) or reduced row-echelon (RREF/Gauss-Jordan) form and solves for augmented columns (if any.)
3. /// </summary>
4. public static MatrixEliminationResult Eliminate(double[,] input, MatrixReductionForm form, int augmentedCols = 0) {
5.   int totalRowCount = input.GetLength(0);
6.   int totalColCount = input.GetLength(1);
7.
8.   if (augmentedCols >= totalColCount)
9.     throw new ArgumentException(nameof(augmentedCols),
10.       Properties.Resources.Exception_TooMuchAugmentedColumns);
11.
12.   MatrixEliminationResult result = new MatrixEliminationResult();
13.
14.   double[,] output = CreateCopy(input);
15.
16.   // number of pivots found
17.   int numPivots = 0;
18.
19.   // loop through columns, exclude augmented columns
20.   for (int col = 0; col < totalColCount - augmentedCols; col++) {
21.     int? pivotRow = FindPivot(output, numPivots, col, totalRowCount);
22.
23.     if (pivotRow == null)
24.       continue// no pivots, go to another column
25.
26.     ReduceRow(output, pivotRow.Value, col, totalColCount);
27.
28.     SwitchRows(output, pivotRow.Value, numPivots, totalColCount);
29.
30.     pivotRow = numPivots;
31.     numPivots++;
32.
33.     // Eliminate Previous Rows
34.     if (form == MatrixReductionForm.ReducedRowEchelonForm) {
35.       for (int tmpRow = 0; tmpRow < pivotRow; tmpRow++)
36.         EliminateRow(output, tmpRow, pivotRow.Value, col, totalColCount);
37.     }
38.
39.     // Eliminate Next Rows
40.     for (int tmpRow = pivotRow.Value; tmpRow < totalRowCount; tmpRow++)
41.       EliminateRow(output, tmpRow, pivotRow.Value, col, totalColCount);
42.
43.   }
44.
45.   result.Rank = numPivots;
46.   result.FullMatrix = output;
47.   result.AugmentedColumnCount = augmentedCols;
48.   if (augmentedCols > 0 && form == MatrixReductionForm.ReducedRowEchelonForm) { // matrix has solution
49.     result.Solution = ExtractColumns(output, totalColCount - augmentedCols, totalColCount - 1);
50.   }
51.
52.   return result;
53. }
54.
55. private static int? FindPivot(double[,] input, int startRow, int col, int rowCount) {
56.   for (int i = startRow; i < rowCount; i++) {
57.     if (input[i, col] != 0)
58.       return i;
59.   }
60.
61.   return null;
62. }
63.
64. private static void SwitchRows(double[,] input, int row1, int row2, int colCount) {
65.   if (row1 == row2)
66.     return;
67.
68.   for (int col = 0; col < colCount; col++) {
69.     var tmpVal1 = input[row1, col];
70.     input[row1, col] = input[row2, col];
71.     input[row2, col] = tmpVal1;
72.   }
73. }
74.
75. private static void ReduceRow(double[,] input, int row, int col, int colCount) {
76.   var coeffecient = 1.0 / input[row, col];
77.
78.   if (coeffecient == 1)
79.     return;
80.
81.   for (; col < colCount; col++)
82.     input[row, col] *= coeffecient;
83. }
84.
85. /// <summary>
86. /// Eliminates row using another pivot row.
87. /// </summary>
88. private static void EliminateRow(double[,] input, int row, int pivotRow, int pivotCol, int colCount) {
89.   if (pivotRow == row)
90.     return;
91.
92.   if (input[row, pivotCol] == 0)
93.     return;
94.
95.   double coeffecient = input[row, pivotCol];
96.
97.   for (int col = pivotCol; col < colCount; col++) {
98.     input[row, col] -= input[pivotRow, col] * coeffecient;
99.   }
100. }
This function has been wrapped into our Matrix wrapper with those functions:
1. public virtual Matrix Reduce(MatrixReductionForm form, int? augmentedColCount = null);
2. public virtual MatrixSolutionState GetSolutionState(int? augmentedColCount = null);
3. public virtual Matrix Solve(int? augmentedColCount = null);
4. public virtual int GetRank(int? augmentedColCount = null);
Matrix Determinant

This function determinant uses the Laprace Expansion method. It works by calculating the cross-product of the matrix. Here’s the implementation,
1. /// <summary>
2. /// Calculates determinant. Internally uses Laprace Expansion method.
3. /// </summary>
4. /// <remarks>
5. /// Returns 1 for an empty matrix. See https://math.stackexchange.com/questions/1762537/what-is-the-determinant-of/1762542
6. /// </remarks>
7. public static double Determinant(double[,] input) {
8.   var results = CrossProduct(input);
9.
10.   return results.Sum();
11. }
12.
13. public static double[] CrossProduct(double[,] input) {
14.   int rowCount = input.GetLength(0);
15.   int colCount = input.GetLength(1);
16.
17.   if (rowCount != colCount)
18.     throw new InvalidOperationException(Properties.Resources.Exception_RequiredSquareMatrix);
19.
20.   if (rowCount == 0)
21.     return new double[] { 1 };
22.
23.   if (rowCount == 1)
24.     return new double[] { input[0, 0] };
25.
26.   if (rowCount == 2)
27.     return new double[] { (input[0, 0] * input[1, 1]) - (input[0, 1] * input[1, 0]) };
28.
29.   double[] results = new double[colCount];
30.
31.   for (int col = 0; col < colCount; col++) {
32.     // checkerboard pattern, even col  = 1, odd col = -1
33.     var checkerboardFactor = col % 2.0 == 0 ? 1 : -1;
34.     var coeffecient = input[0, col];
35.
36.     var crossMatrix = GetCrossMatrix(input, 0, col);
37.     results[col] = checkerboardFactor * (coeffecient * Determinant(crossMatrix));
38.   }
39.
40.   return results;
41. }
42.
43. /// <summary>
44. /// Retrieves all matrix entries except the specified row and col. Used in cross product and determinant functions.
45. /// </summary>
46. public static double[,] GetCrossMatrix(double[,] input, int skipRow, int skipCol) {
47.   int rowCount = input.GetLength(0);
48.   int colCount = input.GetLength(1);
49.
50.   var output = new double[rowCount - 1, colCount - 1];
51.   int outputRow = 0;
52.
53.   for (int row = 0; row < rowCount; row++) {
54.     if (row == skipRow)
55.       continue;
56.
57.     int outputCol = 0;
58.
59.     for (int col = 0; col < colCount; col++) {
60.       if (col == skipCol)
61.         continue;
62.
63.       output[outputRow, outputCol] = input[row, col];
64.
65.       outputCol++;
66.     }
67.     outputRow++;
68.   }
69.
70.   return output;
71. }
Matrix Inverses

The matrix inverse function eliminates a function and determines if all columns are unique by checking matrix rank. Another function checks if the matrix is invertible by checking determinant.
1. /// <summary>
2. /// Returns a value indicates whether the specified matrix is invertible. Internally uses array determinant.
3. /// </summary>
4. public static bool IsInvertible(double[,] input) {
5.   var rowCount = input.GetLength(0);
6.   var colCount = input.GetLength(1);
7.
8.   if (rowCount != colCount)
9.     return false;
10.
11.   return Determinant(input) != 0;
12. }
13.
14. /// <summary>
15. /// Calculates the inverse of a matrix. Returns null if non-invertible.
16. /// </summary>
17. public static double[,] Invert(double[,] matrix) {
18.   var rowCount = matrix.GetLength(0);
19.   var colCount = matrix.GetLength(1);
20.
21.   if (rowCount != colCount)
22.     throw new InvalidOperationException(Properties.Resources.Exception_RequiredSquareMatrix);
23.
24.   var newMatrix = ConcatHorizontally(matrix, CreateIdentityMatrix(rowCount));
25.
26.   var result = Eliminate(newMatrix, MatrixReductionForm.ReducedRowEchelonForm, rowCount);
27.   if (result.Rank != colCount)
28.     return null;
29.
30.   return result.Solution;
31. }
Matrix Multiplication

Four variations of the Multiply function are available: scalar multiplication, matrix multiplications, unsafe multiplications, and powers.
1. /// <summary>
2. /// Multiplies/Scales a matrix by a scalar input.
3. /// </summary>
4. public static double[,] Multiply(double scalar, double[,] input) {
5.   var rowCount = input.GetLength(0);
6.   var colCount = input.GetLength(1);
7.
8.   // creating the final product matrix
9.   double[,] output = new double[rowCount, colCount];
10.
11.   for (int row = 0; row < rowCount; row++) {
12.     for (int col = 0; col < colCount; col++) {
13.       output[row, col] = scalar * input[row, col];
14.     }
15.   }
16.
17.   return output;
18. }
19.
20. /// <summary>
21. /// Multiplies two matrices together.
22. /// </summary>
23. public static double[,] Multiply(double[,] matrix1, double[,] matrix2) {
24.   // cahing matrix lengths for better performance
25.   var matrix1Rows = matrix1.GetLength(0);
26.   var matrix1Cols = matrix1.GetLength(1);
27.   var matrix2Rows = matrix2.GetLength(0);
28.   var matrix2Cols = matrix2.GetLength(1);
29.
30.   // checking if product is defined
31.   if (matrix1Cols != matrix2Rows)
32.     throw new InvalidOperationException(Properties.Resources.Exception_UndefinedProduct);
33.
34.   // creating the final product matrix
35.   double[,] output = new double[matrix1Rows, matrix2Cols];
36.
37.   // looping through matrix 1 rows
38.   for (int matrix1_row = 0; matrix1_row < matrix1Rows; matrix1_row++) {
39.     // for each matrix 1 row, loop through matrix 2 columns
40.     for (int matrix2_col = 0; matrix2_col < matrix2Cols; matrix2_col++) {
41.       // loop through matrix 1 columns to calculate the dot product
42.       for (int matrix1_col = 0; matrix1_col < matrix1Cols; matrix1_col++) {
43.         output[matrix1_row, matrix2_col] += matrix1[matrix1_row, matrix1_col] * matrix2[matrix1_col, matrix2_col];
44.       }
45.     }
46.   }
47.
48.   return output;
49. }
50.
51. /// <summary>
52. /// Multiplies two matrices together. Uses unsafe code. Better with extremely large matrices.
53. /// </summary>
54. public static double[,] MultiplyUnsafe(double[,] matrix1, double[,] matrix2) {
55.   // cahing matrix lengths for better performance
56.   var matrix1Rows = matrix1.GetLength(0);
57.   var matrix1Cols = matrix1.GetLength(1);
58.   var matrix2Rows = matrix2.GetLength(0);
59.   var matrix2Cols = matrix2.GetLength(1);
60.
61.   // checking if product is defined
62.   if (matrix1Cols != matrix2Rows)
63.     throw new InvalidOperationException(Properties.Resources.Exception_UndefinedProduct);
64.
65.   // creating the final product matrix
66.   double[,] output = new double[matrix1Rows, matrix2Cols];
67.
68.   unsafe
69.   {
70.     // fixing pointers to matrices
71.     fixed (
72.       double* pProduct = output,
73.       pMatrix1 = matrix1,
74.       pMatrix2 = matrix2) {
75.
76.       int i = 0;
77.       // looping through matrix 1 rows
78.       for (int matrix1_row = 0; matrix1_row < matrix1Rows; matrix1_row++) {
79.         // for each matrix 1 row, loop through matrix 2 columns
80.         for (int matrix2_col = 0; matrix2_col < matrix2Cols; matrix2_col++) {
81.           // loop through matrix 1 columns to calculate the dot product
82.           for (int matrix1_col = 0; matrix1_col < matrix1Cols; matrix1_col++) {
83.
84.             var val1 = *(pMatrix1 + (matrix1Rows * matrix1_row) + matrix1_col);
85.             var val2 = *(pMatrix2 + (matrix2Cols * matrix1_col) + matrix2_col);
86.
87.             *(pProduct + i) += val1 * val2;
88.
89.           }
90.
91.           i++;
92.
93.         }
94.       }
95.
96.     }
97.   }
98.
99.   return output;
100. }
101.
102. /// <summary>
103. /// Raises an input matrix to the nth power.
104. /// </summary>
105. public static double[,] Power(double[,] input, int power) {
106.   if (input.GetLength(0) != input.GetLength(1))
107.     throw new InvalidOperationException(Properties.Resources.Exception_RequiredSquareMatrix);
108.   if (power < 0)
109.     throw new ArgumentException(nameof(power));
110.   if (power == 0)
111.     return CreateIdentityMatrix(input.GetLength(0));
112.
113.   double[,] output = CreateCopy(input);
114.   for (int i = 0; i < power; i++) {
115.     output = Multiply(output, input);
116.   }
117.
118.   return output;
119. }
Subspace Projection Matrices

The following function creates projection matrix from a subspace.
1. /// <summary>
2. /// Creates projection matrix for the specified subspace.
3. /// </summary>
4. public static double[,] CreateProjectionMatrix(double[,] subspace) {
5.   var subspaceTranspose = MatrixFunctions.Transpose(subspace);
6.
7.   double[,] value = MatrixFunctions.Multiply(subspaceTranspose, subspace);
8.
9.   value = MatrixFunctions.Invert(value);
10.
11.   value = MatrixFunctions.Multiply(value, subspaceTranspose);
12.
13.   value = MatrixFunctions.Multiply(subspace, value);
14.
15.   return value;
16. }
Reflection Matrices

The following functions create 2D and 3D reflection matrices,
1. public static double[,] Create2DReflectionMatrix(MatrixAxis axis) {
2.   if (axis == MatrixAxis.Z)
3.     throw new InvalidOperationException(Properties.Resources.Exception_InvalidAxis);
4.
5.   var output = CreateIdentityMatrix(2);
6.
7.   if (axis == MatrixAxis.X)
8.     output[1, 1] *= -1;
9.   else  if (axis == MatrixAxis.Y )
10.     output[0, 0] *= -1;
11.
12.   return output;
13. }
14.
15. public static double[,] Create3DReflectionMatrix(Matrix3DReflectionPlane axis) {
16.   var output = CreateIdentityMatrix(4);
17.
18.   if (axis == Matrix3DReflectionPlane.XY)
19.     output[2, 2] *= -1;
20.
21.   else if (axis == Matrix3DReflectionPlane.YZ)
22.     output[0,0] *= -1;
23.
24.   else if (axis == Matrix3DReflectionPlane.ZX)
25.     output[1,1] *= -1;
26.
27.   return output;
28. }
Rotation Matrices

The following functions create 2D and 3D rotation matrices. They accept angle, unit (radians / degrees) and direction (clockwise / counter-clockwise),
1. /// <summary>
2. /// Creates 2-dimensional rotation matrix using the specified angle.
3. /// </summary>
4. public static double[,] Create2DRotationMatrix(double angle, AngleUnit unit, MatrixRotationDirection direction) {
5.   // sin and cos accept only radians
6.
8.   if (unit == AngleUnit.Degrees)
10.
11.
12.   double[,] output = new double[2, 2];
13.
18.
19.   if (direction == MatrixRotationDirection.Clockwise)
20.     output[1, 0] *= -1;
21.   else
22.     output[0, 1] *= -1;
23.
24.
25.   return output;
26. }
27.
28. /// <summary>
29. /// Creates 2-dimensional rotation matrix using the specified angle and axis.
30. /// </summary>
31. public static double[,] Create3DRotationMatrix(double angle, AngleUnit unit, MatrixAxis axis) {
32.   // sin and cos accept only radians
33.
35.   if (unit == AngleUnit.Degrees)
37.
38.
39.   double[,] output = new double[3, 3];
40.
41.   if (axis == MatrixAxis.X) {
42.     output[0, 0] = 1;
45.     output[1, 2] = -1 * Math.Sin(angleRadians);
47.   } else if (axis == MatrixAxis.Y) {
48.     output[1, 1] = 1;
50.     output[2, 0] = -1 * Math.Sin(angleRadians);
53.   } else if (axis == MatrixAxis.Z) {
54.     output[2, 2] = 1;
57.     output[0, 1] = -1 * Math.Sin(angleRadians);
59.   }
60.
61.
62.   return output;
63. }
Shearing Matrices

The following functions create shearing matrices using a specific factor over the given axis,
1. public static double[,] Create2DShearingMatrix(double factor, MatrixAxis axis) {
2.   if (axis == MatrixAxis.Z)
3.     throw new InvalidOperationException(Properties.Resources.Exception_InvalidAxis);
4.
5.   var output = CreateIdentityMatrix(2);
6.
7.   if (axis ==  MatrixAxis.X)
8.     output[0, 1] = factor;
9.
10.   else if (axis == MatrixAxis.Y )
11.     output[1, 0] = factor;
12.
13.   return output;
14. }
15.
16. public static double[,] Create3DShearingMatrix(double factor, MatrixAxis axis) {
17.   var output = CreateIdentityMatrix(4);
18.
19.   if (axis == MatrixAxis.X) {
20.     output[1, 0] = factor;
21.     output[2, 0] = factor;
22.   } else if (axis == MatrixAxis.Y) {
23.     output[0, 1] = factor;
24.     output[2, 1] = factor;
25.   } else if (axis == MatrixAxis.Z) {
26.     output[0, 2] = factor;
27.     output[1, 2] = factor;
28.   }
29.
30.   return output;
31. }

## Vectors

The following lists some of the major vector functions covered in this library. We are having a look at the raw functions. Wrappers will be covered later.

Angels

This function calculates the angle between two vectors. It relies on two other functions, DotProduct and GetMagnitude.
1. public static double AngleBetween(double[] vector1, double[] vector2, AngleUnit unit) {
2.   if (vector1.Length != vector2.Length)
3.     throw new InvalidOperationException(Properties.Resources.Exception_DimensionsMismatch);
4.
5.   var dotProduct = DotProduct(vector1, vector2);
6.   var lengthProduct = GetMagnitude(vector1) * GetMagnitude(vector2);
7.
8.   var result = Math.Acos(dotProduct / lengthProduct);
9.   if (unit == AngleUnit.Degrees)
11.
12.   return result;
13. }
Normalization

The following code demonstrates two operations: normalization (i.e. converting to unit vector) and magnitude (length) calculator.
1. /// <summary>
2. /// Normalizes a vector.
3. /// </summary>
4. public static double[] ToUnitVector(double[] input) {
5.   var length = input.Length;
6.
7.   double[] output = new double[length];
8.   var coeffecient = 1.0 / GetMagnitude(input);
9.
10.   for (int i = 0; i < length; i++) {
11.     output[i] = coeffecient * input[i];
12.   }
13.
14.   return output;
15. }
16.
17.
18. public static double GetMagnitude(double[] input) {
19.   double val = 0;
20.
21.   for (int i = 0; i < input.Length; i++)
22.     val += input[i] * input[i];
23.
24.   val = Math.Sqrt(val);
25.
26.   return val;
27. }
Dot-Product and Cross-Product

The following code demonstrates scaling, dot-product, and cross product.
1. public static double DotProduct(double[] vector1, double[] vector2) {
2.   if (vector1.Length != vector2.Length)
3.     throw new InvalidOperationException(Properties.Resources.Exception_DimensionsMismatch);
4.
5.   double product = 0;
6.   for (int i = 0; i < vector1.Length; i++)
7.     product += vector1[i] * vector2[i];
8.
9.   return product;
10. }
11.
12. public static double[] Scale(double scalar, double[] vector) {
13.   double[] product = new double[vector.Length];
14.
15.   for (int i = 0; i < vector.Length; i++) {
16.     product[i] = scalar * vector[i];
17.   }
18.
19.   return product;
20. }
21.
22.
23. public static double[] CrossProduct(double[] vector1, double[] vector2) {
24.   int length = 3;
25.
26.   if (length != vector1.Length || length != vector2.Length)
27.     throw new InvalidOperationException(Properties.Resources.Exception_3DRequired);
28.
29.   double[,] matrix = new double[length, length];
30.   for (int row = 0; row < length; row++) {
31.     for (int col = 0; col < length; col++) {
32.       if (row == 0)
33.         matrix[row, col] = 1;
34.       else if (row == 1)
35.         matrix[row, col] = vector1[col];
36.       else if (row == 2)
37.         matrix[row, col] = vector2[col];
38.     }
39.   }
40.
41.
42.   return MatrixFunctions.CrossProduct(matrix);
43. }
Projection

Projection of a vector onto another vector is handled using the following functions:
1. public static double ProjectionFactor(double[] vector1, double[] vector2) {
2.   return DotProduct(vector1, vector2) / DotProduct(vector2, vector2);
3. }
4. public static double[] Projection(double[] vector1, double[] vector2) {
5.   var factor = ProjectionFactor(vector1, vector2);
6.   return Scale(factor, vector2);
7. }

## Wrappers

Working with raw Array objects especially multi-dimensional arrays is fairly cumbersome. To make it easier, we wrapped those raw functions into two wrapper classes, Matrix and Vector. Both classes implement ICloneable and IEquatable. Both have indexers and a direct way to access their inner representation (i.e. array).

Properties and Methods

The following figures show the exported members of both classes.

Operators

We overrided some operators for both classes to allow clean and clear access to their operations. Here’s the overriden operator list for Matrix:
1. public static Matrix operator +(Matrix m) { return m; }
2. public static Matrix operator -(Matrix m) { return m.Scale(-1); }
3. public static Matrix operator +(Matrix a, Matrix b) { return a.Add(b); }
4. public static Matrix operator -(Matrix a, Matrix b) { return a.Subtract(b); }
5. public static Matrix operator +(Matrix a, double[,] b) { return a.Add(b); }
6. public static Matrix operator -(Matrix a, double[,] b) { return a.Subtract(b); }
7.
8. public static Matrix operator *(double a, Matrix m) { return m.Scale(a); }
9. public static Matrix operator *(Matrix a, Matrix b) { return a.Multiply(b); }
10. public static Matrix operator *(Matrix a, double[,] b) { return a.Multiply(b); }
11.
12. public static bool operator ==(Matrix a, Matrix b) {
13.   if ((a as object) == null || (b as object) == null)
14.     return false;
15.   return a.Equals(b);
16. }
17. public static bool operator !=(Matrix a, Matrix b) {
18.   if ((a as object) == null || (b as object) == null)
19.     return false;
20.   return a.Equals(b) == false;
21. }
22.
23. public static Matrix operator ^(Matrix a, int power) { return a.Power(power); }
24.
25.
26. public static implicit operator double[,] (Matrix m) { return m.InnerMatrix; }
27. public static explicit operator Matrix(double[,] m) { return new Linears.Matrix(m); }

## Last Word

As stated previously, the code is using a MIT license. So please use the code without any restrictions, limitations, or warranties, and feel free to relay your comments, suggestions, and corrections.