using System;
namespace Algorithms.Numeric;
/// <summary>
/// Algorithm used to find the inverse of any matrix that can be inverted.
/// </summary>
public class GaussJordanElimination
{
private int RowCount { get; set; }
/// <summary>
/// Method to find a linear equation system using gaussian elimination.
/// </summary>
/// <param name="matrix">The key matrix to solve via algorithm.</param>
/// <returns>
/// whether the input matrix has a unique solution or not.
/// and solves on the given matrix.
/// </returns>
public bool Solve(double[,] matrix)
{
RowCount = matrix.GetUpperBound(0) + 1;
if (!CanMatrixBeUsed(matrix))
{
throw new ArgumentException("Please use a n*(n+1) matrix with Length > 0.");
}
var pivot = PivotMatrix(ref matrix);
if (!pivot)
{
return false;
}
Elimination(ref matrix);
return ElementaryReduction(ref matrix);
}
/// <summary>
/// To make simple validation of the matrix to be used.
/// </summary>
/// <param name="matrix">Multidimensional array matrix.</param>
/// <returns>
/// True: if algorithm can be use for given matrix;
/// False: Otherwise.
/// </returns>
private bool CanMatrixBeUsed(double[,] matrix) => matrix?.Length == RowCount * (RowCount + 1) && RowCount > 1;
/// <summary>
/// To prepare given matrix by pivoting rows.
/// </summary>
/// <param name="matrix">Input matrix.</param>
/// <returns>Matrix.</returns>
private bool PivotMatrix(ref double[,] matrix)
{
for (var col = 0; col + 1 < RowCount; col++)
{
if (matrix[col, col] == 0)
{
// To find a non-zero coefficient
var rowToSwap = FindNonZeroCoefficient(ref matrix, col);
if (matrix[rowToSwap, col] != 0)
{
var tmp = new double[RowCount + 1];
for (var i = 0; i < RowCount + 1; i++)
{
// To make the swap with the element above.
tmp[i] = matrix[rowToSwap, i];
matrix[rowToSwap, i] = matrix[col, i];
matrix[col, i] = tmp[i];
}
}
else
{
// To return that the matrix doesn't have a unique solution.
return false;
}
}
}
return true;
}
private int FindNonZeroCoefficient(ref double[,] matrix, int col)
{
var rowToSwap = col + 1;
// To find a non-zero coefficient
for (; rowToSwap < RowCount; rowToSwap++)
{
if (matrix[rowToSwap, col] != 0)
{
return rowToSwap;
}
}
return col + 1;
}
/// <summary>
/// Applies REF.
/// </summary>
/// <param name="matrix">Input matrix.</param>
private void Elimination(ref double[,] matrix)
{
for (var srcRow = 0; srcRow + 1 < RowCount; srcRow++)
{
for (var destRow = srcRow + 1; destRow < RowCount; destRow++)
{
var df = matrix[srcRow, srcRow];
var sf = matrix[destRow, srcRow];
for (var i = 0; i < RowCount + 1; i++)
{
matrix[destRow, i] = matrix[destRow, i] * df - matrix[srcRow, i] * sf;
}
}
}
}
/// <summary>
/// To continue reducing the matrix using RREF.
/// </summary>
/// <param name="matrix">Input matrix.</param>
/// <returns>True if it has a unique solution; false otherwise.</returns>
private bool ElementaryReduction(ref double[,] matrix)
{
for (var row = RowCount - 1; row >= 0; row--)
{
var element = matrix[row, row];
if (element == 0)
{
return false;
}
for (var i = 0; i < RowCount + 1; i++)
{
matrix[row, i] /= element;
}
for (var destRow = 0; destRow < row; destRow++)
{
matrix[destRow, RowCount] -= matrix[destRow, row] * matrix[row, RowCount];
matrix[destRow, row] = 0;
}
}
return true;
}
}